Cheatsheet

.htaccess Cheatsheet

# .htaccess Cheatsheet

.htaccess is Apache's per-directory config override file — the standard way to control routing, headers, and caching on shared hosting where you can't touch the main server config. This sheet covers the directives used most, drawn from real, load-bearing rewrite rules.

Quick reference

Core directives

Directive Purpose
`RewriteEngine On` Turns on `mod_rewrite` for this scope — required before any `RewriteRule`
`RewriteCond` A condition that must be true for the *next* `RewriteRule` to apply
`RewriteRule pattern target [flags]` The actual rewrite/redirect, `pattern` is a regex against the URL path
`Header set/always set <Name> <Value>` Sets a response header (`always` applies to error responses too)
`ErrorDocument <code> <path>` Custom error page for a given status code
`Options -Indexes` Disables Apache's automatic directory listing
`<IfModule mod_x.c> ... </IfModule>` Only apply the block if that Apache module is loaded (safe on hosts without it)

Common RewriteRule flags

Flag Meaning
`[L]` Last — stop processing further rules if this one matched
`[R]` / `[R=301]` / `[R=302]` Issue an HTTP redirect with the given status (default 302)
`[QSA]` Query String Append — preserve the original `?query=string` on the rewritten URL
`[NC]` No Case — case-insensitive pattern match
`[F]` Forbidden — return 403 instead of rewriting
`[E=VAR:value]` Set an environment variable other directives/scripts can read

Common patterns

Force HTTPS

RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

Redirect www to a canonical apex domain

RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]

Clean URLs — route everything through a front controller

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]

The two conditions matter: without !-f/!-d, requests for real files/directories (images, CSS) would also get routed to index.php instead of served directly.

Forward the Authorization header (common on PHP-FPM/CGI setups)

RewriteCond %{HTTP:Authorization} .+
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

On many shared-hosting PHP configurations, Apache strips the Authorization header from $_SERVER by default — this rule re-injects it as an environment variable PHP can read.

Security headers

<IfModule mod_headers.c>
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "DENY"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Content-Security-Policy "default-src 'self'"
</IfModule>

Long-lived caching for static assets

<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType text/css "access plus 1 year"
    ExpiresByType application/javascript "access plus 1 year"
</IfModule>

Safe to set aggressively long max-age values only if your assets are cache-busted with a version/hash in the filename or query string — otherwise a deploy won't reach returning visitors until the cache expires.

Block direct access to sensitive folders

RewriteRule ^(config|data|snapshots)(/|$) - [F,L]

Pitfalls

  • RewriteEngine On is required per scope: a .htaccess in a subdirectory doesn't automatically inherit it from the parent — omit it and every RewriteRule below is silently ignored, not an error.
  • Rule order matters, and [L] doesn't mean "stop everything": [L] stops the current pass through the ruleset, but Apache then restarts matching from the top against the new URL. This can cause unexpected loops if a later rule matches the rewritten URL again — add another RewriteCond to guard against it.
  • Header set inside an <IfModule> block that never gets checked for a matching request path: if you scope Header directives inside a <FilesMatch> or <Directory> block, headers only apply to matching requests — a common cause of "why isn't my CSP header showing up" on some routes but not others.
  • Case sensitivity varies by OS: Apache on Linux treats RewriteRule patterns as case-sensitive by default (add [NC] for case-insensitive matching); this can differ from local development on Windows/macOS where the filesystem itself is case-insensitive, masking the bug until deploy.
  • .htaccess is parsed on every request unless disabled: AllowOverride All (needed for .htaccess to work at all) has a real performance cost since Apache re-reads and re-parses the file per request — on high-traffic sites, moving these directives into the main vhost config (where supported) avoids that overhead entirely.

Related ZipKit tools

  • Nginx Reverse Proxy Generator — the equivalent config generator for nginx's location/proxy_pass syntax, if you're migrating off Apache.
  • CSP Header Builder — build the Content-Security-Policy string to drop straight into a Header set line.

Related cheatsheets