Security

Layer 7 attack protection with Nginx rate limiting and Cloudflare

If you run a web application on a VPS, a Layer 7 DDoS attack is not a matter of "if" but "when." Unlike volumetric floods at Layer 3/4, Layer 7 attacks mimic legitimate HTTP traffic, they're harder to filter at the firewall and can bring your Nginx server to its knees with a few hundred concurrent connections. This guide uses two overlapping layers: Nginx rate limiting at the origin (your VPS) and Cloudflare's WAF at the edge. By the time you finish, you'll have a config that drops abusive clients before they consume a socket.

Prerequisites

  • A VPS running Ubuntu 24.04 LTS with a non-root sudo user. If you are looking for a reliable Vietnam VPS with root access, you can rent a Linux VPS from thueVPS, our NVMe SSD plans include a dedicated IPv4 and full root control.
  • Nginx installed and serving a site.
  • Cloudflare proxying your domain (orange cloud enabled in DNS).
  • Basic familiarity with SSH and editing config files.

Why Layer 7 attacks bypass traditional defenses

A typical SYN flood is stopped by a VPS firewall or Cloudflare's network-layer scrubbing. Layer 7 attacks speak HTTP: they finish the TCP handshake, send a valid GET/POST request, then open another. Your Nginx worker processes hit the worker_connections limit, and the server stops responding to real users. Nginx rate limiting (limit_req_zone) caps request rates at the application level, a single IP sending 5000 requests per second gets a 503 after the first burst. At the edge, Cloudflare's WAF and rate limiting rules block the attack before it reaches your VPS bandwidth.

The combination is necessary because each layer covers the other's blind spots: Cloudflare handles volumetric spikes and geo-distributed attacks, while Nginx acts as the last line of defense if an attacker bypasses Cloudflare (e.g., hitting the origin IP directly).

Step 1, Block direct origin traffic (Cloudflare-only access)

If an attacker discovers your origin IP, they hit Nginx without passing Cloudflare's filters. You must restrict Nginx to accept traffic only from Cloudflare's proxy IP ranges. Cloudflare publishes their current IP ranges at https://www.cloudflare.com/ips-v4 and https://www.cloudflare.com/ips-v6.

Create a file under /etc/nginx to store them:

sudo nano /etc/nginx/cloudflare-ips.conf

Paste the current ranges (these may change; check on setup):

# IPv4
allow 173.245.48.0/20;
allow 103.21.244.0/22;
allow 103.22.200.0/22;
allow 103.31.4.0/22;
allow 141.101.64.0/18;
allow 108.162.192.0/18;
allow 190.93.240.0/20;
allow 188.114.96.0/20;
allow 197.234.240.0/22;
allow 198.41.128.0/17;
allow 162.158.0.0/15;
allow 104.16.0.0/13;
allow 104.24.0.0/14;
allow 172.64.0.0/13;
allow 131.0.72.0/22;
# IPv6
allow 2400:cb00::/32;
allow 2606:4700::/32;
allow 2803:f800::/32;
allow 2405:b500::/32;
allow 2405:8100::/32;
allow 2a06:98c0::/29;
allow 2c0f:f248::/32;
deny all;

Save and exit. Then include this file in your server block's location / or in the server context:

include /etc/nginx/cloudflare-ips.conf;

Verify: Test config and reload:

sudo nginx -t
sudo systemctl reload nginx

If you do not own a VPS yet, consider a VPS Vietnam with a dedicated IPv4, setting up Cloudflare proxying is straightforward with a clean IP and rDNS.

Step 2, Restore real visitor IP from Cloudflare headers

When Cloudflare proxies your traffic, Nginx sees Cloudflare's IP in $remote_addr, not the visitor's. Rate limiting must use the real client IP, or you'd limit Cloudflare's entire range. Install the Cloudflare module or use the geo + map approach. The cleanest way is to set up realip:

sudo nano /etc/nginx/conf.d/cloudflare-realip.conf
# Set real IP from CF-Connecting-IP
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
# ... (repeat all Cloudflare IPv4/IPv6 ranges from step 1)
set_real_ip_from 2c0f:f248::/32;
real_ip_header CF-Connecting-IP;
real_ip_recursive on;

Verify: Check the log format, $remote_addr should now show visitor IPs, not Cloudflare's.

Step 3, Implement Nginx rate limiting zones

Rate limiting in Nginx uses two directives: limit_req_zone (defines a shared memory zone and rate) and limit_req (applies it to a location). Open /etc/nginx/nginx.conf and add a zone inside the http block:

http {
    # ... other settings
    limit_req_zone $binary_remote_addr zone=login:10m rate=5r/s;
    limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
    limit_req_zone $binary_remote_addr zone=general:10m rate=100r/s;
    # ...
}

Explanation of each:

  • login, 5 requests per second per IP. Login endpoints are high-value targets for brute force. An attacker trying 1000 passwords per second hits this zone and gets a 503 after the first 5.
  • api, 30 r/s. Public API endpoints. Legitimate clients (even under burst) rarely exceed this.
  • general, 100 r/s for static assets and pages. Most bots send more than 100 requests per second; humans on a page refresh are ~1-5 requests.

The zone size 10m stores about 160,000 IP entries, more than enough for a medium-traffic VPS. Adjust the zone name and rate to your application's baseline. If your site sees 200 real requests per second during peak, set the general zone to 300 r/s.

Now apply the zones in your server block or specific locations:

server {
    # ...
    location /wp-login.php {
        limit_req zone=login burst=10 nodelay;
        proxy_pass http://backend;
    }
    location /api/ {
        limit_req zone=api burst=20 nodelay;
        proxy_pass http://backend;
    }
    location / {
        limit_req zone=general burst=50;
        try_files $uri $uri/ /index.php;
    }
}

