Rate-limiting is a very useful Nginx feature that is often misunderstood and misconfigured. We can use it to limit the number of HTTP requests a user can make in a given time. Request, which can be a simple GET request on the homepage of a website, or a POST request on the login form.

Traffic restrictions can be used for security purposes, such as slowing down brute force password cracking. By limiting the rate of incoming requests to the typical value of real users and identifying the target URL (through logs), it can also be used to defend against DDOS attacks. More often, this feature is used to protect upstream application servers from being overwhelmed by too many user requests at once.

This article will cover the basics and advanced configurations of Nginx’s traffic limiting. “Traffic limiting” also applies to Nginx Plus.

How does Nginx limit streams

Nginx’s “traffic restriction” uses the Leaky Bucket Algorithm, which is widely used in communications and packet-switched computer networks to deal with emergencies when bandwidth is limited. It’s like a bucket with water pouring out of the top and a leaky bucket at the bottom. If the rate of pouring water is greater than the rate of leaking water at the bottom of the bucket, the water in the bucket will overflow. Similarly, in terms of request processing, water represents requests from clients, buckets represent queues of requests waiting to be processed based on the first-in, first-out scheduling algorithm (FIFO), water leaking from the bottom of the bucket represents requests leaving the buffer to be processed by the server, and water spilling out of the bucket represents requests that are discarded and not processed.

Configure basic traffic limiting

Limit_req_zone and limit_req are configured as follows: limit_req_zone $binary_REMOTE_ADDR zone= myLIMIT :10m rate=10r/s;

server { location /login/ { limit_req zone=mylimit;

    proxy_pass http://my_upstream;
}
Copy the code

}

The limit_req_zone directive defines parameters related to traffic limiting, while the limit_req directive enables traffic limiting in the context in which it occurs (in the example, for all requests for “/login/”). The limit_req_zone directive is usually defined in an HTTP block, making it available in multiple contexts. It takes the following three parameters:

Key - Defines the request characteristics of the application restrictions. The Nginx variable remote_addr in the example takes up less space) Zone - defines the shared memory region used to store the state of each IP address and the frequency with which the request URL is restricted. Information stored in the shared memory area means that it can be shared between worker processes in Nginx. The definition is divided into two parts: the name of the area is identified by zone=keyword, and the size of the area is followed by a colon. The status information for 16,000 IP addresses takes about 1MB, so the area in the example can store 160,000 IP addresses. Rate - Defines the maximum request Rate. In the example, the rate cannot exceed 10 requests per second. Nginx actually tracks requests in milliseconds, so the rate limit is equivalent to one request per 100 milliseconds. Because "contingencies" (see next section) are not allowed, this means that requests that arrive within 100 milliseconds of the previous request will be rejected. When Nginx runs out of space to add new entries, it deletes old entries. If there is still not enough space to hold the new record, Nginx returns a 503 status code. In addition, to prevent memory from running out, Nginx deletes up to two unused entries for 60 seconds each time a new entry is created.Copy the code

The limit_req_zone directive sets parameters for traffic limits and shared memory regions, but does not actually limit request rates. So you need to apply traffic limits to a specific location or server block by adding the limit_req directive. In the above example, we put a traffic limit on the /login/ request.

Each IP address is now limited to 10 requests per second for /login/, or more precisely, the URL cannot be requested within 100 milliseconds of the previous request. Deal with emergency

What if we receive two requests within 100 milliseconds? For the second request, Nginx will return 503 status code to the client. This may not be what we want, because applications tend to be emergent by nature. Instead, we want to buffer any excess requests and then process them in a timely manner. Let’s update the configuration to use the burst parameter in limit_req:

