.htaccess Guide: Redirects, Caching and Security Rules

A practical .htaccess guide for Apache, cPanel and LiteSpeed: one-hop HTTPS and www redirects, 301s, caching, security headers and fixing 500 errors.

By the MakeItFlow team Updated 9 min read
Share LinkedIn X Facebook WhatsApp
.htaccess Guide: Redirects, Caching and Security Rules

If your site runs on cPanel or any Apache-compatible host, the .htaccess file is where redirects, caching, compression and many security rules live. It's also one of the easiest files to break: a single typo returns a 500 error for every page, and a careless redirect can loop forever. This .htaccess guide gives you tested, copy-ready blocks for redirects, caching and security, explains where each goes, and lists the mistakes that cause most outages. The directive behaviour was checked against the Apache HTTP Server 2.4 documentation in October 2026.

Key takeaways

  • Download a copy of your .htaccess file before editing, since a single typo returns a 500 error for every page.
  • Send the HTTP, HTTPS, www and non-www versions to your chosen address in a single 301 redirect.
  • Exclude /.well-known/ from redirects and dotfile blocks so Let's Encrypt and AutoSSL renewals do not fail.
  • Always write 301 with the Redirect directive, because without a status it sends a temporary 302.

What .htaccess is and where to find it

.htaccess is a per-directory configuration file. Apache reads it on every request and applies it to that directory and everything below it. On cPanel you'll find it in public_html; open File Manager, then Settings > Show Hidden Files (dotfiles) to see it.

A few points from the Apache docs worth knowing:

  • It only works if the server allows it. AllowOverride None, the Apache default, means .htaccess is ignored completely. Shared hosts normally enable it.
  • It's slower than the main config. Apache checks every directory on the path for an .htaccess file and recompiles rewrite regular expressions on every request. If you have root access to a VPS, put rules in the virtual host instead.
  • Inside .htaccess, rewrite patterns have no leading slash. RewriteRule ^old-page$ works in .htaccess; RewriteRule ^/old-page$ won't match there.

LiteSpeed and OpenLiteSpeed

Many shared hosts run LiteSpeed Web Server instead of Apache. LiteSpeed's documentation describes it as a drop-in replacement on cPanel that "tries to match Apache rewrite rules as much as it can", so the rules below generally work unchanged. OpenLiteSpeed, the free edition, has more limited .htaccess support and may need a restart before changes apply, so check your host's documentation if a rule seems to be ignored.

Before editing, download a copy of the current file. If anything goes wrong, re-upload it and the site is back instantly.

HTTPS and www redirects in one hop

Search engines treat http://example.com, http://www.example.com, https://example.com and https://www.example.com as four addresses. All of them should reach your chosen version in a single 301 redirect, not a chain of two or three.

Redirect to https://example.com (non-www)

RewriteEngine On

# Leave certificate validation files alone
RewriteCond %{REQUEST_URI} !^/\.well-known/

# Redirect if not HTTPS, or if the host starts with www
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\. [NC]
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]

Redirect to https://www.example.com (www)

RewriteEngine On
RewriteCond %{REQUEST_URI} !^/\.well-known/
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^ https://www.example.com%{REQUEST_URI} [R=301,L]

How it works: conditions joined with [OR] form a group, and the .well-known condition above them must also be true. So the rule fires when the request is not for /.well-known/ and is either plain HTTP or the wrong hostname. Writing the hostname out in full, rather than using %{HTTP_HOST}, is what makes it one hop.

Why exclude /.well-known/? Let's Encrypt and cPanel AutoSSL validate domains by fetching files under /.well-known/. Redirecting those requests can make certificate renewal fail, especially for a hostname that doesn't yet have a valid certificate.

Behind Cloudflare or a load balancer

If a proxy terminates HTTPS and talks to your server over HTTP, %{HTTPS} is always off and the rule above loops. Check the forwarded header instead:

RewriteCond %{HTTP:X-Forwarded-Proto} !https

Better still, give your server a certificate and use Cloudflare's Full (strict) mode, as covered in our Cloudflare free plan settings guide.

