The .htaccess file lets you implement a basic level of website protection without access to Apache's main configuration. Blocking malicious bots, protecting against hotlinking, restricting access to sensitive files—these and other measures can be implemented on shared hosting. This article brings together practical rules and explains them. To generate ready-to-use configurations, use the reChecker .htaccess Generator. You can check security headers with Security Headers.
Blocking access to sensitive files
Hiding configuration and service files is the first step toward reducing the attack surface.
Denying access to .htaccess and configuration files
<FilesMatch "^\.">
Require all denied
</FilesMatch>Blocks all files whose names begin with a dot: .htaccess, .htpasswd, .env.
Protecting specific file extensions
<FilesMatch "\.(env|ini|log|sql|bak|config|yml|yaml)$">
Require all denied
</FilesMatch>Protecting directories
# Запрет листинга директорий
Options -Indexes
# Запрет следования симлинкам за пределы DocumentRoot
Options -FollowSymLinksBlocking access to directories
<DirectoryMatch "^/.*/\.(git|svn|hg)">
Require all denied
</DirectoryMatch>Blocking malicious bots and scanners
Aggressive crawlers, vulnerability scanners, and spam bots create server load and may be used for reconnaissance. Blocking by User-Agent is a simple way to filter out some traffic.
Basic list of blocked User-Agents
RewriteEngine On
RewriteCond %{HTTP_USER_AGENT} (AhrefsBot|SemrushBot|MJ12bot|DotBot|BLEXBot) [NC]
RewriteRule .* - [F,L]The [F] flag returns 403 Forbidden.
Extended list
RewriteCond %{HTTP_USER_AGENT} (360Spider|Acunetix|sqlmap|nikto|masscan|nmap|zgrab) [NC]
RewriteRule .* - [F,L]
RewriteCond %{HTTP_USER_AGENT} (Scrapy|Python-urllib|curl|wget|libwww) [NC]
RewriteCond %{REQUEST_URI} !^/api/crawl
RewriteRule .* - [F,L]Warning: blocking curl and wget may break legitimate integrations (webhooks, API clients). Use with caution.
Blocking empty and suspicious User-Agents
RewriteCond %{HTTP_USER_AGENT} ^$ [OR]
RewriteCond %{HTTP_USER_AGENT} ^(-|$|Wget|curl|libwww) [NC]
RewriteRule .* - [F,L]Blocking by Referer (comment spam)
RewriteCond %{HTTP_REFERER} (viagra|casino|porn|pharmacy) [NC]
RewriteRule .* - [F,L]Hotlink protection
Hotlinking is the use of your images or media files on other websites. The traffic goes through your server, using up bandwidth.
Blocking hotlinks while allowing your own domain
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?example\.com [NC]
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?google\.(com|ru) [NC]
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?yandex\.(ru|com) [NC]
RewriteRule \.(jpg|jpeg|png|gif|webp|svg|ico|pdf)$ - [F,L]An empty Referer (!^$) is allowed; otherwise, direct link visits will be blocked.
Replacing with a placeholder instead of 403
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?example\.com [NC]
RewriteRule \.(jpg|jpeg|png|gif)$ /images/hotlink-blocked.png [L]Restricting HTTP methods
Allow only the methods you need. GET and HEAD are sufficient for a static website.
<LimitExcept GET POST HEAD>
Require all denied
</LimitExcept>Or with mod_rewrite:
RewriteCond %{REQUEST_METHOD} !^(GET|POST|HEAD)$
RewriteRule .* - [F,L]Protection against injection and path traversal
Blocking suspicious requests
RewriteCond %{QUERY_STRING} (<|%3C).*script.*(>|%3E) [NC,OR]
RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2}) [OR]
RewriteCond %{QUERY_STRING} (\.\./|\.\.\\|/etc/passwd|union.*select|insert.*into)
RewriteRule .* - [F,L]Blocking path traversal
RewriteCond %{REQUEST_URI} (\.\./|\.\.\\|%2e%2e%2f|%2e%2e/) [NC]
RewriteRule .* - [F,L]Rate limiting (restricting request frequency)
Full rate limiting is usually unavailable on shared hosting. It can be partially implemented with mod_evasive or mod_qos, but these require access to the server configuration. In .htaccess, you can restrict access to individual scripts.
Restricting form access by IP
Use an external service or application. In .htaccess, the only option is blocking an IP after abuse has been identified:
# Блокировка конкретного IP
<RequireAll>
Require all granted
Require not ip 192.168.1.100
Require not ip 10.0.0.0/24
</RequireAll>Protecting wp-login.php (WordPress)
<Files wp-login.php>
Order Deny,Allow
Deny from all
Allow from 192.168.1.0/24
Allow from 203.0.113.50
</Files>The Order/Deny/Allow syntax is obsolete in Apache 2.4, but is still encountered. The modern version is:
<Files wp-login.php>
<RequireAll>
Require not ip 0.0.0.0/0
Require ip 192.168.1.0/24
Require ip 203.0.113.50
</RequireAll>
</Files>Protection against DDoS and brute-force attacks
Blocking by country (requires mod_geoip)
If GeoIP is available:
# Разрешить только РФ и СНГ
SetEnvIf GEOIP_COUNTRY_CODE RU AllowCountry
SetEnvIf GEOIP_COUNTRY_CODE BY AllowCountry
SetEnvIf GEOIP_COUNTRY_CODE KZ AllowCountry
Deny from all
Allow from env=AllowCountryLimiting request size
In .htaccess, you cannot set LimitRequestBody for the entire website—only in the main configuration. However, you can limit uploads in PHP:
// В php.ini или .user.ini
upload_max_filesize = 2M
post_max_size = 2MSecure HTTP headers
Some headers can be set with .htaccess:
<IfModule mod_headers.c>
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "SAMEORIGIN"
Header set X-XSS-Protection "1; mode=block"
Header set Referrer-Policy "strict-origin-when-cross-origin"
Header set Permissions-Policy "geolocation=(), microphone=(), camera=()"
</IfModule>You can check the current headers with Security Headers on reChecker. For more details, see the security headers guide.
Clickjacking protection
Header always set X-Frame-Options "SAMEORIGIN"DENY prohibits embedding in any iframe. SAMEORIGIN allows embedding only from the same domain.
MIME-sniffing protection
Header set X-Content-Type-Options "nosniff"Prevents the browser from guessing the content type and executing, for example, JS from a file with the type text/plain.
Content Security Policy (basic)
CSP is best set in the application, but a basic version is possible in .htaccess:
Header set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.example.com; style-src 'self' 'unsafe-inline'"An overly strict CSP can break the website. Test it gradually.
Protecting administrative paths
Blocking access to /admin by IP
<LocationMatch "^/admin">
Require ip 192.168.1.0/24
Require ip 203.0.113.50
</LocationMatch>Hiding the Apache version
In .htaccess, you cannot remove Server—only in httpd.conf:
ServerTokens Prod
ServerSignature OffAdditional protection against comment spam
In addition to blocking by Referer, you can restrict access to the comment script:
<Files "comment.php">
# Разрешить только POST
<LimitExcept POST>
Require all denied
</LimitExcept>
</Files>Logging suspicious activity
Creating a custom log (if permitted):
SetEnvIf Request_URI "wp-login\.php" suspicious_request
CustomLog /var/log/apache2/suspicious.log combined env=suspicious_requestOn shared hosting, the log path may be different or unavailable.
.htaccess security checklist
| Measure | Directive / Action |
|---|---|
| Hide .htaccess, .env | <FilesMatch "^\."> |
| Disable directory listing | Options -Indexes |
| Block bots | RewriteCond %{HTTP_USER_AGENT} |
| Hotlink protection | RewriteCond %{HTTP_REFERER} |
| Restrict methods | <LimitExcept GET POST HEAD> |
| XSS/injection in query | RewriteCond %{QUERY_STRING} |
| Path traversal | RewriteCond %{REQUEST_URI} |
| Security headers | Header set X-Frame-Options and others |
| /admin by IP | <LocationMatch> + Require ip |
Limitations of .htaccess
.htaccess does not replace a full security configuration:
- No access to
mod_evasive,mod_security—an advanced WAF is available only in the server configuration. - Rate limiting is limited.
- Some headers (such as HSTS) are best set at the server level.
- User-Agent blocking is easily bypassed by spoofing the header.
Use .htaccess as a first layer of protection, especially on shared hosting. For critical projects, use a dedicated server with a full WAF and monitoring.
Blocking by Referer (empty and suspicious)
An empty Referer may indicate a direct link or a bot. Allow only known sources:
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 .* - [F,L]Caution: blocking an empty Referer breaks bookmarks, manually entered URLs, and some mobile apps.
Protection against directory traversal in URLs
RewriteCond %{REQUEST_URI} (\.\./|\.\.\\|%2e%2e%2f|%2e%2e/) [NC]
RewriteRule .* - [F,L]Restricting access to XML-RPC (WordPress)
By default, WordPress exposes xmlrpc.php for pingbacks and remote publishing. It is often used for brute-force attacks:
<Files xmlrpc.php>
Require all denied
</Files>Or with mod_rewrite:
RewriteRule ^xmlrpc\.php$ - [F,L]Protection against request forgery (CSRF)
CSRF is handled at the application level (tokens, SameSite cookies). In .htaccess, you can only restrict methods for forms:
<LocationMatch "/submit|/comment|/login">
<LimitExcept POST>
Require all denied
</LimitExcept>
</LocationMatch>Logging blocked requests
SetEnvIf Request_URI "wp-login\.php" blocked_request
SetEnvIf Remote_Addr "192.168.1.100" blocked_request
CustomLog /var/log/apache2/blocked.log combined env=blocked_requestOn shared hosting, the log path may differ or be unavailable.
Testing after implementation
After adding the rules:
- Make sure the website loads and static assets are served.
- Check login and data submission forms.
- Test APIs and webhooks if they use curl/wget.
- Check the headers with Security Headers.
Backing up .htaccess
Create a copy before making changes:
cp .htaccess .htaccess.backup.$(date +%Y%m%d)If an error occurs, 500 restore the file and check the syntax. An invalid .htaccess can block access to the website.
For more on configuring Apache, see the complete .htaccess guide.