location /login/ { limit_req zone=mylimit burst=20; proxy_pass http://my_upstream; }

The Burst parameter defines how many requests the client can make beyond the rate specified in the zone (the MyLIMIT zone in the example, which is limited to 10 requests per second, or one request per 100 milliseconds). Requests that arrive within 100 milliseconds of the previous request will be queued. We set the queue size to 20.

This means that if 21 requests are sent from a given IP address, Nginx will immediately send the first request to the upstream server farm and then queue the remaining 20 requests. The queued requests are forwarded every 100 milliseconds, and Nginx returns 503 to the client only if the number of incoming requests in the queue exceeds 20. Queuing without delay

Configuring burst parameters will make communication smoother, but it can be impractical because the configuration can make the site look slow. In the example above, the 20th packet in the queue takes two seconds to be forwarded, at which point the response returned to the client may no longer be useful. To resolve this situation, add the nodelay parameter after the burst parameter:

location /login/ { limit_req zone=mylimit burst=20 nodelay;

proxy_pass http://my_upstream;
Copy the code

}

With the nodelay parameter, Nginx will still assign positions in the queue based on the Burst parameter and apply the configured rate limit, rather than clearing the queue of requests waiting to be forwarded. Conversely, when a request arrives “too early,” Nginx will forward the request as soon as there is room in the queue. The location in the queue is marked as “taken” and will not be released for use by another request until some time later (in this case, 100 milliseconds later).

Suppose, as mentioned earlier, there are 20 slots in the queue and 21 requests from a given IP address arrive at the same time. Nginx immediately forwards the 21 requests and marks the 20 positions occupied in the queue, then releases one position every 100 milliseconds. If 25 requests arrive at the same time, Nginx will immediately forward 21 of them, mark 20 positions in the queue, and return a 503 status code to reject the remaining four requests.

Now suppose that 101 milliseconds after the first set of requests is forwarded, another 20 requests arrive at the same time. Only one place in the queue will be released, so Nginx forwards one request and returns a 503 status code to reject the other 19 requests. If 501 milliseconds have elapsed before 20 new requests arrive, five locations are released, so Nginx immediately forwards five requests and rejects the other 15.

The effect is equivalent to a “traffic limit” of 10 requests per second. The nodelay parameter is useful if you want to enforce “traffic limiting” without limiting the allowed interval between two requests.

Note: For most deployments, we recommend configuring the limit_req directive with the BURST and nodelay parameters. Example of Advanced Configuration

By combining basic “traffic limiting” with other Nginx features, we can achieve more fine-grained traffic limiting.

White list

The following example shows how to enforce “traffic restriction” on any request that is not whitelisted:

geo $limit { default 1; 10.0.0.0/8 0; 192.168.0.0/64 0; }

map
l i m i t limit
limit_key { 0 “”; 1 $binary_remote_addr; }

limit_req_zone $limit_key zone=req_zone:10m rate=5r/s;

server { location / { limit_req zone=req_zone burst=10 nodelay;

#... }Copy the code

}

This example uses both the GEO and Map directives. The GEO block assigns a value of 0 to the $LIMIT variable corresponding to an IP address that is in the whitelist and a value of 1 to any other IP address that is not in the whitelist. We then use a map to turn these values into keys, as follows:

ifCopy the code

The limit_key variable will be assigned an empty string if the value of the variable is

The $limit_key variable of an IP address in the whitelist is assigned to an empty string. The $limit_key variable of an IP address that is not in the whitelist is assigned to the IP address of the client. If the first parameter after limit_req_zone is an empty string, traffic restriction is not applied. Therefore, IP addresses in the whitelist (10.0.0.0/8 and 192.168.0.0/24 network segments) are not restricted. All other IP addresses are limited to five requests per second.Copy the code

The limit_req directive applies the limit to the location block of /, allowing bursts of up to 10 packets above the configured limit and without delayed forwarding. Location contains multiple limit_req directives

You can configure multiple limit_req directives in a single Location block. When all the restrictions that conform to a given request are applied, that means the most stringent one will be adopted. For example, if multiple instructions have latency, the longest one will be used. Similarly, requests are rejected by partial directives, even if other directives are allowed through.

Extend the previous example of applying Traffic restriction to IP addresses in the whitelist:

http { # …

limit_req_zone $limit_key zone=req_zone:10m rate=5r/s; limit_req_zone $binary_remote_addr zone=req_zone_wl:10m rate=15r/s; server { # ... location / { limit_req zone=req_zone burst=10 nodelay; limit_req zone=req_zone_wl burst=20 nodelay; #... }}Copy the code

}

IP addresses in the whitelist do not match the first traffic restriction, but the second req_zone_WL and are restricted to 15 requests per second. The IP address that is not in the whitelist can match the two limits, so the one with the stronger limit is applied: 5 requests per second. Configuring Related Functions

By default, Nginx logs requests that are delayed or discarded due to traffic restrictions, as follows:

2015/06/13 04:20:00 [error] 120315#0: * 20086 memory requests, excess: 1.000 by zone “mylimit”, client: 192.168.1.2, server: nginx.com, request: “GET/HTTP/1.0”, host: “nginx.com”

Fields contained in a log entry:

Excess - Specifies the number of requests that are exceeded per millisecond. Zone - Defines the zone where traffic restriction is implemented. Client - IP address server that initiates the request Request - Indicates the actual HTTP request sent by the client. Host - Indicates the value of host in the HTTP headerCopy the code

By default, Nginx records rejected requests at the error level, as shown in [error] in the above example (Ngin records delayed requests at a lower level, typically info). To change the logging level of Nginx, use the limit_req_log_level command. Here, we set the logging level for rejected requests to WARN:

location /login/ { limit_req zone=mylimit burst=20 nodelay; limit_req_log_level warn;

proxy_pass http://my_upstream;
Copy the code

}

Error code sent to the client

Generally, when a client exceeds the configured traffic limit, the Nginx response status code is 503(Service Temporarily Unavailable). You can use the limit_req_status directive to set this to another status code (such as the 444 status code below):

location /login/ { limit_req zone=mylimit burst=20 nodelay; limit_req_status 444; }

Specify location to reject all requests

If you want to deny all requests to a specific URL, instead of just limiting the rate, you can simply configure the deny all directive in the location block:

location /foo.php { deny all; }

conclusion

You’ve already covered many of the capabilities of Nginx and Nginx Plus for “traffic limiting,” including setting request rates for different LOations of HTTP requests and configuring burst and nodelay parameters for “traffic limiting.” It also covers advanced configurations for applying different “traffic limiting” to the whitelist and blacklist of client IP addresses and describes how to log rejected and delayed requests.

END