WordPress, Shopify, Wix and Squarespace

  • WordPress: put your redirect block above the # BEGIN WordPress section. WordPress rewrites everything to index.php, and it may overwrite anything between its own markers. Also set both URLs in Settings > General to your canonical https:// address.
  • Shopify, Wix, Squarespace: you don't have an .htaccess file. HTTPS and the primary domain are set in the platform's domain settings.

After any change, test all four versions with our redirect checker. It follows every hop, flags 302s that should be 301s and catches loops.

Trailing slashes

Pick one style and stick to it. Apache's DirectorySlash already adds a slash to real directories. To remove trailing slashes from everything else:

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/\.well-known/
RewriteRule ^(.+)/$ /$1 [R=301,L]

Don't add trailing-slash rules on WordPress. WordPress enforces the style set in Settings > Permalinks, and a conflicting rule causes a redirect loop.

301 redirects for moved pages

For single page moves, Redirect (from mod_alias) is the simplest option:

Redirect 301 /old-services /services
Redirect 301 /about-us.html /about
Redirect 410 /discontinued-product

Points the Apache docs make clear:

  • Without a status, Redirect sends a 302 (temporary). Always write 301 or permanent for moves.
  • It matches complete path segments: /old-services also redirects /old-services/web-design, with the extra path appended, but not /old-services-2024.
  • Query strings are carried over to the target.

For patterns, use RedirectMatch or RewriteRule:

# Move a whole blog folder to /news, keeping the slug
RedirectMatch 301 ^/blog/(.*)$ /news/$1

Pick one module for redirects if you can. Mixing Redirect lines with a RewriteRule block makes it hard to predict which runs first, so on WordPress sites many people use RewriteRule ... [R=301,L] for everything. Keep redirects pointing at the final URL; our guide to redirect chains explains why.

Browser caching with mod_expires and Cache-Control

Long cache lifetimes for static files make repeat visits much faster:

<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType image/avif "access plus 1 year"
    ExpiresByType image/webp "access plus 1 year"
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType image/svg+xml "access plus 1 year"
    ExpiresByType font/woff2 "access plus 1 year"
    ExpiresByType text/css "access plus 1 month"
    ExpiresByType application/javascript "access plus 1 month"
    ExpiresByType text/html "access plus 0 seconds"
</IfModule>

mod_expires sets both the Expires and Cache-Control: max-age headers. Keep HTML at zero so visitors always get fresh pages, and only use a year-long lifetime for files whose names change when they're updated (most themes and build tools add a version string). For immutable versioned assets you can add the header directly:

<IfModule mod_headers.c>
    <FilesMatch "\.(css|js|woff2|webp|avif)$">
        Header set Cache-Control "public, max-age=31536000, immutable"
    </FilesMatch>
</IfModule>

Caching plugins such as LiteSpeed Cache or WP Rocket write their own blocks into .htaccess. Don't duplicate them. Our guide to browser caching and Cache-Control covers good values in more depth.

Compression with mod_deflate and mod_brotli

<IfModule mod_brotli.c>
    AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/css text/javascript application/javascript application/json image/svg+xml
</IfModule>
<IfModule !mod_brotli.c>
    <IfModule mod_deflate.c>
        AddOutputFilterByType DEFLATE text/html text/plain text/css text/javascript application/javascript application/json image/svg+xml
    </IfModule>
</IfModule>

mod_brotli is available from Apache 2.4.26 but many hosts don't load it; the block falls back to gzip via mod_deflate. LiteSpeed compresses responses itself, so these lines are often unnecessary there. Don't compress images such as JPEG, PNG or WebP; they're already compressed. See gzip vs Brotli for the differences.

Security headers in .htaccess

<IfModule mod_headers.c>
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
    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"
    Header always set Permissions-Policy "geolocation=(), camera=(), microphone=()"
    Header unset X-Powered-By
    Header always unset X-Powered-By
</IfModule>

Only add includeSubDomains once every subdomain works over HTTPS; our HSTS guide explains why. If a security plugin or your application already sends these headers, remove one source so browsers don't receive duplicates. Check the result with our security headers checker, or build a block with the security headers generator. If you also run Nginx, the equivalent config and its own gotchas are in our Nginx security headers guide.

Blocking sensitive files and directory listings

