Ingress-NGINX Rate Limiting & Hardening Prompt
Design per-route rate limiting, connection limits, and abuse controls on ingress-nginx using annotations — including the memcached shared-state caveat, whitelist CIDRs, and how limits interact across replicas.
- Target user
- Platform and SRE engineers running the ingress-nginx controller in production
- Difficulty
- Advanced
- Tools
- Claude, ChatGPT, Cursor
The prompt
You are a senior SRE who has hardened many production ingress-nginx deployments against abusive traffic. You know exactly which `nginx.ingress.kubernetes.io/limit-*` annotations exist, how NGINX's leaky-bucket `limit_req`/`limit_conn` actually behave (including the burst multiplier and the fact that per-controller-replica counters are NOT shared unless memcached is configured), and how to layer rate limits with real-client-IP detection behind an upstream load balancer. I will provide: - The Ingress object(s) and the routes/hosts to protect - The threat: scraping, credential stuffing, expensive endpoint abuse, or accidental client retry storms - Controller topology: number of ingress-nginx replicas, and whether there is an L4/L7 load balancer or CDN in front - Where the real client IP comes from (`X-Forwarded-For`, PROXY protocol, cloud LB) and any trusted CIDRs to exempt - Current annotations / values already set Your job: 1. **Get client-IP detection right first**, because rate limiting keys on it. Explain `use-forwarded-headers`, `compute-full-forwarded-for`, `proxy-real-ip-cidr`, and PROXY protocol — and warn that if the controller keys on the LB's IP instead of the real client, every request shares one bucket and the limit is meaningless (or blocks everyone at once). 2. **Map the annotations precisely** and what each does: - `limit-rps` — requests/second/originating-IP (backed by `limit_req`). - `limit-rpm` — requests/minute/IP. - `limit-connections` — concurrent connections/IP (backed by `limit_conn`). - `limit-burst-multiplier` — burst = rps × multiplier (default 5); controls how much short-term spillover is allowed before 503. - `limit-rate` / `limit-rate-after` — response bandwidth throttling per connection. - `limit-whitelist` — CIDRs exempt from the limits (health checks, internal callers). Explain that exceeding the limit returns HTTP 503 by default (configurable via `limit-req-status-code`). 3. **Address the shared-counter caveat directly.** By default each controller replica keeps its OWN counter, so the effective limit is roughly `limit-rps × replicas`. Explain the options: set per-replica limits accounting for replica count, or configure the memcached-backed global limit (`global-rate-limit-*` annotations + a memcached service) for a true cluster-wide limit — and the trade-offs (memcached is a dependency and a single point of coordination). 4. **Produce the annotated Ingress** for the target policy, plus any controller-level ConfigMap settings that must accompany it (forwarded-headers, real-ip CIDRs, default status code). 5. **Design a rollout that won't self-inflict an outage**: start in observation using access logs / metrics to size the limit against real p99 traffic, apply a generous limit first, then tighten. Always whitelist health-check and known internal CIDRs so probes and internal services aren't throttled. 6. **Give the verification and tuning loop**: how to load-test the route (e.g. `hey`/`wrk`), read `nginx_ingress_controller_requests` by status, watch for 503 spikes, and confirm legitimate bursts pass while abuse is shed. Include how to read the controller logs for `limiting requests` entries. 7. **Cover the interactions**: rate limiting does not replace `limit-connections` for slow-loris-style abuse; WAF/ModSecurity is a separate layer; and per-path annotations require separate Ingress objects (annotations are per-Ingress, not per-path). Flag anything that can cause a self-DoS: a limit set below real traffic, keying on the LB IP (one shared bucket), or forgetting to whitelist health-check probes. --- Ingress object(s) + routes to protect: ```yaml [PASTE] ``` Threat / abuse pattern: [DESCRIBE] Controller replicas + LB/CDN in front: [DESCRIBE] Real client IP source + trusted CIDRs: [DESCRIBE] Current annotations / ConfigMap values: [PASTE or "defaults"]
Run this prompt with AI
Test it, get an AI-improved version, or compare models — live in the Prompt Workspace. No copy-paste.
Why this prompt works
ingress-nginx rate limiting looks like a two-line annotation, but the parts that bite in production are invisible in the docs snippet: the counter is per-replica, the limit keys on whatever IP NGINX thinks is the client, and a too-tight limit takes down your own health checks. This prompt forces client-IP correctness first, then sizing, then the shared-state decision.
How to use it
- Verify real-client-IP detection before touching limit numbers — everything downstream depends on it.
- Decide per-replica vs. global (memcached) limits based on how many controller replicas you run.
- Whitelist probes and internal CIDRs up front.
- Roll out generous, observe, then tighten against measured traffic.
Useful commands
# Inspect the effective annotations on an Ingress
kubectl get ingress <name> -n <ns> -o yaml | grep -A20 annotations
# Controller ConfigMap (real-ip / forwarded headers live here)
kubectl -n ingress-nginx get configmap ingress-nginx-controller -o yaml
# Watch request counts by status code
kubectl -n ingress-nginx get --raw /metrics 2>/dev/null | grep nginx_ingress_controller_requests | grep 'status="503"'
# Controller logs showing throttling
kubectl -n ingress-nginx logs deploy/ingress-nginx-controller | grep -i 'limiting requests'
# Load test a route (confirm bursts pass, abuse is shed)
hey -z 30s -q 50 -c 20 https://api.example.com/expensive
Pattern: per-route rate limit with real-IP and whitelist
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-expensive
namespace: prod
annotations:
nginx.ingress.kubernetes.io/limit-rps: "10"
nginx.ingress.kubernetes.io/limit-burst-multiplier: "3" # burst = 30
nginx.ingress.kubernetes.io/limit-connections: "20"
nginx.ingress.kubernetes.io/limit-req-status-code: "429" # clients back off
nginx.ingress.kubernetes.io/limit-whitelist: "10.0.0.0/8,192.168.100.0/24"
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /expensive
pathType: Prefix
backend:
service:
name: api
port:
number: 8080
Pattern: controller ConfigMap for real client IP behind an LB
apiVersion: v1
kind: ConfigMap
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
data:
use-forwarded-headers: "true"
compute-full-forwarded-for: "true"
proxy-real-ip-cidr: "10.0.0.0/8" # trust only your LB subnet
# use-proxy-protocol: "true" # if the LB speaks PROXY protocol instead
Pattern: true cluster-wide limit (memcached-backed)
metadata:
annotations:
nginx.ingress.kubernetes.io/global-rate-limit: "100"
nginx.ingress.kubernetes.io/global-rate-limit-window: "1m"
nginx.ingress.kubernetes.io/global-rate-limit-key: "$remote_addr"
# Requires the controller configured with a memcached service:
# global-rate-limit-memcached-host / -port in the controller ConfigMap.
Common findings this catches
- One shared bucket blocks everyone → controller keys on the LB IP; forwarded-headers/real-ip misconfigured.
- Limit is effectively N× intended → per-replica counters and multiple controller replicas; not using the global limit.
- Health checks 503’ing → probe/internal CIDRs missing from
limit-whitelist. - Retry storms after enforcement → 503 default status; switch to 429 so compliant clients back off.
- Per-path policy not applying → annotations set on an Ingress covering multiple paths; split into separate Ingress objects.
When to escalate
- Sustained abuse beyond L7 rate limiting (volumetric/DDoS) — engage the CDN/edge or network team.
- Application-layer attacks (injection, credential stuffing patterns) — add a WAF/ModSecurity layer, out of scope for rate limits.
- Controller-wide instability under load — review controller resources/replica count with the platform team before tightening limits further.
Related prompts
-
NetworkPolicy Default-Deny Baseline Design Prompt
Author a zero-trust NetworkPolicy baseline for a cluster — default-deny ingress and egress per namespace, explicit allow rules for DNS and platform traffic, and a safe rollout that won't black-hole production.
-
Kubernetes Encryption-at-Rest KMS Provider Design Prompt
Design and roll out etcd encryption-at-rest with an EncryptionConfiguration and a KMS v2 provider — provider ordering, key rotation, and re-encrypting existing Secrets without downtime.
-
Kubernetes User Namespaces Pod Isolation Design Prompt
Design and roll out user-namespaced pods (hostUsers: false) so container root maps to an unprivileged host UID — hardening against container-escape and CVE blast radius without breaking volumes or images.
-
Kubernetes API Server Audit Policy Design Prompt
Design a kube-apiserver audit policy that captures security-relevant events at the right level (Metadata vs Request vs RequestResponse) without flooding the audit backend or leaking secrets.
More Kubernetes & Helm prompts & error guides
Browse every Kubernetes & Helm prompt and troubleshooting guide in one place.
Reading prompts? Get all 500 in one free PDF
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.