.htaccess Generator for WordPress

Build a commented Apache 2.4 .htaccess for WordPress with security rules, compression, browser caching, HTTPS redirects and login IP allowlists.

Enable JavaScript to customise; default output below.

Security

More options Show
Live preview .htaccess
# .htaccess for WordPress (Apache 2.4)
# Back up your current .htaccess before replacing it.
# Custom rules live outside the "BEGIN WordPress" markers so WordPress
# never overwrites them when permalinks are saved.

# Hide the Apache version on server-generated pages.
ServerSignature Off

# Block directory browsing.
Options -Indexes

# Protect wp-config.php.
<Files wp-config.php>
Require all denied
</Files>

# Block dotfiles, install leftovers, dependency manifests and logs.
<FilesMatch "^(?:\..*|readme\.html|license\.txt|wp-config-sample\.php|composer\.(?:json|lock)|package(?:-lock)?\.json|debug\.log|error_log)$">
Require all denied
</FilesMatch>
# Block hidden folders such as .git and .svn (keeps .well-known for SSL checks).
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule (?:^|/)\.(?!well-known/) - [F,L]
</IfModule>

# Block PHP execution in the uploads folder.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^wp-content/uploads/.*\.(?:php[0-9]*|phtml|phar|phps)$ - [NC,F,L]
</IfModule>

# Security headers.
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>

# Gzip compression for text-based responses.
<IfModule mod_deflate.c>
<IfModule mod_filter.c>
AddOutputFilterByType DEFLATE text/html text/plain text/css text/xml text/javascript
AddOutputFilterByType DEFLATE application/javascript application/json application/xml
AddOutputFilterByType DEFLATE application/rss+xml application/atom+xml image/svg+xml
AddOutputFilterByType DEFLATE font/ttf font/otf application/vnd.ms-fontobject
</IfModule>
</IfModule>

# Browser caching for static assets.
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/webp "access plus 1 month"
ExpiresByType image/avif "access plus 1 month"
ExpiresByType image/svg+xml "access plus 1 month"
ExpiresByType image/x-icon "access plus 1 month"
ExpiresByType font/woff2 "access plus 1 month"
ExpiresByType font/woff "access plus 1 month"
ExpiresByType text/css "access plus 1 month"
ExpiresByType text/javascript "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
</IfModule>

# BEGIN WordPress
# The directives (lines) between "BEGIN WordPress" and "END WordPress" are
# dynamically generated, and should only be modified via WordPress filters.
# Any changes to the directives between these markers will be overwritten.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

# END WordPress

Output is valid and updates as you type.

Put together a working .htaccess file for a WordPress site on Apache 2.4 without hunting for snippets that may be outdated. Turn on the security, caching and redirect rules you want, and the generator writes a commented file that keeps the standard WordPress permalink block intact.

Best practices

Three habits that keep a new .htaccess from taking the site down.

Keep rules above the WordPress block

WordPress rewrites only the lines between # BEGIN WordPress and # END WordPress when you save permalinks. Put your own rules above that block so saving permalinks never removes them.

Check which Apache modules are loaded

Rules wrapped in <IfModule> are skipped silently when the module is missing, so a rule can look active and do nothing. Confirm mod_rewrite, mod_headers, mod_expires and mod_deflate are enabled, or ask your host.

Back up before you replace the file

One directive your host does not permit returns a 500 error for the whole site. Keep a copy of the current file, for example cp .htaccess .htaccess.bak, so you can restore it in seconds.

How to use

  1. Connect to your site over SFTP or your host’s file manager and open the folder that contains wp-config.php and wp-content. The .htaccess file lives there; enable “show hidden files” if you cannot see it.
  2. Download a copy of the current .htaccess as a backup. If WordPress or a plugin added rules above or below the WordPress block, keep those lines.
  3. Pick your options on the Security, Caching, Redirects & SSL and Access tabs. Leave Force HTTPS off until https:// loads your site without certificate warnings.
  4. Copy or download the output and replace the contents of .htaccess. Paste back any plugin rules you want to keep, outside the # BEGIN WordPress and # END WordPress markers.
  5. Load the home page, a post, /wp-admin/ and the login page in a private window. If you get a 500 error, restore the backup and switch options off one at a time to find the line your host does not allow.

