.htaccess is an Apache configuration file that applies rules to the folder it sits in and to every subfolder. On shared hosting it is the only way to control the server without access to the main config — but any syntax mistake takes the whole site down with a 500 error, so the file must be edited carefully and with a backup on hand.
Forcing HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
The condition %{HTTPS} off only triggers for requests without TLS, so the rule does not loop on pages that are already secure.
Rewrite rule flags: what they mean
| Flag | Meaning |
|---|---|
| L | Last — stop processing further rules after this one |
| R=301 | Permanent redirect (302 with no number is temporary) |
| NC | No Case — the condition ignores letter case |
| QSA | Query String Append — keep GET parameters during the redirect |
Without QSA, a link like example.com/page?utm_source=ads loses the utm_source parameter after the redirect — this breaks ad campaign analytics, so the flag is mandatory for rules that carry parameters.
Redirecting www to non-www
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]
The reverse case (adding www) needs a mirrored rule: the condition catches hosts without www., and the replacement adds it at the front.
Common error: 500 Internal Server Error
After editing .htaccess the site suddenly shows a 500 error. The domain's error_log holds the reason:
[Wed Sep 24 10:12:03 2026] [core:alert] [pid 8842] /home/user/public_html/.htaccess:
Invalid command 'RewriteEngin', perhaps misspelled or defined by a module
not included in the server configuration
This is a typo in the directive name (RewriteEngin instead of RewriteEngine) — Apache cannot recognize it and stops processing the whole file. The second common cause of the same error is that the mod_rewrite module is not loaded on the server at all; then the message reads "not included in the server configuration" with no typo in the directive text.
Browser caching
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
</IfModule>
| File type | Recommended lifetime | Why |
|---|---|---|
| Images | 1 year | Rarely change, versioned through the file name |
| CSS / JS | 1 month | Updated more often, risk of serving a stale version |
| HTML | No cache or minutes | Page content must refresh immediately |
| Fonts | 1 year | Almost never change |
GZIP compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/css
AddOutputFilterByType DEFLATE application/javascript application/json
</IfModule>
GZIP via mod_deflate shrinks text responses by 60–80%, but it does not compress already-compressed formats — adding image/jpeg or video/mp4 to the list is pointless, the server just burns CPU with no size gain.
Protecting a directory
# Blocking access to a folder (Apache 2.2)
<Files *>
Order Deny,Allow
Deny from all
</Files>
# Protecting wp-config.php
<Files wp-config.php>
Order Allow,Deny
Deny from all
</Files>
# The same block for Apache 2.4+
<Files wp-config.php>
Require all denied
</Files>
The Order/Deny directives belong to mod_access_compat from Apache 2.2 — on newer servers running Apache 2.4 the Require all denied syntax works, and it is better to use it if the hosting supports it.
When rules in .htaccess will not work
- The main server config has
AllowOverride Nonefor the directory — then Apache does not read .htaccess at all, and no edits will have any effect - The
mod_rewriteormod_expiresmodule is disabled by the administrator — ask hosting support to confirm - The site runs on Nginx, not Apache — Nginx never reads .htaccess at all, the rules need to move into the server{} config
- The provider runs LiteSpeed without the Apache-directive compatibility module — some rare rules (for example, mod_security through .htaccess) are not supported there
Verifying the result
curl -I http://example.com/
# Expected: HTTP/1.1 301 Moved Permanently
# Location: https://example.com/
The Location header must point to the final HTTPS address without www (or with www, depending on the chosen canonical form), and the status code must be exactly 301, not 302, otherwise search engines will not pass the page's ranking weight to the new address. More on SSL types and HTTPS setup is in the article on redirecting HTTP to HTTPS, and on blocking other sites from linking to your images in Hotlink Protection.