Protect Your Bare‑Metal Server: NGINX Rate‑Limiting for L7 DDoS
Configure NGINX rate-limiting on bare-metal servers to stop Layer 7 DDoS. Learn to set zones, apply limits, and integrate Fail2Ban.
6 min read
Running a public‑facing web service on a bare‑metal server gives you full control over the stack, but it also means you must defend against Layer 7 (application‑layer) DDoS attacks. Unlike network‑level floods, Layer 7 attacks send many legitimate‑looking HTTP requests, exhausting CPU, memory, or database connections on your server.
NGINX’s built‑in rate‑limiting directives let you throttle abusive clients before they can cause damage. This guide walks through the entire process of configuring NGINX rate‑limiting on a bare‑metal server, covering:
Setting up shared memory zones for tracking request rates
Applying limits per IP, per location, and per server block
Combining rate‑limiting with Fail2Ban for automated bans
Testing and tuning the configuration
1. Prerequisites and Environment Overview
Before you start, ensure you have:
A bare‑metal server running a recent version of NGINX (preferably the official stable release).
Root or sudo access to edit NGINX configuration files.
Basic familiarity with Linux command‑line tools.
Because the article does not target a specific distribution, installation steps are provided for both Debian/Ubuntu (using apt) and AlmaLinux/Rocky/RHEL (using dnf). Windows Server uses a different binary and configuration model, so those steps are omitted here.
2. Installing NGINX
Debian / Ubuntu (apt)
# Update the package index
sudo apt update
# Install NGINX
sudo apt install -y nginx
# Verify the installation
nginx -v
Explanation:apt update refreshes the local package list. apt install nginx pulls the latest stable NGINX package from the distribution’s repository. nginx -v prints the installed version.
AlmaLinux / Rocky / RHEL (dnf)
# Enable the EPEL repository (required for the latest NGINX)
sudo dnf install -y epel-release
# Install the official NGINX repository
sudo dnf install -y https://nginx.org/packages/rhel/nginx-release.rpm
# Install NGINX
sudo dnf install -y nginx
# Verify the installation
nginx -v
Explanation: The EPEL repository provides extra packages. The official NGINX repository ensures you get the upstream version rather than the older distro‑provided one. nginx -v confirms the binary is available.
3. Understanding NGINX Rate‑Limiting Directives
NGINX uses two pairs of directives to control traffic:
Request rate limiting – limits the number of requests per second per key (usually the client IP). Implemented with limit_req_zone (defines a shared memory zone) and limit_req (applies the limit).
Connection limiting – caps the number of simultaneous connections per key. Implemented with limit_conn_zone and limit_conn.
Both mechanisms rely on a shared memory zone that stores counters for each key. The zone is defined at the http level and can be referenced in any server or location block.
4. Defining Shared Memory Zones
Open the main NGINX configuration file (/etc/nginx/nginx.conf) and add the following inside the http block:
# Limit 10 requests per second per IP, with a burst of 20
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
# Limit 50 simultaneous connections per IP
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
What each part does:
$binary_remote_addr – the client IP address in binary form (more efficient for hashing).
zone=req_limit:10m – creates a memory zone named req_limit with 10 MB of storage, sufficient for hundreds of thousands of unique IPs.
rate=10r/s – permits 10 requests per second for each IP.
burst=20 (applied later) – allows short spikes up to 20 extra requests without immediate rejection.
The second zone, conn_limit, tracks concurrent connections without a rate component.
5. Applying Limits to Server Blocks
Now we’ll enforce the limits on a typical site configuration. Edit the site’s file (e.g., /etc/nginx/sites-available/example.com on Debian/Ubuntu or /etc/nginx/conf.d/example.com.conf on AlmaLinux/Rocky) and add the directives inside the appropriate server or location blocks.
Basic per‑IP request limiting
server {
listen 80;
server_name example.com;
# Apply request rate limiting
limit_req zone=req_limit burst=20 nodelay;
# Apply connection limiting
limit_conn conn_limit 50;
location / {
# Normal proxy or static content configuration goes here
try_files $uri $uri/ =404;
}
}
Directive breakdown:
limit_req zone=req_limit burst=20 nodelay; – uses the req_limit zone, permits a burst of 20 extra requests, and nodelay forces immediate rejection once the burst is exceeded (useful for aggressive DDoS mitigation).
limit_conn conn_limit 50; – caps simultaneous connections from a single IP at 50.
Fine‑tuning for specific locations
Sometimes you want stricter limits on API endpoints while keeping the main site more permissive. Add a location block for the API:
Here the burst is reduced to 10 and the concurrent connection limit to 20, reflecting the higher sensitivity of API resources.
6. Integrating Fail2Ban for Automated Bans
Rate‑limiting throttles traffic, but you may still want to block IPs that repeatedly exceed thresholds. Fail2Ban can monitor NGINX logs and add offending IPs to iptables or nftables.
Then add a corresponding filter file /etc/fail2ban/filter.d/nginx-limit-req.conf:
[Definition]
# Match log lines where the request was rejected with status 503 (default for limit_req)
failregex = ^<HOST> - - \[.*\] ".*" 503 .*
Restart Fail2Ban to apply the changes:
sudo systemctl restart fail2ban
Now any IP that triggers the 503 “Service Unavailable” response from limit_req more than five times within five minutes will be blocked for one hour.
7. Testing the Configuration
After reloading NGINX (sudo systemctl reload nginx), verify the limits with a simple curl loop:
# Replace example.com with your domain or IP
for i in $(seq 1 30); do
curl -s -o /dev/null -w "%{http_code} " http://example.com/
done
echo
You should see a series of 200 responses followed by 503 once the burst limit is exceeded. Adjust rate, burst, and limit_conn values based on observed traffic patterns.
8. Fine‑Tuning and Best Practices
Start conservative. Begin with modest limits (e.g., 5 r/s, burst 10) and monitor legitimate traffic.
Use separate zones for different services. A public API may need stricter limits than a static website.
Combine with caching. Enabling proxy_cache reduces backend load, giving rate‑limiting more headroom.
Monitor memory usage. Each zone consumes shared memory; increase the :10m size only if you see “no memory” warnings in the NGINX error log.
Log rejected requests. Add a custom log format to capture the $limit_req_status variable for later analysis.
Conclusion
NGINX’s native rate‑limiting features provide a straightforward, low‑overhead defense against Layer 7 DDoS attacks on bare‑metal servers. By defining shared memory zones, applying per‑IP request and connection limits, and optionally coupling the setup with Fail2Ban, you can mitigate abusive traffic while preserving a good experience for legitimate users. Regularly review logs, adjust thresholds, and complement rate‑limiting with caching and upstream hardening to maintain a resilient web presence.
nginx rate limitingddos protectionfail2banbare metal serverlayer 7 attackslinux server securityrequest throttlingnginx configuration
Try it on your own server
Follow along on a Cloud VPS with full root access, or read the step-by-step knowledge base guides.