Ravindra BagaleCourses & study guides Track your progress

Labs · Cyber Security

Lab: Add Rate Limiting to Nginx on Your Own Server and Confirm It Returns 429 Under a Small, Local Load Test

Intermediate30 minYour own EC2 server with Nginx (Labs 4 and 7) · curl · ab (ApacheBench, from httpd-tools)

Course: Cyber Security · Chapter 40: DoS and DDoS – Availability Attacks

Chapter 40 explains DoS and DDoS attacks; this lab adds a simple first defence, per-IP rate limiting, and tests it safely on your own server.

Chala mitrano! A website stays up only if no single visitor can eat all its capacity. Rate limiting says: "each IP, maximum 5 requests per second, a small burst is OK, after that please wait." Today we add it to our own Nginx and test it with a tiny load test, from the server to itself. Chala!

Suppose we are…

Suppose we run the web servers for IRCTC-style ticket booking at a smaller travel start-up. At 10:00 sharp, when bookings open, a few scripts hammer the site and real users get errors. A simple first defence is per-IP rate limiting in Nginx: normal users never notice it, but one IP sending hundreds of requests per second gets HTTP 429 Too Many Requests. We practise on our own EC2 server.

Goal of this lab

By the end you will have:

  • An Nginx rate limit of 5 requests per second per IP with a burst of 10.
  • Proof from curl and ab that excess requests get 429.
  • Log lines showing what Nginx limited.

What you need (all free)

  • Your own EC2 free-tier server with Nginx from Labs 4 and 7 (Amazon Linux 2023). An Ubuntu VM with Nginx also works.
  • About 30 minutes.

Safety and ethics

Load-test only your own server, and only from that server to localhost, with the small numbers shown. Sending heavy traffic to anyone else's website is a denial-of-service attack and illegal, even "just to test".

Steps

  1. SSH into your server and add the rate-limit settings. On Amazon Linux, files in /etc/nginx/conf.d/ are loaded inside the http block, so all sites get the limit:

    sudo tee /etc/nginx/conf.d/rate-limit.conf > /dev/null <<'EOF2'
    limit_req_zone $binary_remote_addr zone=perip:10m rate=5r/s;
    limit_req zone=perip burst=10 nodelay;
    limit_req_status 429;
    EOF2
    

    rate=5r/s is the steady rate per IP, burst=10 allows a short spike, nodelay serves the burst immediately, and limit_req_status 429 sends the correct "Too Many Requests" code.

  2. Test and reload:

    sudo nginx -t && sudo systemctl reload nginx
    

    What you should see: syntax is ok and test is successful.

  3. Send 30 quick requests from the server to itself and print only the status codes:

    for i in $(seq 1 30); do curl -s -o /dev/null -w "%{http_code} " http://localhost/; done; echo
    

    What you should see: about 11 200 codes (1 + the burst of 10), then 429 429 429 ....

  4. Wait 3 seconds and run one request: curl -s -o /dev/null -w "%{http_code}\n" http://localhost/. It returns 200 again: the limit refills over time, so normal users recover quickly.

  5. Install ApacheBench and run a small load test (100 requests, 10 at a time):

    sudo dnf install -y httpd-tools
    ab -n 100 -c 10 http://localhost/ | grep -E "Complete requests|Non-2xx|Requests per second"
    

    What you should see: Complete requests: 100 and Non-2xx responses: around 85–90. Those are the 429s.

  6. Read what Nginx logged:

    sudo tail -n 3 /var/log/nginx/error.log
    sudo awk '$9==429' /var/log/nginx/access.log | wc -l
    

    What you should see: lines with limiting requests, excess: 10.xxx by zone "perip", client: 127.0.0.1, and a count of 429 responses in the access log.

  7. Open your site in your browser from your laptop and click around normally. You should never see a 429, because a person clicks far slower than 5 requests per second (one page can load several files at once, which the burst covers).

Ravindra Bagale's Tip

Rate limiting on one server stops small floods and abusive scripts. A real DDoS from thousands of IPs needs help in front of the server: AWS Shield (on by default for basic protection), CloudFront or a CDN, and a WAF with rate rules. Layers again! And be generous with limits for pages with many images.

Common mistakes

Mistake What happens Fix
Putting limit_req_zone inside a server block nginx -t fails: directive not allowed here It belongs in the http context (conf.d on Amazon Linux)
Forgetting limit_​req_​status 429 Clients get 503, which looks like a server failure Set 429
Burst too small Real users get 429 when a page loads many images Use a burst like 10–20
Load-testing a site you do not own That is a DoS attack Only localhost on your own server
Not reloading after the change The old config still runs nginx -​t && systemctl reload nginx

Self-check checklist

0 of 5 done

Try-at-home challenge

Keep the site-wide limit but make a stricter one for a login page: 1 request per second, burst 5, only for location /login. Where do the two lines go?

Check your answer

Add a second zone in rate-limit.conf (http context): limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;. Then in your site's server block add location /login { limit_req zone=login burst=5 nodelay; try_files $uri $uri/ =404; }. When a location has its own limit_req, it is used there instead of the site-wide one. Test with nginx -t and the curl loop on /login.

Clean up to avoid charges

  1. To remove the limit: sudo rm /etc/nginx/conf.d/rate-limit.conf && sudo nginx -t && sudo systemctl reload nginx (or keep it; it is good protection).
  2. If you started an EC2 instance only for this lab: EC2 → Instances → select it → Instance state → Stop (or Terminate if you no longer need it). A stopped instance has no compute charge, but its EBS disk still counts against the 30 GB free tier.
  3. Release any Elastic IP you are not using (EC2 → Elastic IPs → Release), because public IPv4 addresses are charged.
  4. Check Billing → Bills at month end.

Samjla ka? Limit per IP, allow a small burst, return 429, read the logs. Aata pudhe jaauya: Chapter 41 protects sessions and cookies.