WordPress .htaccess Generator

Build a clean WordPress .htaccess with HTTPS, www redirects, security hardening, caching and compression rules.

Runs in your browser

Options

WordPress rewrite rules

Use / when WordPress is installed in the web root.

Redirects

301-redirects every http:// request to https://.

Canonical host
Security

Options -Indexes

Breaks Jetpack and the mobile app.

Headers

nosniff, Referrer-Policy, X-Frame-Options, Permissions-Policy

Performance
PHP limits

Only works with mod_php — not PHP-FPM, LiteSpeed LSAPI or most managed hosts.

Placed in the custom section, above the WordPress block.

Before you upload
This file is for Apache and LiteSpeed only — Nginx ignores .htaccess. Download a backup of your current .htaccess first; a typo causes a site-wide 500 error.

.htaccess (site root)

# ==================================================================
# Custom rules — keep them ABOVE "# BEGIN WordPress".
# WordPress rewrites only the lines between its own BEGIN/END markers
# when permalinks are saved; everything outside is left untouched.
# Generated with Utilo. Back up your existing .htaccess first.
# ==================================================================

# ----- Force HTTPS -----
<IfModule mod_rewrite.c>
	RewriteEngine On
	RewriteCond %{HTTPS} off
	# Skip when a proxy/CDN (e.g. Cloudflare, load balancer) already terminated HTTPS.
	RewriteCond %{HTTP:X-Forwarded-Proto} !https
	RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>

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

# ----- Protect .htaccess and .htpasswd -----
<FilesMatch "^\.ht">
	Require all denied
</FilesMatch>

# ----- Disable directory browsing -----
Options -Indexes

# ----- Security headers -----
<IfModule mod_headers.c>
	Header always set X-Content-Type-Options "nosniff"
	Header always set Referrer-Policy "strict-origin-when-cross-origin"
	Header always set X-Frame-Options "SAMEORIGIN"
	Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
</IfModule>

# ----- Browser caching -----
# WordPress adds ?ver= to theme/plugin CSS and JS, so long lifetimes are safe for versioned assets.
<IfModule mod_expires.c>
	ExpiresActive On
	ExpiresByType text/html                     "access plus 0 seconds"
	ExpiresByType application/json              "access plus 0 seconds"
	ExpiresByType application/xml               "access plus 0 seconds"
	ExpiresByType text/xml                      "access plus 0 seconds"
	ExpiresByType application/rss+xml           "access plus 1 hour"
	ExpiresByType application/atom+xml          "access plus 1 hour"
	ExpiresByType text/css                      "access plus 1 year"
	ExpiresByType application/javascript        "access plus 1 year"
	ExpiresByType text/javascript               "access plus 1 year"
	ExpiresByType image/jpeg                    "access plus 1 year"
	ExpiresByType image/png                     "access plus 1 year"
	ExpiresByType image/gif                     "access plus 1 year"
	ExpiresByType image/webp                    "access plus 1 year"
	ExpiresByType image/avif                    "access plus 1 year"
	ExpiresByType image/svg+xml                 "access plus 1 year"
	ExpiresByType image/x-icon                  "access plus 1 week"
	ExpiresByType image/vnd.microsoft.icon      "access plus 1 week"
	ExpiresByType font/woff2                    "access plus 1 year"
	ExpiresByType font/woff                     "access plus 1 year"
	ExpiresByType font/ttf                      "access plus 1 year"
	ExpiresByType font/otf                      "access plus 1 year"
	ExpiresByType application/vnd.ms-fontobject "access plus 1 year"
	ExpiresByType video/mp4                     "access plus 1 year"
	ExpiresByType video/webm                    "access plus 1 year"
	ExpiresByType audio/mpeg                    "access plus 1 year"
	ExpiresByType application/pdf               "access plus 1 month"
</IfModule>

# ----- Compression (gzip) -----
<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/x-javascript application/json application/ld+json
		AddOutputFilterByType DEFLATE application/xml application/xhtml+xml application/rss+xml application/atom+xml
		AddOutputFilterByType DEFLATE image/svg+xml image/x-icon image/vnd.microsoft.icon
		AddOutputFilterByType DEFLATE font/ttf font/otf application/vnd.ms-fontobject
	</IfModule>
</IfModule>

# ===================== End of custom rules =====================

# 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

wp-content/uploads/.htaccess

# wp-content/uploads/.htaccess
# Disable PHP execution in the uploads folder. Media files are served
# normally; any PHP file an attacker manages to upload cannot run.
<FilesMatch "\.(?i:php[0-9]?|phtml|phar|phps|pht)$">
	Require all denied
