Back to Blog
DevOpsJune 28, 20264 min read

Nginx Rate Limiting: How to Protect Your API from Brute Force

Master Nginx rate limiting using limit_req_zone to protect your backend APIs. Prevent brute force attacks and resource exhaustion with these configs.

NginxDevOpsAPI SecurityRate LimitingWeb ServersPerformance

When one of our production APIs started crawling during a suspected credential stuffing attack last year, I realized our application-level guards weren't enough. We were wasting CPU cycles just to reject requests that should have been dropped at the edge. Implementing Nginx rate limiting saved our database from total saturation and gave us the breathing room to block the bad actors properly.

If you're looking for Rate limiting API security: A Practical Guide for Node.js and Laravel, it's important to remember that offloading this to your web server is always more efficient than handling it in your application code.

How Nginx Rate Limiting Works

Nginx uses the leaky bucket algorithm to control the rate of incoming requests. Think of it like a funnel: you have a defined flow rate, and if requests come in faster than that, they get queued or rejected.

The core of this is the limit_req_zone directive. You define a shared memory zone to track client IPs and then apply that zone to specific locations.

Setting Up the Basic Config

To get started, add the limit_req_zone inside your http block in nginx.conf. This allocates memory to store the state of your clients.

NGINX
http {
    # Define a zone named "api_limit" using the binary remote address
    # 10m is about 160,000 unique IP addresses
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=5r/s;

    server {
        location /api/ {
            # Apply the limit, allow a burst of 10 requests, and don't delay
            limit_req zone=api_limit burst=10 nodelay;
            proxy_pass http://backend_upstream;
        }
    }
}

In this setup, we allow 5 requests per second. The burst=10 parameter allows a client to exceed that rate temporarily, but Nginx will buffer the extra requests. If the client exceeds the burst capacity, they receive a 503 Service Unavailable error immediately.

Why You Need a Robust Nginx Security Config

Applying rate limiting isn't just about preventing brute force; it's about availability. When you prevent ddos nginx configurations, you stop the noise before it hits your application layer.

We initially tried to implement this globally, but that broke our health checks and internal service-to-service communication. We learned the hard way that you need to exempt your internal network or specific monitoring endpoints.

Comparing Rate Limiting Strategies

StrategyProsCons
Nginx limit_reqExtremely fast, low CPU usageBasic IP-based tracking only
App-level MiddlewareContext-aware (user IDs, roles)Expensive, consumes app resources
Redis/Lua ScriptsFlexible, distributed stateAdds complexity and latency

If you need more granular control, like limiting by API key or session, you might need to look at Rate Limiting API Endpoints: Protecting Laravel Apps to see how to bridge the gap between Nginx and your application data.

Handling False Positives

One issue I've run into is blocking legitimate users behind NATs or corporate proxies. If a whole office shares one public IP, 5 requests per second might be too low.

I recommend setting a higher burst capacity for these types of endpoints. If you’re seeing too many legitimate users hitting the limit, consider using a whitelist or a broader rate limit for specific paths. Also, don't forget to log the limited requests. Checking your error.log for "limiting requests" entries will show you exactly who (or what) is being throttled.

Best Practices for Production

  1. Use nodelay carefully: Without it, Nginx will queue requests and introduce artificial latency. For APIs, nodelay is usually preferred so clients fail fast rather than hanging.
  2. Custom Error Pages: By default, Nginx returns a generic 503. Use limit_req_status 429; to return a "Too Many Requests" status code so your client-side code knows exactly why the request failed.
  3. Monitor with Prometheus: If you have the Nginx VTS module or similar, export these metrics to Grafana. Seeing a spike in 429s is often the first indicator of a botnet trying to brute force your login endpoint.

FAQ

Does Nginx rate limiting work on a per-server basis? Yes, limit_req_zone is local to the specific Nginx instance. If you have a load-balanced cluster, each server will track its own limits. For global limits, you'd need a centralized store like Redis.

How do I exclude my internal IP from rate limiting? Use the geo directive to define a whitelist and set the limit to 0 for those IPs. It’s a clean way to keep your internal tools running while protecting the public API.

Is it possible to rate limit by something other than IP? Nginx is limited to variables. Using $binary_remote_addr is standard, but you could theoretically use $http_x_api_key if your application passes that header to Nginx, though this requires careful configuration to avoid header spoofing.

I'm still refining our approach to handle distributed attacks, as IP-based limiting is easily bypassed by rotating proxies. Next time, I think I'll look into integrating Nginx with a WAF or a dedicated security plugin that can handle fingerprinting beyond just the IP address. For now, strict rate limiting is keeping our response times stable and our logs relatively clean.

Similar Posts