Site icon GetPageSpeed

NGINX Cookie Limit Module: Rate Limiting Guide

NGINX Cookie Limit Module: Rate Limiting Guide

Attackers frequently exploit HTTP cookies to bypass traditional rate limiting. A single malicious IP can rotate or forge cookies to appear as multiple legitimate users, overwhelming your server while evading standard defenses. The NGINX cookie limit module (ngx_cookie_limit_req_module) solves this problem by combining cookie-aware rate limiting with Redis-backed forged cookie detection.

In this guide, you will learn how to install, configure, and deploy the NGINX cookie limit module to protect your server from cookie-based abuse.

The ngx_cookie_limit_req_module is a third-party NGINX module that provides two layers of protection against cookie-based attacks:

  1. Cookie rate limiting — Applies a leaky-bucket rate limit specifically to requests that carry cookies, similar to NGINX’s native limit_req but scoped to cookie-bearing traffic.
  2. Forged cookie detection — Uses Redis HyperLogLog to track how many unique cookie values each IP address generates. When an IP exceeds the configured threshold, it is blocked entirely.

This combination makes the NGINX cookie limit module effective against bots and scrapers that rotate session cookies to bypass per-session rate limits.

How Does It Work?

The module operates in the NGINX preaccess phase and uses Redis as a shared state store across all worker processes.

Like NGINX’s built-in limit_req, this module uses a leaky-bucket algorithm to control the rate of cookie-bearing requests. You define a rate (e.g., 30r/s) and an optional burst size. Requests that exceed the rate are either delayed or rejected.

This is the module’s unique feature. For each incoming request that carries a cookie:

  1. The module computes an MD5 hash of the cookie value.
  2. It adds the hash to a Redis HyperLogLog data structure keyed by the client’s IP address (PFADD).
  3. It checks the cardinality of the HyperLogLog (PFCOUNT) — the number of unique cookie values from that IP.
  4. If the count exceeds cookie_max, the IP is blocked for block_second seconds by setting a Redis key with a TTL.

HyperLogLog is a probabilistic data structure that uses minimal memory (approximately 12 KB per IP) while providing accurate cardinality estimates. This makes the module efficient even under high traffic loads.

Why Not Use Native NGINX Rate Limiting?

NGINX’s built-in limit_req module is excellent for general-purpose rate limiting, but it has limitations when dealing with cookie-based attacks:

Feature Native limit_req Cookie Limit Module
Rate limiting by IP Yes Yes
Rate limiting by cookie Yes (via $cookie_*) Yes
Cross-worker state Shared memory only Redis (persistent)
Forged cookie detection No Yes (HyperLogLog)
Survives NGINX restart No Yes (Redis)
Blocks IPs with too many cookies No Yes

If you only need basic per-IP rate limiting, the native limit_req directive is sufficient. However, if you face attackers who rotate cookies or forge session identifiers, the NGINX cookie limit module provides the additional protection you need.

Prerequisites

The module requires a running Redis-compatible server. Install Redis or Valkey (a Redis-compatible alternative available on newer distributions):

# RHEL 9 and older / Debian / Ubuntu
sudo dnf install redis
sudo systemctl enable --now redis

# Rocky Linux 10 / Fedora 42+
sudo dnf install valkey
sudo systemctl enable --now valkey

Verify the server is running:

redis-cli ping

You should see PONG in response.

RHEL, CentOS, AlmaLinux, Rocky Linux

Install the GetPageSpeed repository and the module package:

sudo dnf install https://extras.getpagespeed.com/release-latest.rpm
sudo dnf install nginx-module-cookie-limit

Load the module by adding the following directive at the top of /etc/nginx/nginx.conf, before any http block:

load_module modules/ngx_http_cookie_limit_req_module.so;

SELinux Configuration

On RHEL-based systems with SELinux enabled (the default), NGINX is not allowed to make network connections to Redis. You must enable the httpd_can_network_connect boolean:

sudo setsebool -P httpd_can_network_connect 1

Without this, you will see redis connection error: Permission denied 127.0.0.1 in the NGINX error log.

Debian and Ubuntu

First, set up the GetPageSpeed APT repository, then install the module:

sudo apt-get update
sudo apt-get install nginx-module-cookie-limit

On Debian/Ubuntu, the package handles module loading automatically. No load_module directive is needed.

You can find detailed package information on the RPM module page or the APT module page.

Configuration Directives

The module provides four directives. All directive names and their parameters are verified against the module source code.

Defines a shared memory zone for cookie rate limiting and configures the Redis connection for forged cookie detection.

Syntax:  cookie_limit_req_zone key zone=name:size rate=rate redis=ip block_second=seconds cookie_max=number
Default: —
Context: http

