I once watched a client’s server grind to a halt during a product launch — not because of malicious traffic, but because a handful of users with slow connections were tying up worker threads while thousands of others tried to get through. Limiting concurrent connections, both globally and per-IP, is how I prevent this kind of resource exhaustion. Here’s my full approach.
Why Limiting Concurrent Connections Matters
- Prevents resource exhaustion from any single client (or attacker) hogging all available worker threads
- Protects against basic DoS attacks where an attacker opens many simultaneous connections to overwhelm the server
- Ensures fair access so no single user can degrade the experience for everyone else
- Keeps the server responsive under legitimate traffic spikes by reserving capacity
Prerequisites
- Root or sudo access
- Apache 2.4+ (I’ll cover both native MPM tuning and
mod_qos/mod_evasivefor more granular per-IP limiting) - An understanding of your server’s typical and peak traffic patterns before setting limits (guessing blindly risks blocking legitimate users)
Step 1: Understand MaxRequestWorkers as Your First Limit
The most basic form of connection limiting in Apache is simply capping the total number of workers via your MPM configuration, which I cover in more depth in my performance tuning post:
<IfModule mpm_event_module>
MaxRequestWorkers 150
</IfModule>
Once this limit is reached, new connections queue up rather than being served immediately, which protects the server from being overwhelmed — but it doesn’t prevent one IP from consuming a disproportionate share of those workers.
Step 2: Limit Simultaneous Connections Per-IP With mod_qos
For genuine per-client connection limiting, I install mod_qos, which gives fine-grained control:
sudo apt install libapache2-mod-qos
sudo a2enmod qos
Then I configure a per-IP connection cap:
<IfModule mod_qos.c>
QS_SrvMaxConnPerIP 50
QS_SrvMaxConnClose 2000
</IfModule>
QS_SrvMaxConnPerIP limits how many simultaneous connections a single IP address can hold open.
Step 3: Limit Requests Per Second With mod_ratelimit
While mod_qos controls concurrent connections, mod_ratelimit throttles bandwidth per connection, which indirectly helps manage load from data-heavy clients:
sudo a2enmod ratelimit
<Location "/downloads">
SetOutputFilter RATE_LIMIT
SetEnv rate-limit 400
</Location>
That example caps download speed to 400KB/s for anything under /downloads, freeing up bandwidth for other visitors.
Step 4: Use mod_evasive for DoS-Style Protection
mod_evasive is specifically designed to detect and block clients making an abnormally high number of requests in a short window — a classic sign of a DoS attempt or aggressive scraper:
sudo apt install libapache2-mod-evasive
sudo a2enmod evasive
Configuration:
<IfModule mod_evasive20.c>
DOSHashTableSize 3097
DOSPageCount 5
DOSPageInterval 1
DOSSiteCount 50
DOSSiteInterval 1
DOSBlockingPeriod 60
DOSEmailNotify admin@example.com
DOSLogDir "/var/log/mod_evasive"
</IfModule>
Here’s what these actually mean, since I always double check them against the server’s real traffic patterns:
DOSPageCount— max requests to the same page withinDOSPageIntervalseconds before flaggingDOSSiteCount— max total requests to the site withinDOSSiteIntervalsecondsDOSBlockingPeriod— how long (in seconds) an offending IP gets blocked
Step 5: Limit Connections at the Listen/Socket Level
For an additional layer, I sometimes use ListenBackLog to control how many pending connections the OS-level socket queue can hold before rejecting new ones outright:
ListenBackLog 511
This is more of a safety valve than a primary defense, but it’s worth setting deliberately rather than leaving at the OS default.
Step 6: Test Your Configuration
I always run a controlled load test to confirm limits are behaving as expected before relying on them in production:
ab -n 500 -c 100 https://yourdomain.com/
I watch the server’s response codes and worker activity during the test (via mod_status) to confirm the limits kick in appropriately rather than crashing the server.
Step 7: Monitor Real-World Impact
After deploying connection limits, I keep an eye on the mod_evasive and mod_qos logs for a few days to make sure legitimate users — like people behind a shared corporate NAT, where many users share one public IP — aren’t getting incorrectly throttled.
sudo tail -f /var/log/mod_evasive/*
If I see a shared office IP getting flagged, I raise the per-IP limits or add that IP to a whitelist:
<IfModule mod_evasive20.c>
DOSWhitelist 203.0.113.0/24
</IfModule>
Real-World Use Case
During a flash sale for an e-commerce client, we anticipated a traffic spike but were also worried about scrapers hitting the site aggressively to snipe inventory. I set QS_SrvMaxConnPerIP 20 and tuned mod_evasive thresholds based on load-test data beforehand. The sale went smoothly — a couple of scraper IPs got auto-blocked within the first ten minutes, while legitimate shoppers behind shared office or campus IPs weren’t affected because the per-IP cap was set generously enough.
Common Mistakes to Avoid
- Setting per-IP limits too low, which can block legitimate users sharing a NAT’d connection (common in offices, universities, and some mobile carriers).
- Not load testing before a known high-traffic event, discovering the limits are wrong only after the event has started.
- Relying only on Apache-level limiting for serious DDoS attacks — for large-scale distributed attacks, you need upstream protection like a CDN or a dedicated DDoS mitigation service.
- Forgetting to whitelist your own monitoring or load-testing tools, which can trigger the very limits you set up.
Troubleshooting Tips
If real users start reporting connection errors after implementing limits, check the mod_evasive and mod_qos logs to identify which rule is triggering, and consider whether the threshold needs to account for shared IPs (offices, VPNs, mobile carriers using CGNAT). If server load remains high despite connection limits, verify your MPM MaxRequestWorkers setting matches your actual available memory, since connection limiting alone won’t fix an underlying resource shortage.
Security and Performance Best Practices
- Use a CDN (like Cloudflare) in front of Apache to absorb the bulk of traffic spikes and distributed attacks before they reach your server at all.
- Combine connection limiting with IP blocking and mod_security for layered protection.
- Regularly review logs to fine-tune thresholds as your traffic patterns evolve.
- Document your limits and the reasoning behind them, since these numbers often need revisiting as the site grows.
Frequently Asked Questions
What’s the difference between MaxRequestWorkers and per-IP connection limits? MaxRequestWorkers caps the total connections the server will handle across everyone. Per-IP limits (via mod_qos or mod_evasive) prevent any single client from consuming a disproportionate share of that total.
Will limiting connections protect against a large-scale DDoS attack? Not on its own. Apache-level limiting helps with moderate abuse and basic DoS attempts, but large distributed attacks need upstream mitigation like a CDN or specialized DDoS protection service.
How do I choose the right per-IP connection limit? Base it on real traffic analysis — check your logs for legitimate concurrent connection patterns from shared IPs (offices, mobile carriers) and set the limit comfortably above that baseline.
Does mod_evasive replace a firewall? No, it’s a complementary, application-aware layer that reacts to request patterns Apache can see, whereas a firewall operates at the network level without that context.
Summary and Key Takeaways
Limiting concurrent connections in Apache protects your server from both malicious abuse and accidental resource exhaustion during traffic spikes. Start with sane MaxRequestWorkers tuning, add per-IP connection caps with mod_qos, and layer in mod_evasive for automated DoS-style detection — always load-testing your limits before relying on them during a real traffic event.