Labs · Cyber Security
Lab: Add Rate Limiting to Nginx on Your Own Server and Confirm It Returns 429 Under a Small, Local Load Test
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!
चला मित्रांनो! Website तेव्हाच चालू राहते जेव्हा कोणताही एक visitor त्याची सगळी क्षमता खाऊ शकत नाही. Rate limiting म्हणतं: "प्रत्येक IP ला जास्तीत जास्त 5 requests प्रति second, छोटा burst चालेल, त्यानंतर थांबा." आज आपण ते आपल्या Nginx ला लावणार आणि छोट्या load test ने test करणार, server वरून स्वतःलाच. चला!
चलो दोस्तों! Website तभी चलती है जब कोई एक visitor उसकी सारी क्षमता न खा सके। Rate limiting कहता है: "हर IP को ज़्यादा से ज़्यादा 5 requests प्रति second, छोटा burst चलेगा, उसके बाद रुको।" आज हम इसे अपने Nginx में लगाएँगे और छोटे load test से test करेंगे, server से खुद को ही। चलो!
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
curlandabthat excess requests get429. - 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
-
SSH into your server and add the rate-limit settings. On Amazon Linux, files in
/etc/nginx/conf.d/are loaded inside thehttpblock, 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; EOF2rate=5r/sis the steady rate per IP,burst=10allows a short spike,nodelayserves the burst immediately, andlimit_req_status 429sends the correct "Too Many Requests" code. -
Test and reload:
sudo nginx -t && sudo systemctl reload nginxWhat you should see:
syntax is okandtest is successful. -
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; echoWhat you should see: about 11
200codes (1 + the burst of 10), then429 429 429 .... -
Wait 3 seconds and run one request:
curl -s -o /dev/null -w "%{http_code}\n" http://localhost/. It returns200again: the limit refills over time, so normal users recover quickly. -
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: 100andNon-2xx responses:around 85–90. Those are the 429s. -
Read what Nginx logged:
sudo tail -n 3 /var/log/nginx/error.log sudo awk '$9==429' /var/log/nginx/access.log | wc -lWhat 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. -
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.
Ravindra Bagale's Tip – मराठी
एका server वरचं rate limiting छोटे floods आणि abusive scripts थांबवतं. हजारो IPs वरून येणाऱ्या खऱ्या DDoS साठी server च्या पुढे मदत लागते: AWS Shield (basic protection default ने चालू), CloudFront किंवा CDN, आणि rate rules असलेला WAF. पुन्हा layers! आणि खूप images असलेल्या pages साठी limits उदार ठेवा.
Ravindra Bagale's Tip – हिंदी
एक server पर rate limiting छोटे floods और abusive scripts रोकता है। हज़ारों IPs से आने वाले असली DDoS के लिए server के आगे मदद चाहिए: AWS Shield (basic protection default से चालू), CloudFront या CDN, और rate rules वाला WAF। फिर से layers! और बहुत images वाले pages के लिए limits उदार रखो।
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
- 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). - 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.
- Release any Elastic IP you are not using (EC2 → Elastic IPs → Release), because public IPv4 addresses are charged.
- 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.