</FilesMatch>

Some browsers save the file as “htaccess” without the leading dot — rename it to .htaccess after downloading.

What does the WordPress .htaccess file do?

.htaccess is a per-directory configuration file read by Apache and LiteSpeed on every request. WordPress uses it for one essential job: the rewrite block between # BEGIN WordPress and # END WordPress sends every URL that is not a real file or folder to index.php, which is what makes pretty permalinks work.

The same file is also the most practical place to add server-level rules on shared hosting: redirect HTTP to HTTPS, pick one canonical host (www or non-www), deny access to wp-config.php, stop directory listings, set long browser-cache lifetimes for static assets and enable gzip compression. These rules run before PHP starts, so they are faster than doing the same thing in a plugin.

WordPress rewrites only the lines between its own markers when you save Settings → Permalinks (via insert_with_markers()). Anything you put outside those markers survives. This generator therefore places all custom rules in clearly commented sections above the WordPress block, so redirects and access rules are evaluated before the catch-all rewrite to index.php.

How to use the WordPress .htaccess Generator

  1. Pick your installation type — single site, multisite with subdirectories or multisite with subdomains — and the WordPress folder if it is not the web root.
  2. Choose redirects (HTTPS, www or non-www) and the security, header, caching and compression rules you want. Start from the Recommended, Minimal or Hardened preset.
  3. Download a backup of your current .htaccess via FTP or your host’s file manager.
  4. Copy or download the generated file and upload it to the folder that contains wp-config.php. Optionally upload the second file to wp-content/uploads/.
  5. Load the home page, a post, /wp-admin/ and an image. If you see a 500 error, restore the backup and remove the last block you added.

Example

The official single-site block that WordPress writes when pretty permalinks are enabled:

# BEGIN WordPress
<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

Gotchas worth knowing

  • Nginx ignores .htaccess. On Nginx hosts the equivalent rules go into the server block — the Redirect Generator outputs Nginx syntax for redirects.
  • One hop, not two. When you force HTTPS and a canonical host together, the host rule redirects straight to https://, so http://example.com reaches https://www.example.com in a single 301.
  • Behind a CDN or proxy, %{HTTPS} can be off even though visitors use HTTPS. The X-Forwarded-Proto condition prevents the classic redirect loop with Cloudflare Flexible SSL and load balancers.
  • php_value only works with mod_php. On PHP-FPM it causes a 500 error unless wrapped in <IfModule> — which the generator does. Use .user.ini or your host’s PHP settings instead.
  • HSTS is sticky. Browsers cache it for the whole max-age; test with a short value before committing to a year or preloading.
  • Blocking xmlrpc.php stops brute-force and pingback abuse but also breaks Jetpack and the official mobile apps.

Disabling PHP in the uploads folder

A common post-exploitation step is uploading a PHP web shell through a vulnerable form or plugin into wp-content/uploads. Media files never need to execute, so a small .htaccess in that folder denying .php, .phtml and .phar files neutralises the attack without affecting images or PDFs.

Frequently Asked Questions

Where is the .htaccess file in WordPress?

In the folder that contains wp-config.php and wp-admin/, usually public_html or www. It starts with a dot, so enable “show hidden files” in your FTP client or file manager.

Will WordPress overwrite my custom rules?

No, as long as they are outside the # BEGIN WordPress / # END WordPress markers. WordPress only replaces the lines between its markers when permalinks are saved. Security plugins use their own markers in the same way.

Why do my custom rules go above the WordPress block?

The WordPress block ends with a catch-all RewriteRule . /index.php [L]. Rewrite-based redirects placed after it may never run, so HTTPS, host and blocking rules belong first.

What is the default .htaccess for WordPress Multisite?

Multisite uses a different block that maps wp-admin, wp-content and wp-includes for every site. Choose “Multisite — subdirectories” or “Multisite — subdomains” above; the rules match the ones WordPress shows under Tools → Network Setup.

My site shows “500 Internal Server Error” after uploading. What now?

Restore your backup immediately, then add the sections back one at a time. The usual culprits are php_value on PHP-FPM, a module that is not installed, or Options being disallowed by the host’s AllowOverride setting.

Can I add redirects for old URLs here?

Yes — paste them into Extra rules, or use the WordPress Redirect Generator to convert a list of old and new URLs into validated Redirect and RewriteRule lines. For crawl rules see the robots.txt Generator.