Burst allows a short spike above the rate. nodelay means excess requests are processed immediately during the burst, not queued. For login endpoints, nodelay is dangerous if you set a high burst, an attacker with 20 burst slots still fires 20 rapid requests. Use a low burst value (5-10) for sensitive paths.

Verify: Reload Nginx and test with a quick burst:

ab -n 100 -c 10 http://yourdomain.com/api/test

Watch for 503 Service Unavailable responses in the output, those are hits from the rate limiter. Also check Nginx's error log:

sudo tail -f /var/log/nginx/error.log | grep "limiting"

You'll see entries like "limiting requests, excess: 5.234 by zone 'general'...", that confirms the zone is active.

Step 4, Configure Cloudflare WAF rate limiting rules

Edge rate limiting catches attacks before they consume your VPS bandwidth. In the Cloudflare dashboard, go to SecurityWAFRate limiting rules. Create a custom rule:

  • Expression: (http.request.uri.path contains "/wp-login.php" and ip.geoip.country ne "VN"), protects login from non-local IPs.
  • Characteristics: IP.
  • Limit: 20 requests per 60 seconds.
  • Action: Block or Managed Challenge (recommended for borderline traffic).

For a general API endpoint:

  • Expression: http.request.uri.path contains "/api/"
  • Limit: 100 requests per 10 seconds.
  • Action: Block.

Why set both Cloudflare + Nginx? Cloudflare stops the traffic at the edge, the requests never hit your VPS network interface. If an attacker bypasses Cloudflare (e.g., hits the origin IP directly), Nginx's rate limiter is still active. The two layers are independent; one failing does not break the other.

If you operate multiple services on one VPS, like a self-hosted n8n instance or a website, you can create separate WAF rules per domain. For automation-heavy setups, a VPS for n8n with Cloudflare proxying ensures your workflow engine is not exposed to direct attacks.

Verify: Use curl from a non-proxied machine to hit the origin IP directly (if you know it):

curl -I http://your-origin-ip.com
# Should return 403 if Cloudflare IP check is active

For rate limit testing, send rapid requests:

for i in {1..200}; do curl -s -o /dev/null -w "%{http_code}\n" http://yourdomain.com/api/test; done | sort | uniq -c

You should see a mix of 200 and 429 (Cloudflare) or 503 (Nginx).

Troubleshooting

Rate limit zone fills up quickly. If your zone is 10m and you see hundreds of thousands of unique IPs (e.g., from a botnet), increase the zone size or add a geo filter to exclude known friendly IPs. limit_req_zone with $binary_remote_addr uses ~64 bytes per entry.

Legitimate users hit 503. Increase the burst parameter or the rate. Start with double your baseline peak traffic and monitor for a week. nodelay can cause cascading bursts, remove it for general traffic so excess requests are queued (slowed down) rather than dropped.

Cloudflare shows "429" but Nginx does not. The edge rule is working, traffic is blocked before it reaches your VPS. If you want to see the blocked traffic in Nginx logs, disable the edge rule temporarily for testing and observe the origin rate limiter.

Origin IP leaks. Check DNS history on sites like securitytrails.com, old A records may expose your IP. Change the origin IP if it has been exposed, then set allow only from Cloudflare ranges as in Step 1.

FAQ

Does Nginx rate limiting stop all Layer 7 attacks?

No. Sophisticated attackers distribute requests across thousands of IPs, each IP stays below your rate limit. Nginx rate limiting is effective against single-source attacks (bots, scrapers, brute force). For distributed attacks, combine it with Cloudflare's WAF, a web application firewall (like ModSecurity), and a CDN that can absorb large volumes.

Should I set rate limiting on Cloudflare or Nginx?

Both, for defense in depth. Cloudflare stops traffic at the edge, your VPS never sees it. Nginx protects you if the attacker bypasses Cloudflare or if you have a direct origin connection. The two layers are complementary, not redundant.

Does rate limiting affect SEO or Googlebot?

Googlebot sends bursts of requests when crawling. If your general zone is too tight (e.g., 10 r/s), Googlebot may get 503 errors, which harms indexing. Set the general zone to at least 100 r/s with a burst of 50, and whitelist Googlebot's IP ranges (listed at Google's crawler docs) in a separate limit_req_zone with a higher limit.

Can I use Nginx rate limiting for API key-based throttling?

Yes. Replace $binary_remote_addr with $http_x_api_key or a custom header, but be cautious, headers can be forged. For production, combine with an API gateway or a dedicated rate limiting service like Kong's rate limiting plugin.

Does thueVPS support Cloudflare proxying for their VPS plans?

Yes. All Linux VPS plans from thueVPS come with a dedicated IPv4 and full root access, so you can configure Nginx, install Cloudflare modules, and set up reverse proxying without restrictions. The NVMe SSD storage and domestic bandwidth ensure low latency when Cloudflare routes traffic through Vietnam PoPs.

What if I do not use Cloudflare? Can I still rate-limit with Nginx alone?

Absolutely. Nginx rate limiting works independently of Cloudflare. You will still block single-source Layer 7 attacks (brute force, scrapers, slow loris variants). For volumetric distributed attacks without Cloudflare, consider a dedicated DDoS mitigation service or a higher-tier VPS with more bandwidth and CPU, our dedicated servers can handle larger volumes with proper Nginx tuning.

Related articles

Note: This guide is for general reference. Every system and infrastructure has its own specifics, so test each step in a safe environment and consult a qualified engineer before applying it in production.