Parameters:

Enables cookie rate limiting in a specific context, referencing a zone defined by cookie_limit_req_zone.

Syntax:  cookie_limit_req zone=name [burst=number] [nodelay]
Default: —
Context: http, server, location, if

Parameters:

Sets the severity level for log messages when the module rejects or delays requests.

Syntax:  cookie_limit_req_log_level info | notice | warn | error
Default: cookie_limit_req_log_level error;
Context: http, server, location

When a request is rejected, the module logs at the configured level. Delayed requests are logged one level below the configured level. For example, if you set cookie_limit_req_log_level notice, rejections are logged at notice and delays at info.

Sets the HTTP status code returned to clients when their requests are rejected.

Syntax:  cookie_limit_req_status code
Default: cookie_limit_req_status 503;
Context: http, server, location, if

The status code must be between 400 and 599. Common choices include:

Configuration Examples

Basic Setup

This configuration limits cookie-bearing requests to 10 per second per cookie value, with a burst of 20. IPs that generate more than 5 unique cookies are blocked for 5 minutes:

http {
    cookie_limit_req_zone $http_cookie zone=cookies:10m rate=10r/s
                          redis=127.0.0.1 block_second=300 cookie_max=5;

    server {
        listen 80;
        server_name example.com;

        cookie_limit_req zone=cookies burst=20 nodelay;
        cookie_limit_req_status 429;

        location / {
            proxy_pass http://backend;
        }
    }
}

Strict Anti-Bot Configuration

For high-security applications where cookie forgery is a significant threat, use a lower cookie_max and a longer block duration:

http {
    cookie_limit_req_zone $http_cookie zone=strict:20m rate=5r/s
                          redis=127.0.0.1 block_second=3600 cookie_max=3;

    server {
        listen 80;
        server_name secure.example.com;

        cookie_limit_req zone=strict burst=10;
        cookie_limit_req_status 403;
        cookie_limit_req_log_level warn;

        location / {
            proxy_pass http://backend;
        }
    }
}

In this example, any IP that generates more than 3 unique cookie values is blocked for one hour. The rate is limited to 5 requests per second with a burst of 10 (delayed, not immediate).

Protecting Login Endpoints

Apply cookie rate limiting specifically to authentication endpoints where cookie abuse is most common:

http {
    cookie_limit_req_zone $http_cookie zone=auth:5m rate=3r/s
                          redis=127.0.0.1 block_second=600 cookie_max=5;

    server {
        listen 80;
        server_name example.com;

        location /login {
            cookie_limit_req zone=auth burst=5 nodelay;
            cookie_limit_req_status 429;
            proxy_pass http://backend;
        }

        location / {
            proxy_pass http://backend;
        }
    }
}

Redis Configuration for Production

Securing Redis

Since the module connects to Redis on port 6379, make sure Redis is properly secured:

  1. Bind to localhost only — In /etc/redis/redis.conf or /etc/redis.conf:
bind 127.0.0.1
  1. Disable protected mode warning (if bound to localhost):
protected-mode yes
  1. Set a password (optional but recommended if Redis is on a separate host):
requirepass your_strong_password_here

Note that the module does not support Redis authentication. Keep Redis bound to localhost or use firewall rules to restrict access.

Valkey Compatibility

On newer distributions like Rocky Linux 10 and Fedora 42+, the redis package has been replaced by Valkey, a fully compatible Redis fork. The module works seamlessly with Valkey — no configuration changes are needed. Simply install valkey instead of redis and the module connects to it on the same default port 6379.

Redis Memory Management

The module uses Redis HyperLogLog structures and key-value pairs with TTLs. Memory usage depends on your traffic volume:

For most deployments, allocating 64-128 MB to Redis is sufficient. Set a memory limit in Redis configuration:

maxmemory 128mb
maxmemory-policy allkeys-lru

Testing the Configuration

After configuring the module, verify the configuration syntax:

nginx -t

If the test passes, reload NGINX:

sudo systemctl reload nginx

Verifying the Module Is Loaded

Confirm the module file exists:

ls /usr/lib64/nginx/modules/ngx_http_cookie_limit_req_module.so

Then test that NGINX accepts the configuration with module-specific directives — a successful nginx -t proves the module is loaded and working.

Testing Rate Limiting

The module exempts requests from 127.0.0.1 from rate-limit enforcement (by design, to avoid blocking local health checks). To test rate limiting, use the server’s actual IP address or hostname:

for i in $(seq 1 50); do
    curl -s -o /dev/null -w "%{http_code}\n" \
         -b "session=test_cookie_value" \
         http://your-server-ip/
done