Exposed .env files, Git folders and database backups are a common route into small business sites.

# Return 404 for dotfiles and dot-folders (.env, .git, .htpasswd), except .well-known
RedirectMatch 404 /\.(?!well-known/)

# Deny backups, dumps and logs
<FilesMatch "\.(sql|bak|old|orig|log|swp|zip|tar|gz)$">
    Require all denied
</FilesMatch>

# WordPress: protect the config file
<Files wp-config.php>
    Require all denied
</Files>

# Turn off directory listings
Options -Indexes

Use Require all denied, the Apache 2.4 syntax. The old Order deny,allow / Deny from all lines depend on a compatibility module that some servers don't load. Even with these rules, the safest place for backups is outside public_html altogether. Our guide to exposed .env and .git files explains how to check whether you're already leaking them.

Common .htaccess mistakes that cause 500 errors or loops

Symptom Likely cause Fix
500 on every page Typo, or a directive from a module that isn't loaded Check the error log in cPanel (Metrics > Errors); restore your backup
500 after adding Options Host doesn't allow Options overrides Remove the line or ask the host
"Not allowed here" in log Directive not permitted in .htaccess at your AllowOverride level Use a different method or ask the host
ERR_TOO_MANY_REDIRECTS HTTPS redirect behind a proxy, or rules fighting WordPress/plugin settings Use X-Forwarded-Proto, remove duplicate redirects
Two or three hops to the final URL Separate HTTPS and www rules Combine into the one-hop block above
Certificate won't renew .well-known redirected or blocked Exclude /.well-known/ from redirects and dotfile blocks
Redirect works but Google keeps old URL Redirect without a status (302) Add 301
Rule never matches Leading slash in a .htaccess RewriteRule pattern Remove it

The Apache docs suggest a quick test of whether .htaccess is read at all: add a nonsense line such as TestMe. If you don't get a 500, overrides are disabled. Remove the line straight away.

Be aware that wrapping everything in <IfModule> prevents 500 errors from missing modules, but also hides the fact that the rule isn't doing anything. Always confirm the result with curl or a checker:

curl -sI http://www.example.com/ | grep -iE "^HTTP|^location"

When everything is in place, run a free website audit to check redirects, headers, caching and certificate expiry in one report.

Frequently asked questions

Where is the .htaccess file in cPanel?

In the public_html folder for your main domain, or the document root of an addon domain. In File Manager, enable Show Hidden Files (dotfiles) under Settings to see it. If there isn't one, you can create a plain text file named .htaccess.

How do I redirect HTTP to HTTPS and www to non-www in one redirect?

Use one RewriteRule with two conditions joined by [OR]: %{HTTPS} off and a host starting with www. Redirect to the full canonical URL, such as https://example.com%{REQUEST_URI}, with [R=301,L], and exclude /.well-known/.

Why does my .htaccess file cause a 500 Internal Server Error?

Apache returns 500 when it finds a typo, a directive it isn't allowed to use in .htaccess, or a directive from a module that isn't loaded. The server error log names the line; restoring your backup brings the site back while you fix it.

Does .htaccess work on LiteSpeed and Nginx?

LiteSpeed Web Server reads Apache .htaccess files and aims to match Apache rewrite behaviour, so most rules work unchanged. Nginx ignores .htaccess completely; the rules must be rewritten into the Nginx server configuration.

Is Redirect 301 or RewriteRule better for page moves?

Redirect 301 is simpler and fine for one-to-one moves. Use RewriteRule when you need conditions, and avoid mixing the two in one file where you can, so the order is predictable.

Sources

#htaccess #apache #redirects #cpanel #litespeed #caching

Written by the MakeItFlow team

We build the website audit these guides are based on. Every fact is checked against the primary sources listed above and this guide was last reviewed on 25 October 2026 . How we research and check

Share LinkedIn X Facebook WhatsApp

Check your own site in 30 seconds

Run a free audit covering speed, SEO, DNS, email authentication and security headers — and get a prioritised fix list.

Weekly newsletter

One practical website fix, every week

New guides on speed, SEO, email deliverability and security — straight to your inbox. No spam, unsubscribe in one click.

Keep reading