Example

A shop that already has SSL wants every visit on https://www.example.com, gzip compression and year-long caching for assets. Choosing Force HTTPS, the www canonical host with the domain example.com and a 1 year lifetime produces redirect rules like these before the WordPress block:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_HOST} !=www.example.com [NC]
RewriteRule ^ https://www.example.com%{REQUEST_URI} [L,R=301]
</IfModule>

A request for http://example.com/cart/ then goes straight to https://www.example.com/cart/ in one 301 hop, and the query string is carried over automatically.

Pitfalls

  • A single directive your host does not permit returns a 500 error for the whole site. Options -Indexes is the usual culprit when AllowOverride excludes Options; switch it off and ask your host.
  • Rules inside <IfModule> are skipped silently when the module is missing. Without mod_rewrite, permalinks, redirects and the upload and hidden-folder blocks do nothing; without mod_headers, security headers and HSTS are never sent; without mod_expires or mod_deflate, caching and compression are skipped.
  • Forcing HTTPS behind a proxy or CDN that sends plain HTTP to Apache can loop. The generated rule checks X-Forwarded-Proto, but some setups use a different header, so test before relying on it.
  • HSTS is cached by browsers for the whole max-age. If you later drop SSL, returning visitors cannot reach the site over HTTP until it expires.
  • Disabling XML-RPC breaks Jetpack, some mobile publishing apps and remote pingbacks.
  • The login allowlist uses the IP Apache sees. Behind Cloudflare or a load balancer that is the proxy address, so you may lock yourself out; keep FTP access ready to remove the block.
  • The WordPress block assumes WordPress is installed in the web root. For a subdirectory install, keep the block WordPress writes under Settings, Permalinks, which has the correct RewriteBase.
  • Do not mix these rules with old Order allow,deny lines in the same section. Apache 2.4 evaluates legacy and new access rules unpredictably when both appear.

Compatibility

  • Written for Apache 2.4 and later, using Require all denied and Require ip from mod_authz_core. Apache 2.2 is end of life and does not understand these directives.
  • LiteSpeed Web Server and OpenLiteSpeed read .htaccess and support these directives, including mod_rewrite rules. OpenLiteSpeed only re-reads rewrite rules after a graceful restart.
  • Nginx ignores .htaccess entirely. Translate the rules into your server block or ask your host.
  • The WordPress block matches what current WordPress releases write, including the HTTP_AUTHORIZATION line added in WordPress 5.6 for application passwords.
  • image/avif caching only matters if your server maps .avif to that type; older Apache MIME files may not.

Frequently asked questions

Common questions about editing .htaccess on a WordPress site.

Where is the .htaccess file in WordPress?
In the same folder as wp-config.php, usually the web root such as public_html. The leading dot hides it by default in most file managers and FTP clients.
How do I test the new rules?
Open the site in a private window and check a post, a page, an image and the admin. Run curl -I https://www.example.com/ to confirm redirects, Cache-Control and security headers, and request /wp-config.php to confirm it returns 403.
Will WordPress overwrite my custom rules?
No. WordPress only rewrites the lines between its own # BEGIN WordPress and # END WordPress markers when you save permalinks. Everything this generator adds sits outside them.
Does blocking ?author= stop username discovery completely?
No. It closes one common scan, but author archive links and the REST users endpoint can still reveal usernames. Pair it with strong passwords and two-factor login.
Why is my custom 404 page not shown for missing posts?
WordPress handles every URL that is not a real file, so it renders the theme’s 404 template. The ErrorDocument 404 line only applies to missing static files that bypass WordPress.

From the people who built this tool

WP Adminify

The WordPress admin, rebuilt: a dashboard worth looking at, menu and column control, a real file manager and the login page your client sees.

See WP Adminify Free version on WordPress.org

Weekly drops

New tools, when there are new tools

One email when something worth using ships. No schedule to fill, so no filler.

Your address goes nowhere else, and one click unsubscribes.