You should see 200 responses initially, followed by 429 (or your configured status code) once the rate limit is exceeded. With burst=20 nodelay, the first 21 requests pass and the rest are rejected.

Forged cookie detection (cookie_max) applies to all client IPs including localhost. Simulate an attacker rotating cookies:

for i in $(seq 1 20); do
    curl -s -o /dev/null -w "Cookie $i: %{http_code}\n" \
         -b "session=forged_cookie_$i" \
         http://localhost/
done

After the number of unique cookies from your IP exceeds cookie_max, subsequent requests should return the configured error status code.

Monitoring Redis State

Inspect the Redis keys created by the module:

redis-cli keys '*'

You should see HyperLogLog keys prefixed with the client IP (e.g., [192.168.1.100]Independent_IP) and any blocked IP/cookie keys.

To check how many unique cookies an IP has generated:

redis-cli PFCOUNT "[192.168.1.100]Independent_IP"

Performance Considerations

Shared Memory Zone Sizing

The shared memory zone stores rate-limiting state in a red-black tree. Each entry uses approximately 128 bytes. A 10 MB zone can hold around 80,000 entries, which is sufficient for most deployments.

If you see errors like could not allocate node in your NGINX error log, increase the zone size:

cookie_limit_req_zone $http_cookie zone=cookies:20m rate=10r/s
                      redis=127.0.0.1 block_second=300 cookie_max=5;

Redis Latency

Each request with a cookie triggers multiple Redis commands (PFADD, PFCOUNT, and potentially GET/SETEX). The module uses a 1.5-second connection timeout and connects on port 6379.

To minimize latency:

Graceful Degradation

If the Redis connection fails, the module logs an error and allows the request through (NGX_DECLINED). This means your server continues to function even if Redis is temporarily unavailable — rate limiting simply becomes inactive until the connection is restored.

Security Best Practices

Choose Appropriate Thresholds

Combine with Other Security Modules

The module works well alongside other NGINX security tools:

Monitor Your Logs

Watch for rate-limiting and blocking messages in the NGINX error log:

tail -f /var/log/nginx/error.log | grep -i "cookie"

The module logs messages like:

Troubleshooting

“Unknown directive” Error

If nginx -t reports an unknown directive error, the module is not loaded. Verify:

  1. The module file exists at /usr/lib64/nginx/modules/ngx_http_cookie_limit_req_module.so.
  2. The load_module directive is at the top of nginx.conf, outside any block.
  3. The module version matches your NGINX version.

Redis Connection Errors

If you see redis connection error in the NGINX error log:

  1. Verify Redis is running: systemctl status redis (or systemctl status valkey on Rocky Linux 10+).
  2. Check Redis is listening on 127.0.0.1:6379: redis-cli ping.
  3. Check firewall rules if Redis is on a different host.
  4. On RHEL-based systems, ensure SELinux allows network connections: sudo setsebool -P httpd_can_network_connect 1.

Remember that the module connects to port 6379 only — this port number is hardcoded and cannot be changed via configuration.

Requests Not Being Limited

If rate limiting appears inactive:

  1. Confirm the request carries a Cookie header — the module only processes requests with cookies.
  2. Check that the cookie_limit_req directive is in the correct context (http, server, or location).
  3. Verify Redis is accepting writes: redis-cli SET test ok && redis-cli GET test.
  4. Check that cookie_limit_req_zone is in the http context, not inside a server or location block.
  5. Make sure you are not testing from 127.0.0.1 — the module exempts localhost from rate-limit enforcement. Use the server’s real IP address for testing.

High Redis Memory Usage

If Redis memory grows unexpectedly:

  1. Check for leaked keys without TTLs: redis-cli --scan | head -20.
  2. HyperLogLog keys ([IP]Independent_IP) do not expire automatically. They are removed only when the associated IP is blocked (the module calls DEL on the HyperLogLog after blocking).
  3. Consider adding a maxmemory policy to Redis as described in the Redis Configuration section.

Module Limitations

Before deploying the module, be aware of its current limitations:

Conclusion

The NGINX cookie limit module provides effective protection against cookie-based attacks by combining traditional rate limiting with Redis-backed forged cookie detection. It is particularly useful for servers that face automated attacks where bots rotate session cookies to bypass standard rate limits.

For straightforward rate limiting without Redis, consider NGINX’s built-in rate limiting. For more advanced bot protection, combine the cookie limit module with testcookie or a JavaScript challenge.

The module source code is available on GitHub.

D

Danila Vershinin

Founder & Lead Engineer

NGINX configuration and optimizationLinux system administrationWeb performance engineering

10+ years NGINX experience • Maintainer of GetPageSpeed RPM repository • Contributor to open-source NGINX modules

Exit mobile version