The .htaccess file is one of the most powerful—and potentially dangerous—tools in a web developer’s toolkit. One line can redirect all traffic to HTTPS, while another can bring down a site with a 500 error. In this guide, we’ll look at the key use cases for .htaccess: redirects, rewrite rules, security, caching, and performance. Each section includes ready-to-use code examples that you can adapt to your project.
What .htaccess is and how it works
.htaccess (Hypertext Access) is an Apache web server configuration file that controls server behavior at the directory level. Unlike httpd.conf, .htaccess does not require a server restart—changes take effect immediately.
How it works
When an HTTP request arrives, Apache traverses the directory chain from the root to the requested file, checking for .htaccess at each level. Directives are applied in sequence, and a file in a deeper directory overrides the settings in a parent directory. For example, a request for /blog/2026/article.html will check /.htaccess, /blog/.htaccess, and /blog/2026/.htaccess.
Basic file structure:
RewriteEngine On
AddDefaultCharset UTF-8
Options -IndexesEnabling .htaccess support
For Apache to process .htaccess, the server’s main configuration must include the AllowOverride directive:
<Directory /var/www/html>
AllowOverride All
Require all granted
</Directory>The value All allows all types of directives. To improve security, you can limit the set: AuthConfig (authorization), FileInfo (redirects, rewrite), Indexes (directory listings), Limit (access control), and Options (directory options).
Each .htaccess adds overhead: Apache reads and parses the file on every request. For high-traffic projects, it is recommended to move directives to httpd.conf. But on shared hosting, .htaccess remains the only configuration option. You can quickly generate a valid file with the reChecker .htaccess generator.
Redirects
Redirects are the most common use case for .htaccess. Correct redirect configuration is critical for SEO: it preserves page link equity and provides users with a seamless transition.
301 and 302 redirects
# 301 — постоянный редирект (передаёт до 99% ссылочного веса)
Redirect 301 /old-page.html https://example.com/new-page.html
# То же через mod_rewrite
RewriteEngine On
RewriteRule ^old-page\.html$ /new-page.html [R=301,L]
# 302 — временный редирект (вес не передаётся)
Redirect 302 /promo /temporary-promoConsolidating www and non-www
Two versions of a site create duplicate content. Choose a canonical version:
# С www на без www
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
RewriteRule ^(.*)$ https://example.com/$1 [R=301,L]
# С без www на www
RewriteEngine On
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^(.*)$ https://www.example.com/$1 [R=301,L]HTTP → HTTPS redirect
After installing an SSL certificate, you need to redirect all HTTP traffic. You can check that SSL is configured correctly with the SSL Checker.
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]For servers behind a reverse proxy (CloudFlare, AWS ALB):
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]Combined redirect: HTTP + www → HTTPS without www
In practice, you often need to combine multiple redirects. The right approach is a single 301 redirect instead of a chain:
RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\. [NC]
RewriteCond %{HTTP_HOST} ^(?:www\.)?(.+)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]Trailing slash
Consistent URLs are an important SEO factor:
# Добавить trailing slash
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !(.*)/$
RewriteRule ^(.*)$ /$1/ [R=301,L]
# Убрать trailing slash
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^(.+)/$ /$1 [R=301,L]Bulk redirect during a migration
RewriteEngine On
# Редирект раздела
RewriteRule ^blog/category/(.*)$ /articles/topic/$1 [R=301,L]
# Редирект с GET-параметрами
RewriteCond %{QUERY_STRING} ^id=(\d+)$
RewriteRule ^product\.php$ /catalog/product-%1.html? [R=301,L]
# Редирект на другой домен
RewriteRule ^(.*)$ https://newdomain.com/$1 [R=301,L]You can check that your redirects are configured correctly with the redirect checker. It will show the full redirect chain and identify loops.
URL rewriting
URL rewriting creates human-readable addresses instead of technical GET parameters. This can benefit SEO and make URLs more convenient for users.
RewriteRule syntax
RewriteRule шаблон подстановка [флаги]The pattern is a regular expression for the URI (without the leading /). The substitution is the new URI. The main flags are:
| Flag | Description |
|---|---|
[L] | Last rule—stops processing |
[R=301] | External redirect with code 301 |
[NC] | Case-insensitive comparison |
[QSA] | Append the original query string to the new URL |
[F] | Return 403 Forbidden |
[G] | Return 410 Gone |
[END] | Completely stop processing (Apache 2.4+) |
RewriteCond: conditions
RewriteCond %{HTTPS} on # Проверка HTTPS
RewriteCond %{REQUEST_METHOD} POST # Метод запроса
RewriteCond %{HTTP_USER_AGENT} "Googlebot" [NC] # User-Agent
RewriteCond %{REQUEST_FILENAME} !-f # Файл не существует
RewriteCond %{REQUEST_FILENAME} !-d # Директория не существует
RewriteCond %{QUERY_STRING} ^lang=([a-z]{2})$ # Строка запросаPractical examples
Clean URLs for a blog:
RewriteEngine On
RewriteRule ^blog/([a-z0-9\-]+)$ blog.php?slug=$1 [L,QSA]
RewriteRule ^blog/(\d{4})/(\d{2})/([a-z0-9\-]+)$ blog.php?year=$1&month=$2&slug=$3 [L,QSA]Clean URLs for a catalog:
RewriteEngine On
RewriteRule ^catalog/([a-z\-]+)/([a-z\-]+)$ catalog.php?category=$1&sub=$2 [L,QSA]
RewriteRule ^product/([a-z0-9\-]+)$ product.php?slug=$1 [L,QSA]Multilingual website:
RewriteEngine On
RewriteRule ^(en|ru|de|fr)/(.+)$ page.php?lang=$1&page=$2 [L,QSA]Convert URLs to lowercase (Apache 2.4+):
RewriteMap lowercase int:tolower
RewriteCond %{REQUEST_URI} [A-Z]
RewriteRule ^(.*)$ ${lowercase:$1} [R=301,L]Security
.htaccess is the first line of defense, operating before a request reaches the application.
Password-protecting directories
AuthType Basic
AuthName "Restricted Area"
AuthUserFile /home/user/.htpasswd
Require valid-userCreate .htpasswd: htpasswd -c /home/user/.htpasswd admin
Protecting a specific file:
<Files "admin.php">
AuthType Basic
AuthName "Admin Access"
AuthUserFile /home/user/.htpasswd
Require user admin
</Files>Blocking IP addresses
# Apache 2.4 — заблокировать IP
<RequireAll>
Require all granted
Require not ip 192.168.1.100
Require not ip 10.0.0.0/8
</RequireAll>
# Доступ только с определённых IP
<RequireAny>
Require ip 203.0.113.50
Require ip 198.51.100.0/24
</RequireAny>Hotlink protection
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?example\.com [NC]
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?google\. [NC]
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?yandex\. [NC]
RewriteRule \.(jpg|jpeg|png|gif|webp|svg)$ - [F,NC]Security headers
Use the security headers checker to audit your current headers.
<IfModule mod_headers.c>
Header set X-Content-Type-Options "nosniff"
Header set X-XSS-Protection "1; mode=block"
Header set X-Frame-Options "SAMEORIGIN"
Header set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header set Referrer-Policy "strict-origin-when-cross-origin"
Header set Permissions-Policy "camera=(), microphone=(), geolocation=(self)"
Header set Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com"
</IfModule>Blocking suspicious bots and restricting methods
RewriteEngine On
RewriteCond %{HTTP_USER_AGENT} (SemrushBot|AhrefsBot|MJ12bot|DotBot) [NC]
RewriteRule .* - [F,L]
RewriteCond %{HTTP_USER_AGENT} ^$
RewriteRule .* - [F,L]<LimitExcept GET POST HEAD>
Require all denied
</LimitExcept>Caching
Caching reduces server load and speeds up page loading for returning visitors.
Expires (mod_expires)
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 1 month"
ExpiresByType text/html "access plus 0 seconds"
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "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"
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType font/woff "access plus 1 year"
ExpiresByType application/json "access plus 0 seconds"
</IfModule>Cache-Control (mod_headers)
Cache-Control provides more flexible control:
<IfModule mod_headers.c>
<FilesMatch "\.(html|htm)$">
Header set Cache-Control "no-cache, no-store, must-revalidate"
</FilesMatch>
<FilesMatch "\.(css|js)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
<FilesMatch "\.(jpg|jpeg|png|gif|webp|svg|ico)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
<FilesMatch "\.(woff2|woff|ttf|eot)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
</IfModule>ETag
On clustered servers, ETag can cause problems, so it is better to disable it:
Header unset ETag
FileETag NoneCompression
Compression reduces the size of transferred data by 60-80%.
mod_deflate
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/css text/javascript text/xml text/plain
AddOutputFilterByType DEFLATE application/javascript application/json
AddOutputFilterByType DEFLATE application/xml application/rss+xml application/xhtml+xml
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE font/woff font/woff2 application/vnd.ms-fontobject
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|webp|zip|gz|bz2|rar|mp[34])$ no-gzip
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
Header append Vary Accept-Encoding
</IfModule>Checking compression
curl -H "Accept-Encoding: gzip" -I https://example.com/style.cssThe response should include the Content-Encoding: gzip header.
Custom error pages
Apache’s default error pages look unprofessional and reveal information about the server.
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/503.htmlDo not use ErrorDocument 404 https://example.com/not-found: an external redirect will return code 302, not 404, which will harm SEO.
Maintenance mode
RewriteEngine On
RewriteCond %{REMOTE_ADDR} !^203\.0\.113\.50$
RewriteCond %{REQUEST_URI} !\.(css|js|png|jpg|gif|ico|svg|woff2?)$ [NC]
RewriteCond %{REQUEST_URI} !^/maintenance\.html$
RewriteRule ^(.*)$ /maintenance.html [R=503,L]
Header set Retry-After "3600"The developer’s IP is allowed through, static resources are loaded, and search engines receive the Retry-After header and understand that the situation is temporary.
Protecting files
Leaked configuration files are a common cause of security breaches.
Hidden files and environment files
<FilesMatch "^\.">
Require all denied
</FilesMatch>
<FilesMatch "(\.env|\.env\..*|\.htpasswd)$">
Require all denied
</FilesMatch>CMS protection
WordPress — block access to wp-config.php and xmlrpc.php (frequent targets of brute-force attacks):
<Files wp-config.php>
Require all denied
</Files>
<Files xmlrpc.php>
Require all denied
</Files>Bitrix:
<Files ".settings.php">
Require all denied
</Files>
<Files "dbconn.php">
Require all denied
</Files>Protecting .git, backups, and include files
RedirectMatch 404 /\.git
<FilesMatch "\.(sql|sql\.gz|bak|backup|old|orig|save|swp|log|ini|conf|cfg)$">
Require all denied
</FilesMatch>
<FilesMatch "^(config|database|functions|helpers|bootstrap)\.php$">
Require all denied
</FilesMatch>The reChecker .htaccess generator can combine all your protection rules into a single configuration file and create a ready-to-use configuration for your stack.
Performance optimization
Keep-Alive and CORS
<IfModule mod_headers.c>
Header set Connection keep-alive
<FilesMatch "\.(woff2?|ttf|eot|otf)$">
Header set Access-Control-Allow-Origin "*"
</FilesMatch>
</IfModule>MIME types for modern formats
<IfModule mod_mime.c>
AddType image/webp .webp
AddType image/avif .avif
AddType font/woff2 .woff2
AddType font/woff .woff
AddType application/manifest+json .webmanifest
AddType video/mp4 .mp4
AddType video/webm .webm
AddType text/javascript .mjs
</IfModule>Disabling PHP for static files
<Directory "/var/www/html/uploads">
RemoveHandler .php
<FilesMatch "\.(php|phtml|php[3-7]|phps|phar|cgi|pl|py|sh)$">
Require all denied
</FilesMatch>
</Directory>Common errors and debugging
500 Internal Server Error
This is the most common problem. The main causes are:
# 1. Синтаксическая ошибка
Rewrite Engine On # НЕПРАВИЛЬНО — лишний пробел
RewriteEngine On # ПРАВИЛЬНО
# 2. Модуль не установлен — используйте обёртку
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^old$ /new [R=301,L]
</IfModule>
# 3. Относительный путь к .htpasswd
AuthUserFile .htpasswd # НЕПРАВИЛЬНО
AuthUserFile /home/username/.htpasswd # ПРАВИЛЬНОInfinite redirect loop
# Проблема — правило перенаправляет URL на себя
RewriteRule ^(.*)$ /index.php [L]
# Решение — исключаем существующие файлы
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /index.php [L]Rules are not applied
Common causes include AllowOverride being disabled, RewriteEngine On being missing (it is not inherited from parent files), and rules being in the wrong order:
RewriteEngine On
# НЕПРАВИЛЬНО — общее правило перехватывает всё
RewriteRule ^(.*)$ /index.php [L]
RewriteRule ^api/(.*)$ /api/handler.php?route=$1 [L]
# ПРАВИЛЬНО — частные правила первыми
RewriteRule ^api/(.*)$ /api/handler.php?route=$1 [L]
RewriteRule ^(.*)$ /index.php [L]Missing RewriteBase
If the site is in a subdirectory (example.com/myapp/), rules may not work correctly without RewriteBase:
RewriteEngine On
RewriteBase /myapp/
RewriteRule ^page/(\d+)$ index.php?page=$1 [L]Diagnostics
apachectl configtest # Проверка синтаксиса
tail -f /var/log/apache2/error.log # Лог ошибокFor detailed debugging of rewrite rules (in httpd.conf, not in .htaccess):
LogLevel alert rewrite:trace3Conclusion
.htaccess remains an indispensable tool for configuring Apache, especially on shared hosting. Key recommendations:
- Always back up the current
.htaccessbefore making changes. One typo can make the site return 500. - Wrap directives in
<IfModule>to prevent errors when modules are unavailable. - Test changes in a staging environment before deploying to production.
- Minimize the use of
.htaccesson high-traffic projects—move rules to the virtual host configuration. - Use automation. The reChecker .htaccess generator will assemble a configuration with correct syntax and help prevent common errors.
A well-configured .htaccess provides security, performance, and correct SEO behavior. Take the time to configure it once, and your server will work more reliably, quickly, and securely.