.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.
# .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.
Fix the highlighted fields to update the output.
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
# 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
<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
cp .htaccess .htaccess.bak, so you can restore it in seconds.How to use
- Connect to your site over SFTP or your host’s file manager and open the folder that contains
wp-config.phpandwp-content. The.htaccessfile lives there; enable “show hidden files” if you cannot see it. - Download a copy of the current
.htaccessas a backup. If WordPress or a plugin added rules above or below the WordPress block, keep those lines. - Pick your options on the Security, Caching, Redirects & SSL and Access tabs. Leave Force HTTPS off until
https://loads your site without certificate warnings. - Copy or download the output and replace the contents of
.htaccess. Paste back any plugin rules you want to keep, outside the# BEGIN WordPressand# END WordPressmarkers. - 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 -Indexesis the usual culprit whenAllowOverrideexcludes 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,denylines 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 deniedandRequire ipfrom mod_authz_core. Apache 2.2 is end of life and does not understand these directives. - LiteSpeed Web Server and OpenLiteSpeed read
.htaccessand support these directives, including mod_rewrite rules. OpenLiteSpeed only re-reads rewrite rules after a graceful restart. - Nginx ignores
.htaccessentirely. Translate the rules into your server block or ask your host. - The WordPress block matches what current WordPress releases write, including the
HTTP_AUTHORIZATIONline added in WordPress 5.6 for application passwords. image/avifcaching only matters if your server maps.avifto 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?
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?
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?
# BEGIN WordPress and # END WordPress markers when you save permalinks. Everything this generator adds sits outside them.Does blocking ?author= stop username discovery completely?
Why is my custom 404 page not shown for missing posts?
ErrorDocument 404 line only applies to missing static files that bypass WordPress.