What is .htaccess and Why Does It Matter?
The .htaccess file (hypertext access) is a powerful configuration file used by the Apache web server. It allows you to control server behavior on a per-directory basis without modifying the main Apache configuration files or restarting the server. This makes it the go-to tool for shared hosting environments where users don't have access to the main server configuration.
From URL redirects and pretty permalinks to security headers and caching rules, .htaccess handles an enormous range of web server functionality. Nearly every PHP application you've used, whether WordPress, Laravel, Drupal, or Joomla, includes a .htaccess file as part of its standard setup.
.htaccess files. Each file's directives are applied in order. This per-request directory scanning is what makes .htaccess both powerful (instant changes without restart) and potentially slow (filesystem reads on every request).
URL Redirects and Rewriting
URL rewriting with mod_rewrite is the most common use of .htaccess. It lets you transform ugly URLs into clean, SEO-friendly ones, redirect old pages, and route all requests through a front controller.
Enable mod_rewrite
RewriteEngine On
RewriteBase /
HTTP to HTTPS Redirect
The most common redirect in modern web hosting. Force all traffic through HTTPS for security and SEO:
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
WWW to Non-WWW (and Vice Versa)
Remove WWW
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]
Force WWW
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*)$ https://www.%{HTTP_HOST}/$1 [R=301,L]
Common Redirect Types
| Code | Type | Use Case | SEO Impact |
|---|---|---|---|
| 301 | Permanent Redirect | URL changed forever, page moved | Passes link equity to new URL |
| 302 | Temporary Redirect | Maintenance, A/B testing | Does not pass link equity |
| 307 | Temporary (strict) | Preserves HTTP method (POST stays POST) | Does not pass link equity |
| 308 | Permanent (strict) | Like 301 but preserves HTTP method | Passes link equity |
Redirect Specific Pages
Redirect 301 /old-page.html /new-page.html
# Redirect an entire directory
RedirectMatch 301 ^/blog/(.*)$ /articles/$1
# Redirect old domain to new domain
RewriteEngine On
RewriteCond %{HTTP_HOST} ^(www\.)?oldsite\.com$ [NC]
RewriteRule ^(.*)$ https://newsite.com/$1 [R=301,L]
# Remove trailing slashes
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [R=301,L]
WordPress Permalinks
WordPress uses .htaccess to create clean URLs. The standard WordPress rewrite rules route all non-file, non-directory requests through index.php:
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]
# END WordPress
Security Headers
Security headers instruct browsers to enforce various security policies, protecting your site and visitors from common attacks. Adding them via .htaccess is straightforward:
<IfModule mod_headers.c>
# Prevent clickjacking
Header always set X-Frame-Options "SAMEORIGIN"
# Prevent MIME-type sniffing
Header always set X-Content-Type-Options "nosniff"
# Enable XSS protection
Header always set X-XSS-Protection "1; mode=block"
# Referrer policy
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# HTTP Strict Transport Security (HSTS)
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
# Content Security Policy
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;"
# Permissions policy
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
</IfModule>
Content-Security-Policy-Report-Only to test without blocking, then switch to enforcing mode once you've confirmed nothing breaks.
Hotlink Protection
Hotlinking is when other websites embed your images directly, stealing your bandwidth. Prevent this with a few rewrite rules:
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?yourdomain\.com [NC]
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?google\.com [NC]
RewriteRule \.(jpg|jpeg|png|gif|webp|svg)$ - [F,NC,L]
# F = 403 Forbidden for hotlinked images
# Allow empty referer (direct access) and Google (image search)
Directory Listing and Access Control
Disable Directory Listing
Options -Indexes
# Block access to sensitive files
<FilesMatch "\.(env|log|ini|sql|bak|config)$">
Require all denied
</FilesMatch>
# Protect .htaccess itself
<Files .htaccess>
Require all denied
</Files>
# Block access to hidden files (dotfiles)
<FilesMatch "^\.">
Require all denied
</FilesMatch>
Password Protection
Protect a directory or admin area with HTTP Basic Authentication:
$ htpasswd -c /path/outside/webroot/.htpasswd username
# .htaccess in the directory to protect
AuthType Basic
AuthName "Restricted Area"
AuthUserFile /path/outside/webroot/.htpasswd
Require valid-user
.htpasswd file OUTSIDE the web root. If stored inside the document root, the password hashes could be downloaded by anyone who guesses the file path.
IP-Based Access Control
<RequireAll>
Require ip 192.168.1.0/24
Require ip 10.0.0.0/8
</RequireAll>
# Block specific IPs
<RequireAll>
Require all granted
Require not ip 1.2.3.4
Require not ip 5.6.7.0/24
</RequireAll>
Custom Error Pages
ErrorDocument 400 /errors/400.html
ErrorDocument 401 /errors/401.html
ErrorDocument 403 /errors/403.html
ErrorDocument 404 /errors/404.html
ErrorDocument 500 /errors/500.html
ErrorDocument 503 /errors/maintenance.html
Browser Caching with mod_expires
Browser caching tells visitors' browsers to store static files locally. On subsequent visits, the browser loads these files from cache instead of downloading them again, dramatically improving page load times.
ExpiresActive On
# Images - cache for 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/svg+xml "access plus 1 year"
ExpiresByType image/x-icon "access plus 1 year"
# CSS and JavaScript - cache for 1 month
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
# Fonts - cache for 1 year
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType font/woff "access plus 1 year"
# HTML - cache for 10 minutes
ExpiresByType text/html "access plus 600 seconds"
</IfModule>
Gzip Compression with mod_deflate
Compression reduces the size of files sent to the browser, often by 60-80% for text-based resources. This directly speeds up page loads, especially on slower connections.
# Compress text-based content
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE font/woff
AddOutputFilterByType DEFLATE font/woff2
# Don't compress already-compressed files
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|webp)$ no-gzip
</IfModule>
PHP Settings via .htaccess
You can override certain php.ini settings in .htaccess, which is particularly useful on shared hosting where you don't have access to the PHP configuration:
php_value upload_max_filesize 64M
php_value post_max_size 64M
php_value max_execution_time 300
php_value max_input_time 300
php_value memory_limit 256M
php_value max_input_vars 3000
# Error handling
php_flag display_errors Off
php_flag log_errors On
php_value error_log /home/user/logs/php_error.log
php_value and php_flag directives only work when PHP runs as an Apache module (mod_php). If your server uses PHP-FPM (which most modern setups do), these directives won't work in .htaccess. Instead, use a .user.ini file or configure settings in the PHP-FPM pool configuration.
Performance Impact of .htaccess
While .htaccess is incredibly convenient, it comes with a performance cost. Apache must search for and parse .htaccess files on every request, for every directory level from the document root to the requested file.
.htaccess (Per-Directory)
- Slower - parsed on every request
- No restart needed - instant changes
- User accessible - no root needed
- Best for shared hosting
VirtualHost Config (Main Config)
- Faster - parsed once at startup
- Requires restart to apply changes
- Root access needed
- Best for dedicated/VPS servers
.htaccess rules into the VirtualHost configuration and set AllowOverride None. This eliminates the filesystem overhead of .htaccess parsing on every request. On high-traffic sites, this alone can improve response times by 10-20%.
Complete .htaccess Template
Here's a comprehensive .htaccess template combining all the best practices covered above:
Options -Indexes
ServerSignature Off
<FilesMatch "\.(env|log|ini|sql|bak|config|yml|yaml)$">
Require all denied
</FilesMatch>
# === SECURITY HEADERS ===
<IfModule mod_headers.c>
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Strict-Transport-Security "max-age=31536000"
</IfModule>
# === REDIRECTS ===
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# === CACHING ===
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
</IfModule>
# === COMPRESSION ===
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/css
AddOutputFilterByType DEFLATE application/javascript
</IfModule>
.htaccess with Panelica
Panelica supports both Apache and Nginx as web servers. For domains configured to use Apache, .htaccess files work seamlessly with the panel's domain configuration. You can manage your Apache domains through the panel GUI while still customizing behavior with .htaccess in your document root.
Debugging .htaccess Issues
When .htaccess rules don't work as expected, use these debugging techniques:
AllowOverride None is set in your Apache config, .htaccess files are completely ignored. You need at least AllowOverride All for mod_rewrite.apache2ctl -M | grep rewrite. If it's not listed, enable it with a2enmod rewrite.LogLevel alert rewrite:trace6 to your VirtualHost config temporarily. Check the error log for detailed rewrite processing steps..htaccess is being read, then add complexity incrementally.Conclusion
The .htaccess file remains one of the most versatile tools in a web developer's toolkit. From simple redirects to complex URL rewriting, from security headers to browser caching, it handles a remarkably wide range of server configuration tasks, all without requiring server restarts or root access.
While Nginx is increasingly popular as a primary web server (and doesn't support .htaccess), Apache with .htaccess support remains essential for compatibility with thousands of PHP applications that ship with .htaccess files. Understanding how to write and optimize these rules is a fundamental skill for anyone managing web servers or PHP applications.
Start with the template provided above, customize it for your specific needs, and remember: if you have root access, consider moving your most critical rules into the VirtualHost configuration for better performance. And if you need both Apache and Nginx, hosting panels like Panelica let you choose the right web server per domain.