Skip to main content

Building an .htaccess File: Redirects, HTTPS, www, and Caching Without Breaking Your Site

A practical guide to writing Apache .htaccess rules — 301 redirects, force HTTPS, www canonicalization, caching, and custom error pages — with mod_rewrite basics.

Published By Li Lei
#htaccess #apache #mod_rewrite #https #seo

Building an .htaccess File: Redirects, HTTPS, www, and Caching Without Breaking Your Site

The .htaccess file is Apache's per-directory configuration hook. Drop one in your web root and Apache reads it on every request to that folder and its children, no server restart required. That convenience is exactly why shared hosting hands you an .htaccess instead of access to httpd.conf: you get to write redirects, force HTTPS, set cache headers, and swap in custom error pages, all without touching the main server config.

It is also why a single typo can take your whole site offline with a bare "500 Internal Server Error" and no hint in the browser. This guide walks through the rules I reach for most often, the mod_rewrite logic behind them, and how to test changes before they reach a single visitor.

How mod_rewrite Actually Reads Your Rules

Most of the interesting .htaccess work runs through mod_rewrite, Apache's URL-rewriting engine. Three directives carry the load:

  • RewriteEngine On turns the engine on. Nothing rewrites until this line runs.
  • RewriteCond is a condition. It tests something about the request — the protocol, the host, the referer — and the rule below it only fires when the condition matches.
  • RewriteRule is the action. It matches the requested path against a pattern and either rewrites it internally or sends a redirect.

The order matters more than beginners expect. Apache processes rewrite rules top to bottom and stops at the first rule carrying the [L] ("last") flag. So your HTTPS redirect has to sit above your www canonicalization, which sits above any application rewrite. Get the order wrong and you can bounce a request between two redirects until the browser gives up with a "too many redirects" error.

A 301 redirect — the permanent kind that search engines respect and cache — is expressed as a RewriteRule ending in the [R=301,L] flag. A 302 is temporary and tells crawlers to keep indexing the old URL, which is almost never what you want for an HTTPS or domain migration. When in doubt, 301.

Forcing HTTPS the Right Way

Once you have an SSL certificate installed, you want every plain-http request to land on https. The canonical test is whether the request came in encrypted, which mod_rewrite exposes through the %{HTTPS} server variable:

RewriteEngine On

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

Read it as a sentence: if the connection is not encrypted (%{HTTPS} off), then redirect the whole request to the same host and path under https, permanently. %{HTTP_HOST} preserves whatever domain the visitor typed and %{REQUEST_URI} keeps the path and query string, so http://example.com/pricing?ref=x lands cleanly on its https twin in a single hop.

One subtlety: if Apache sits behind a load balancer or CDN that terminates TLS, %{HTTPS} may always read off at the origin. In that setup, test %{HTTP:X-Forwarded-Proto} instead. Always confirm with curl -I http://example.com and look for exactly one 301, not a chain.

Picking One Canonical Host

Search engines treat example.com and www.example.com as two different sites unless you tell them otherwise. Pick one and redirect the other. Here is a worked block that forces HTTPS and the www host together, in the correct order:

RewriteEngine On

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

# 2) Then force the www host
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^ https://www.example.com%{REQUEST_URI} [R=301,L]

The [NC] flag makes the host match case-insensitive. A visitor hitting http://example.com/blog first gets bumped to https://example.com/blog by block 1, then to https://www.example.com/blog by block 2 — two 301s in the worst case, but every subsequent visit goes straight to the canonical URL because browsers and crawlers cache permanent redirects.

If you prefer the bare domain, flip block 2 to match ^www\.example\.com$ and redirect to https://example.com. The cardinal rule: choose exactly one direction. Enabling force-www and strip-www at the same time creates an infinite loop, www → non-www → www, and Apache will happily redirect forever.

Caching, Compression, and Custom Error Pages

Not everything in .htaccess runs through mod_rewrite. Caching and compression are independent modules, and they are where you win the most perceived speed for the least effort.

mod_expires sets how long browsers may reuse a file before re-fetching it:

<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresByType image/jpeg "access plus 1 year"
  ExpiresByType text/css   "access plus 1 month"
  ExpiresByType text/html  "access plus 1 hour"
</IfModule>

The <IfModule> wrapper is not optional decoration. If mod_expires is not loaded on your host, an unwrapped directive throws a 500; wrapped, it is silently skipped. Give images a year, CSS and JS a month, and keep HTML short — an hour or less — so a fresh deploy actually reaches visitors instead of serving a stale page from cache.

mod_deflate gzips text responses on the way out, often shrinking HTML, CSS, and JSON by 60–80%. Custom error pages are a one-liner each: ErrorDocument 404 /404.html and ErrorDocument 503 /maintenance.html. The path is server-relative, and the file has to actually exist, or you get Apache's default page anyway.

I learned the testing discipline the hard way. Early on I edited a live .htaccess directly, fat-fingered a quote inside a RewriteRule, and watched the entire site return 500s for the four minutes it took me to find the stray character. Now I never touch the live file in place. I keep the working copy as .htaccess.bak, drop the new version in, and if anything 500s I swap them back — Apache rereads on the very next request, no reload needed. If you have shell access, apachectl configtest catches syntax errors before they ever hit a user, and a staging subdomain catches the logic errors that pass syntax but loop forever.

Test, Then Deploy

The pattern that keeps me out of trouble: assemble the rules, eyeball the order (HTTPS → host → app), validate the syntax, then dry-run on staging before the live swap. Keep a backup every single time, because a browser showing "500 Internal Server Error" tells you nothing about which line broke.

If hand-writing these blocks feels error-prone, the .htaccess Generator builds them for you: tick the blocks you want — force HTTPS, choose a canonical host, enable gzip and cache headers, add custom error pages — and it emits correctly ordered, commented output you can copy or download. It defaults to R=301 and orders the rewrite blocks so the loops described above can't happen. While you're tightening up your site's crawl behavior, pair it with the robots.txt Generator to control which paths search engines fetch in the first place.

Good .htaccess hygiene is mostly discipline: one canonical host, permanent redirects, short-lived HTML, modules wrapped in <IfModule>, and a backup you can fall back to in five seconds. Get those right and the file quietly does its job on every request.


Made by Toolora · Updated 2026-06-13