Why Block IPs with .htaccess?
Blocking access by IP address is one of the most basic yet effective security measures for web servers. Common scenarios include blocking attackers conducting brute force attacks, restricting access to staging or admin areas, blocking scrapers and abusive bots, and responding to ongoing attacks before a WAF rule can be deployed.
Apache's .htaccess file provides a convenient way to implement IP-based access control without modifying the main server configuration or restarting Apache. Changes take effect immediately.
Apache 2.4 Require Syntax
Apache 2.4 replaced the older Allow/Deny/Order directives with the Require directive. If you are running Apache 2.4 or later (which includes all current Ubuntu, Debian, and CentOS distributions), use this syntax:
Block a Single IP Address
<RequireAll>
Require all granted
Require not ip 203.0.113.50
</RequireAll>
This allows all traffic except from 203.0.113.50, which receives a 403 Forbidden response.
Block Multiple IP Addresses
<RequireAll>
Require all granted
Require not ip 203.0.113.50
Require not ip 198.51.100.23
Require not ip 192.0.2.100
</RequireAll>
Block a CIDR Range
To block an entire subnet, use CIDR notation:
<RequireAll>
Require all granted
Require not ip 203.0.113.0/24
Require not ip 198.51.100.0/24
</RequireAll>
This blocks all 256 addresses in each /24 subnet.
Allow Only Specific IPs (Whitelist)
To restrict access to only specific IP addresses (useful for admin panels or staging sites):
<RequireAny>
Require ip 10.0.0.0/8
Require ip 172.16.0.0/12
Require ip 192.168.1.100
</RequireAny>
Everyone else receives a 403 Forbidden response. This is commonly used to protect /wp-admin/, /phpmyadmin/, or other sensitive paths.
Legacy Apache 2.2 Syntax
If you are still running Apache 2.2 (end of life, but still found on older servers), the syntax uses Order, Allow, and Deny:
# Block specific IPs (Apache 2.2)
Order Allow,Deny
Allow from all
Deny from 203.0.113.50
Deny from 198.51.100.0/24
# Allow only specific IPs (Apache 2.2)
Order Deny,Allow
Deny from all
Allow from 192.168.1.100
Allow from 10.0.0.0/8
Upgrade to Apache 2.4 when possible. The 2.2 syntax is deprecated and no longer receives security updates.
Combining with mod_rewrite for Custom Error Pages
The standard 403 Forbidden page is generic. You can use mod_rewrite to serve a custom page or redirect blocked visitors:
RewriteEngine On
# Block specific IPs and redirect to a custom page
RewriteCond %{REMOTE_ADDR} ^203\.0\.113\.50$
RewriteRule ^ /blocked.html [L,R=403]
# Block a range
RewriteCond %{REMOTE_ADDR} ^198\.51\.100\.
RewriteRule ^ - [F]
The [F] flag returns a 403 Forbidden. The first example redirects to a custom blocked.html page with a 403 status code.
Block by User-Agent (Bonus)
You can also combine IP blocking with user-agent blocking to target specific bots:
RewriteEngine On
# Block known bad bots by user-agent
RewriteCond %{HTTP_USER_AGENT} (BadBot|EvilScraper|MaliciousCrawler) [NC]
RewriteRule ^ - [F]
# Block specific IP AND user-agent combination
RewriteCond %{REMOTE_ADDR} ^203\.0\.113\.50$
RewriteCond %{HTTP_USER_AGENT} python-requests [NC]
RewriteRule ^ - [F]
Blocking by Country with GeoIP
If you need to block traffic from entire countries, you can use Apache's GeoIP module with .htaccess. First, install the module:
sudo apt install libapache2-mod-geoip
sudo a2enmod geoip
sudo systemctl restart apache2
Then in .htaccess:
# Set the GeoIP database
GeoIPEnable On
# Block traffic from specific countries
RewriteEngine On
RewriteCond %{ENV:GEOIP_COUNTRY_CODE} ^(CN|RU|KP)$
RewriteRule ^ - [F]
Country-level blocking is a blunt instrument and should be used carefully. Legitimate users behind VPNs or traveling may be affected.
Protecting Specific Directories
You can place .htaccess files in specific directories to restrict access to only those paths:
# /var/www/html/admin/.htaccess
# Only allow office IP and VPN
<RequireAny>
Require ip 203.0.113.10
Require ip 10.8.0.0/24
</RequireAny>
# /var/www/html/api/.htaccess
# Block known abusive IPs from the API
<RequireAll>
Require all granted
Require not ip 198.51.100.0/24
</RequireAll>
.htaccess vs iptables vs WAF
Each method of blocking IPs operates at a different layer:
| Method | Layer | Pros | Cons |
|---|---|---|---|
| .htaccess | Application (Apache) | No root required, immediate effect, per-directory control | Only works with Apache, performance overhead on every request, traffic still reaches the server |
| iptables/nftables | Network (kernel) | Blocks traffic before it reaches the web server, very fast, low overhead | Requires root, applies server-wide, harder to manage per-site |
| WAF | Edge (CDN/proxy) | Blocks traffic before it reaches your server, advanced rules (rate limiting, bot detection), centralized management | Requires external service, may add latency, cost |
For best security, use a layered approach: a WAF at the edge to filter the majority of malicious traffic, iptables on the server for network-level protection, and .htaccess for application-specific access control.
Testing with curl
After adding IP blocks, verify they work correctly:
# Test from the blocked IP (or using a proxy)
curl -I https://example.com
# Expected: HTTP/1.1 403 Forbidden
# Test from an allowed IP
curl -I https://example.com
# Expected: HTTP/1.1 200 OK
# Test a specific path
curl -I https://example.com/admin/
# Expected: 403 if your IP is not whitelisted
# Simulate a different IP using X-Forwarded-For (only works if Apache trusts this header)
curl -H "X-Forwarded-For: 203.0.113.50" -I https://example.com
Note: Be careful with X-Forwarded-For headers. If Apache is configured to use mod_remoteip and trusts the X-Forwarded-For header, attackers can spoof their IP. Only trust this header from known proxies and load balancers.
Managing Large Block Lists
If you need to block hundreds of IPs, managing them in .htaccess becomes unwieldy. Consider these alternatives:
- Include file: Move the block list to a separate file and include it:
Include /etc/apache2/blocklist.conf(requires main config access). - iptables/ipset: Use
ipsetwith iptables for managing large IP sets efficiently at the kernel level. - fail2ban: Automatically block IPs based on log analysis (failed logins, 404 floods, etc.).
- WAF: Use a cloud-based WAF that maintains threat intelligence feeds and automatically blocks known malicious IPs.
Summary
Apache's .htaccess file provides a quick and accessible way to block or restrict access by IP address. Use the Apache 2.4 Require syntax for clean, maintainable rules, combine with mod_rewrite for custom responses, and consider country-level blocking with GeoIP when needed. For production environments facing significant attack traffic, pair .htaccess rules with iptables at the network layer and a WAF at the edge for comprehensive protection against brute force attacks and other threats.