
Rate limiting is one of the most effective defensive measures you can implement to protect web applications from abuse, brute force attacks, and denial of service attempts. Apache 2 provides several powerful modules for implementing rate limiting, each suited to different use cases and threat models.
Why Rate Limiting Matters
Before diving into configuration, it’s worth understanding what you’re defending against. Uncontrolled request rates can lead to resource exhaustion on your server, successful credential stuffing attacks against login forms, API abuse that affects legitimate users, and scraping that consumes bandwidth and potentially exposes data you’d prefer to keep harder to harvest at scale.
The Primary Tools: mod_ratelimit vs mod_evasive vs mod_qos
Apache offers several approaches to rate limiting, and choosing the right one depends on your specific requirements.
mod_ratelimit: Bandwidth Throttling
This module limits the bandwidth of a connection rather than the number of requests. It’s ideal for controlling download speeds or preventing a single client from saturating your connection.
<Location "/downloads">
SetOutputFilter RATE_LIMIT
SetEnv rate-limit 400
SetEnv rate-initial-burst 512
</Location>
The rate-limit value is in KB/s, so this configuration limits downloads to approximately 400 KB/s after an initial burst of 512 KB. This approach works well for file servers or media delivery where you want fair bandwidth distribution.
mod_evasive: DDoS and Brute Force Protection
For request-based rate limiting, mod_evasive is the traditional choice. It tracks requests per IP and can temporarily blacklist offenders.
First, install it on Debian/Ubuntu systems:
apt install libapache2-mod-evasive
a2enmod evasive
Then configure it:
<IfModule mod_evasive20.c>
DOSHashTableSize 3097
DOSPageCount 5
DOSSiteCount 100
DOSPageInterval 1
DOSSiteInterval 1
DOSBlockingPeriod 60
DOSEmailNotify [email protected]
DOSSystemCommand "sudo /usr/local/bin/ban_ip.sh %s"
DOSLogDir "/var/log/mod_evasive"
DOSWhitelist 127.0.0.1
DOSWhitelist 192.168.1.*
</IfModule>
Breaking down these directives: DOSPageCount and DOSPageInterval work together—if a client requests the same page more than 5 times in 1 second, they’re blocked. DOSSiteCount and DOSSiteInterval set the threshold for total requests to any page on the site. DOSBlockingPeriod determines how long offenders are blocked in seconds.
The DOSSystemCommand directive is particularly powerful for integration with firewall rules. A simple ban script might look like:
#!/bin/bash
# /usr/local/bin/ban_ip.sh
/sbin/iptables -I INPUT -s $1 -j DROP
# Auto-unban after 10 minutes
echo "/sbin/iptables -D INPUT -s $1 -j DROP" | at now + 10 minutes
mod_qos: Fine-Grained Quality of Service
For more sophisticated rate limiting, mod_qos provides granular control. It can limit concurrent connections, request rates, and even bandwidth on a per-location or per-client basis.
<IfModule mod_qos.c>
# Maximum concurrent connections per IP
QS_SrvMaxConnPerIP 50
# Maximum concurrent connections total
QS_SrvMaxConn 500
# Limit request rate per second per IP for specific locations
<Location "/api">
QS_LocRequestLimitMatch "^.*$" 50
QS_LocRequestPerSecLimitMatch "^.*$" 10
</Location>
# Slow down connections that exceed thresholds rather than blocking
QS_SrvMinDataRate 150 1200
</IfModule>
The QS_SrvMinDataRate directive is elegant—it sets minimum data rates (150 bytes/s with a max of 1200 connections) and slows down connections that fall below this threshold, which helps identify and throttle slowloris-style attacks.
Protecting Specific Endpoints
Real-world rate limiting often needs to be surgical. Login pages, API endpoints, and form submission handlers typically need stricter limits than static content.
Login Page Protection
<Location "/wp-login.php">
<IfModule mod_qos.c>
QS_LocRequestPerSecLimitMatch "^.*$" 2
QS_LocRequestLimitMatch "^.*$" 10
</IfModule>
</Location>
<Location "/user/login">
<IfModule mod_qos.c>
QS_LocRequestPerSecLimitMatch "^.*$" 2
QS_LocRequestLimitMatch "^.*$" 10
</IfModule>
</Location>
API Rate Limiting with Custom Headers
For APIs, you’ll want to communicate rate limit status to clients. This requires combining mod_headers with your rate limiting:
<Location "/api/v1">
<IfModule mod_qos.c>
QS_LocRequestPerSecLimitMatch "^.*$" 30
</IfModule>
<IfModule mod_headers.c>
Header always set X-RateLimit-Limit "30"
Header always set Retry-After "60" "expr=%{REQUEST_STATUS} == 429"
</IfModule>
</Location>
Using mod_rewrite for Conditional Rate Limiting
Sometimes you need rate limiting logic that the dedicated modules don’t handle well. mod_rewrite combined with environment variables can fill gaps:
<IfModule mod_rewrite.c>
RewriteEngine On
# Rate limit POST requests more aggressively
RewriteCond %{REQUEST_METHOD} POST
RewriteCond %{REQUEST_URI} ^/api/
RewriteRule .* - [E=RATE_LIMIT_STRICT:1]
# Skip rate limiting for authenticated internal services
RewriteCond %{HTTP:X-Internal-Service-Key} ^your-secret-key$
RewriteRule .* - [E=RATE_LIMIT_BYPASS:1]
</IfModule>
<If "-n %{ENV:RATE_LIMIT_STRICT} && ! -n %{ENV:RATE_LIMIT_BYPASS}">
<IfModule mod_qos.c>
QS_LocRequestPerSecLimitMatch "^.*$" 5
</IfModule>
</If>
Fail2Ban Integration
For defense in depth, integrate Apache rate limiting with fail2ban. Create a filter that matches mod_evasive log entries:
# /etc/fail2ban/filter.d/apache-evasive.conf
[Definition]
failregex = ^<HOST> was blocked by mod_evasive
ignoreregex =
And the corresponding jail:
# /etc/fail2ban/jail.local
[apache-evasive]
enabled = true port = http,https filter = apache-evasive logpath = /var/log/mod_evasive/dos-* maxretry = 1 bantime = 3600
Performance Considerations
Rate limiting modules consume memory and CPU cycles. A few optimizations help:
Keep the hash table size in mod_evasive as a prime number and sized appropriately for your traffic volume. Too small wastes CPU on collisions; too large wastes memory.
Consider using mod_qos’s QS_ClientEntries directive to limit tracking memory:
QS_ClientEntries 100000
QS_ClientPrefer "^192\.168\."
For high-traffic sites, offload rate limiting to a reverse proxy like nginx or a CDN where possible. Apache can then handle rate limiting for traffic that bypasses those layers.
Testing Your Configuration
Before deploying rate limiting to production, test it. Apache Bench works for basic testing:
ab -n 1000 -c 100 https://yoursite.com/api/endpoint
For more realistic testing, use tools like siege or locust that can simulate varied request patterns:
siege -c 50 -t 60s -v https://yoursite.com/login
Watch your logs during testing:
tail -f /var/log/apache2/error.log /var/log/mod_evasive/*
Common Pitfalls
A few things I’ve seen go wrong in production deployments: setting thresholds too low and blocking legitimate users behind corporate NATs, forgetting to whitelist monitoring services and load balancer health checks, not accounting for AJAX-heavy applications that make many requests per page load, and failing to monitor false positives after deployment.
Start with permissive thresholds, monitor for a week, then tighten based on observed traffic patterns.
Conclusion
Effective rate limiting is about layering defenses. Use mod_ratelimit for bandwidth control, mod_evasive for basic DDoS protection, and mod_qos when you need fine-grained control. Combine these with fail2ban for persistent offenders and always test thoroughly before production deployment.
The configurations here provide a solid foundation, but every environment is different. Monitor your logs, adjust thresholds based on real traffic patterns, and remember that rate limiting is one part of a broader security strategy that should include WAF rules, proper authentication, and regular security audits.
No Comments yet!