Implement OpenResty rate limiting and DDoS protection with advanced Lua rules

Advanced 45 min Jun 13, 2026
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up comprehensive rate limiting and DDoS protection for OpenResty using nginx directives, Redis-backed Lua middleware, and advanced security rules with monitoring and alerting.

Prerequisites

  • Root access to server
  • Basic NGINX/OpenResty knowledge
  • Redis server available
  • Understanding of HTTP and rate limiting concepts

What this solves

OpenResty combines NGINX with embedded Lua scripting to create powerful rate limiting and DDoS protection systems. This tutorial shows you how to implement multi-layered defense using nginx.conf directives for basic protection, Redis-backed Lua scripts for sophisticated rate limiting, and custom middleware for advanced threat detection and mitigation.

Prerequisites

You'll need a server with root access and basic familiarity with NGINX configuration. Redis will be used for distributed rate limiting state management. We'll also integrate with existing monitoring systems for security event tracking.

Step-by-step installation

Install OpenResty and dependencies

OpenResty provides NGINX with Lua scripting capabilities built-in. We'll also install Redis for state management and development tools.

sudo apt update
wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add -
echo "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/openresty.list
sudo apt update
sudo apt install -y openresty redis-server lua-cjson lua-resty-redis
sudo dnf update -y
sudo dnf install -y wget
wget https://openresty.org/package/centos/openresty.repo
sudo mv openresty.repo /etc/yum.repos.d/
sudo dnf install -y openresty redis lua-cjson

Configure Redis for rate limiting state

Redis will store rate limiting counters and DDoS detection state across OpenResty worker processes. Configure it for persistence and security.

# Basic security and persistence
bind 127.0.0.1
port 6379
requirepass OpenResty_RateLimit_2024!
save 900 1
save 300 10
save 60 10000

Memory optimization for rate limiting

maxmemory 256mb maxmemory-policy allkeys-lru

Faster key expiration

hz 50
sudo systemctl enable --now redis-server
sudo systemctl status redis-server

Create directory structure for Lua scripts

Organize Lua scripts in a dedicated directory with proper permissions for the OpenResty worker processes.

sudo mkdir -p /etc/openresty/lua/ratelimit
sudo mkdir -p /var/log/openresty/security
sudo chown -R nobody:nogroup /etc/openresty/lua
sudo chown -R www-data:www-data /var/log/openresty
sudo chmod 755 /etc/openresty/lua/ratelimit

Create Redis connection module

This Lua module handles Redis connections with connection pooling and error handling for rate limiting operations.

local redis = require "resty.redis"
local cjson = require "cjson"

local _M = {}

function _M.new()
    local red = redis:new()
    red:set_timeout(1000) -- 1 second
    
    local ok, err = red:connect("127.0.0.1", 6379)
    if not ok then
        ngx.log(ngx.ERR, "failed to connect to redis: ", err)
        return nil, err
    end
    
    -- Authenticate
    local res, err = red:auth("OpenResty_RateLimit_2024!")
    if not res then
        ngx.log(ngx.ERR, "failed to authenticate with redis: ", err)
        return nil, err
    end
    
    return red
end

function _M.close(red)
    if red then
        local ok, err = red:set_keepalive(10000, 100)
        if not ok then
            ngx.log(ngx.ERR, "failed to set keepalive: ", err)
        end
    end
end

function _M.increment_counter(key, window, limit)
    local red, err = _M.new()
    if not red then
        return nil, err
    end
    
    -- Use Redis pipeline for atomic operations
    red:init_pipeline()
    red:incr(key)
    red:expire(key, window)
    
    local results, err = red:commit_pipeline()
    if not results then
        _M.close(red)
        return nil, err
    end
    
    local current_count = results[1]
    _M.close(red)
    
    return tonumber(current_count)
end

return _M

Create basic rate limiting module

This module implements sliding window rate limiting with different limits for different request types and client classes.

local redis_client = require "ratelimit.redis_client"
local cjson = require "cjson"

local _M = {}

-- Rate limit configurations
local rate_limits = {
    default = { requests = 100, window = 60 },
    api = { requests = 1000, window = 60 },
    login = { requests = 5, window = 300 },
    upload = { requests = 10, window = 60 }
}

local function get_client_ip()
    local headers = ngx.var.http_x_forwarded_for
    if headers then
        local ip = headers:match("([^,]+)")
        return ip:gsub("%s+", "")
    end
    return ngx.var.remote_addr
end

local function get_rate_limit_config(uri)
    if string.match(uri, "/api/") then
        return rate_limits.api
    elseif string.match(uri, "/login") or string.match(uri, "/auth") then
        return rate_limits.login
    elseif string.match(uri, "/upload") then
        return rate_limits.upload
    else
        return rate_limits.default
    end
end

function _M.check_rate_limit()
    local client_ip = get_client_ip()
    local uri = ngx.var.uri
    local config = get_rate_limit_config(uri)
    
    local key = "rate_limit:" .. client_ip .. ":" .. uri
    local current_count, err = redis_client.increment_counter(key, config.window, config.requests)
    
    if not current_count then
        ngx.log(ngx.ERR, "Rate limiting error: ", err)
        -- Fail open - allow request if Redis is down
        return true
    end
    
    -- Log rate limiting decisions
    local log_data = {
        timestamp = ngx.time(),
        client_ip = client_ip,
        uri = uri,
        current_count = current_count,
        limit = config.requests,
        window = config.window,
        allowed = current_count <= config.requests
    }
    
    local log_file = io.open("/var/log/openresty/security/rate_limit.log", "a")
    if log_file then
        log_file:write(cjson.encode(log_data) .. "\n")
        log_file:close()
    end
    
    if current_count > config.requests then
        -- Set rate limit headers
        ngx.header["X-RateLimit-Limit"] = config.requests
        ngx.header["X-RateLimit-Remaining"] = 0
        ngx.header["X-RateLimit-Reset"] = ngx.time() + config.window
        
        ngx.status = 429
        ngx.header.content_type = "application/json"
        ngx.say(cjson.encode({
            error = "Rate limit exceeded",
            limit = config.requests,
            window = config.window,
            retry_after = config.window
        }))
        ngx.exit(429)
    end
    
    -- Set informational headers
    ngx.header["X-RateLimit-Limit"] = config.requests
    ngx.header["X-RateLimit-Remaining"] = math.max(0, config.requests - current_count)
    
    return true
end

return _M

Create DDoS detection module

This advanced module detects DDoS patterns by analyzing request patterns, response times, and implementing progressive penalties.

local redis_client = require "ratelimit.redis_client"
local cjson = require "cjson"

local _M = {}

-- DDoS detection thresholds
local ddos_config = {
    burst_threshold = 50,      -- requests per 10 seconds
    burst_window = 10,
    penalty_multiplier = 2,    -- increase penalty each time
    max_penalty_time = 3600,   -- 1 hour max penalty
    suspicious_ua_patterns = {
        "curl", "wget", "python", "bot", "crawler", "scanner"
    }
}

local function get_client_ip()
    local headers = ngx.var.http_x_forwarded_for
    if headers then
        local ip = headers:match("([^,]+)")
        return ip:gsub("%s+", "")
    end
    return ngx.var.remote_addr
end

local function is_suspicious_user_agent(ua)
    if not ua then return true end
    
    ua = ua:lower()
    for _, pattern in ipairs(ddos_config.suspicious_ua_patterns) do
        if string.find(ua, pattern) then
            return true
        end
    end
    return false
end

local function calculate_request_score()
    local score = 1
    
    -- Check User-Agent
    local user_agent = ngx.var.http_user_agent
    if is_suspicious_user_agent(user_agent) then
        score = score + 3
    end
    
    -- Check for missing common headers
    if not ngx.var.http_accept then score = score + 2 end
    if not ngx.var.http_accept_language then score = score + 1 end
    
    -- Check request method
    if ngx.var.request_method == "POST" then
        score = score + 1
    end
    
    -- Check for rapid requests (implemented via Redis timing)
    local client_ip = get_client_ip()
    local timing_key = "timing:" .. client_ip
    local red, err = redis_client.new()
    
    if red then
        local last_request = red:get(timing_key)
        local current_time = ngx.time()
        
        if last_request and last_request ~= ngx.null then
            local time_diff = current_time - tonumber(last_request)
            if time_diff < 1 then  -- Less than 1 second between requests
                score = score + 5
            end
        end
        
        red:setex(timing_key, 60, current_time)
        redis_client.close(red)
    end
    
    return score
end

function _M.check_ddos_pattern()
    local client_ip = get_client_ip()
    local current_time = ngx.time()
    
    -- Check if client is currently penalized
    local penalty_key = "penalty:" .. client_ip
    local red, err = redis_client.new()
    
    if not red then
        ngx.log(ngx.ERR, "DDoS detector Redis connection failed: ", err)
        return true  -- Fail open
    end
    
    local penalty_end = red:get(penalty_key)
    if penalty_end and penalty_end ~= ngx.null then
        if current_time < tonumber(penalty_end) then
            redis_client.close(red)
            
            -- Log penalty enforcement
            local log_data = {
                timestamp = current_time,
                client_ip = client_ip,
                action = "penalty_enforced",
                penalty_end = penalty_end,
                uri = ngx.var.uri
            }
            
            local log_file = io.open("/var/log/openresty/security/ddos.log", "a")
            if log_file then
                log_file:write(cjson.encode(log_data) .. "\n")
                log_file:close()
            end
            
            ngx.status = 403
            ngx.header.content_type = "application/json"
            ngx.say(cjson.encode({
                error = "Access temporarily blocked due to suspicious activity",
                retry_after = tonumber(penalty_end) - current_time
            }))
            ngx.exit(403)
        else
            -- Penalty expired, remove it
            red:del(penalty_key)
        end
    end
    
    -- Calculate request score
    local request_score = calculate_request_score()
    
    -- Track burst requests
    local burst_key = "burst:" .. client_ip
    local burst_count = redis_client.increment_counter(burst_key, ddos_config.burst_window, ddos_config.burst_threshold)
    
    if not burst_count then
        redis_client.close(red)
        return true  -- Fail open
    end
    
    -- Apply scoring to burst detection
    local weighted_burst = burst_count * (request_score / 2)
    
    if weighted_burst > ddos_config.burst_threshold then
        -- Get current penalty count
        local penalty_count_key = "penalty_count:" .. client_ip
        local current_penalties = red:get(penalty_count_key)
        current_penalties = (current_penalties and current_penalties ~= ngx.null) and tonumber(current_penalties) or 0
        
        -- Calculate penalty duration
        local penalty_duration = math.min(
            300 * math.pow(ddos_config.penalty_multiplier, current_penalties),
            ddos_config.max_penalty_time
        )
        
        -- Set penalty
        red:setex(penalty_key, penalty_duration, current_time + penalty_duration)
        red:incr(penalty_count_key)
        red:expire(penalty_count_key, 86400)  -- Reset penalty count after 24 hours
        
        -- Log DDoS detection
        local log_data = {
            timestamp = current_time,
            client_ip = client_ip,
            action = "ddos_detected",
            burst_count = burst_count,
            request_score = request_score,
            weighted_burst = weighted_burst,
            penalty_duration = penalty_duration,
            penalty_count = current_penalties + 1,
            user_agent = ngx.var.http_user_agent,
            uri = ngx.var.uri
        }
        
        local log_file = io.open("/var/log/openresty/security/ddos.log", "a")
        if log_file then
            log_file:write(cjson.encode(log_data) .. "\n")
            log_file:close()
        end
        
        redis_client.close(red)
        
        ngx.status = 403
        ngx.header.content_type = "application/json"
        ngx.say(cjson.encode({
            error = "Suspicious activity detected - access blocked",
            penalty_duration = penalty_duration
        }))
        ngx.exit(403)
    end
    
    redis_client.close(red)
    return true
end

return _M

Configure OpenResty with rate limiting

Set up the main OpenResty configuration with nginx.conf directives for basic protection and Lua integration.

worker_processes auto;
error_log /var/log/openresty/error.log warn;
worker_rlimit_nofile 65535;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

http {
    include mime.types;
    default_type application/octet-stream;
    
    # Basic security headers
    add_header X-Frame-Options SAMEORIGIN always;
    add_header X-Content-Type-Options nosniff always;
    add_header X-XSS-Protection "1; mode=block" always;
    
    # Rate limiting zones (nginx level)
    limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
    limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
    
    # Connection limiting
    limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m;
    
    # Lua package path
    lua_package_path "/etc/openresty/lua/?.lua;;";
    
    # Shared dictionaries for caching
    lua_shared_dict rate_limit_cache 10m;
    lua_shared_dict ddos_cache 10m;
    
    # Logging format
    log_format security_log '$remote_addr - $remote_user [$time_local] '
                           '"$request" $status $bytes_sent '
                           '"$http_referer" "$http_user_agent" '
                           '$request_time $upstream_response_time '
                           '$http_x_forwarded_for';
    
    # Main server block
    server {
        listen 80 default_server;
        listen [::]:80 default_server;
        server_name _;
        
        access_log /var/log/openresty/access.log security_log;
        
        # Basic nginx rate limiting (first layer)
        limit_req zone=general burst=20 nodelay;
        limit_conn conn_limit_per_ip 20;
        
        # Block common attack patterns
        location ~ \.(env|git|svn)$ {
            deny all;
            return 404;
        }
        
        # API endpoints with advanced protection
        location /api/ {
            limit_req zone=api burst=50 nodelay;
            
            # Lua-based rate limiting and DDoS detection
            access_by_lua_block {
                local ddos_detector = require "ratelimit.ddos_detector"
                local basic_limiter = require "ratelimit.basic_limiter"
                
                -- Run DDoS detection first
                ddos_detector.check_ddos_pattern()
                
                -- Then apply rate limiting
                basic_limiter.check_rate_limit()
            }
            
            # Your API backend
            proxy_pass http://127.0.0.1:8080;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
        
        # Login endpoints with stricter limits
        location ~ ^/(login|auth|signin) {
            limit_req zone=login burst=3 nodelay;
            
            access_by_lua_block {
                local ddos_detector = require "ratelimit.ddos_detector"
                local basic_limiter = require "ratelimit.basic_limiter"
                
                ddos_detector.check_ddos_pattern()
                basic_limiter.check_rate_limit()
            }
            
            proxy_pass http://127.0.0.1:8080;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
        
        # Default location with basic protection
        location / {
            access_by_lua_block {
                local ddos_detector = require "ratelimit.ddos_detector"
                local basic_limiter = require "ratelimit.basic_limiter"
                
                ddos_detector.check_ddos_pattern()
                basic_limiter.check_rate_limit()
            }
            
            proxy_pass http://127.0.0.1:8080;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
        
        # Health check endpoint (no rate limiting)
        location /health {
            access_log off;
            return 200 "OK";
            add_header Content-Type text/plain;
        }
    }
}

Create monitoring script

This script monitors security events and generates alerts for suspicious activity patterns.

#!/bin/bash

Security monitoring script for OpenResty rate limiting

LOG_DIR="/var/log/openresty/security" RATE_LIMIT_LOG="$LOG_DIR/rate_limit.log" DDOS_LOG="$LOG_DIR/ddos.log" ALERT_THRESHOLD=10 EMAIL_ALERT="admin@example.com"

Create summary report

generate_security_report() { local report_file="$LOG_DIR/security_summary_$(date +%Y%m%d_%H%M).txt" echo "OpenResty Security Report - $(date)" > "$report_file" echo "========================================" >> "$report_file" echo "" >> "$report_file" # Rate limiting stats from last hour echo "Rate Limiting Events (Last Hour):" >> "$report_file" if [[ -f "$RATE_LIMIT_LOG" ]]; then tail -n 1000 "$RATE_LIMIT_LOG" | \ jq -r 'select(.timestamp > (now - 3600) and .allowed == false) | .client_ip' | \ sort | uniq -c | sort -nr | head -20 >> "$report_file" fi echo "" >> "$report_file" # DDoS detection stats echo "DDoS Detection Events (Last Hour):" >> "$report_file" if [[ -f "$DDOS_LOG" ]]; then tail -n 1000 "$DDOS_LOG" | \ jq -r 'select(.timestamp > (now - 3600)) | "\(.client_ip) - \(.action)"' | \ sort | uniq -c | sort -nr | head -20 >> "$report_file" fi echo "Report saved to: $report_file" }

Check for high-volume attacks

check_attack_patterns() { if [[ -f "$DDOS_LOG" ]]; then local recent_attacks=$(tail -n 100 "$DDOS_LOG" | \ jq -r 'select(.timestamp > (now - 300)) | .client_ip' | \ wc -l) if [[ $recent_attacks -gt $ALERT_THRESHOLD ]]; then echo "HIGH ALERT: $recent_attacks DDoS events in last 5 minutes" | \ mail -s "OpenResty DDoS Alert - $(hostname)" "$EMAIL_ALERT" fi fi }

Update Redis statistics

update_redis_stats() { redis-cli -a "OpenResty_RateLimit_2024!" <Main execution case "${1:-all}" in "report") generate_security_report ;; "check") check_attack_patterns ;; "stats") update_redis_stats ;; "all") check_attack_patterns update_redis_stats ;; *) echo "Usage: $0 {report|check|stats|all}" exit 1 ;; esac
sudo chmod +x /etc/openresty/lua/security_monitor.sh

Set up log rotation

Configure logrotate to manage security log files and prevent disk space issues.

/var/log/openresty/security/*.log {
    daily
    missingok
    rotate 30
    compress
    delaycompress
    notifempty
    create 644 www-data www-data
    postrotate
        /usr/local/openresty/nginx/sbin/nginx -s reload
    endscript
}

/var/log/openresty/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 644 www-data www-data
    sharedscripts
    postrotate
        /usr/local/openresty/nginx/sbin/nginx -s reload
    endscript
}

Create systemd service

Set up OpenResty as a systemd service with proper dependencies and security settings.

[Unit]
Description=OpenResty Web Server
After=network.target remote-fs.target nss-lookup.target redis-server.service
Requires=redis-server.service

[Service]
Type=forking
PIDFile=/usr/local/openresty/nginx/logs/nginx.pid
ExecStartPre=/usr/local/openresty/nginx/sbin/nginx -t
ExecStart=/usr/local/openresty/nginx/sbin/nginx
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
KillMode=mixed
KillSignal=SIGTERM
PrivateTmp=true
LimitNOFILE=65535
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable openresty
sudo systemctl start openresty

Set up monitoring cron jobs

Schedule regular security monitoring and report generation.

sudo crontab -e

Add these cron jobs for automated monitoring:

# Check for attacks every 5 minutes
/5    * /etc/openresty/lua/security_monitor.sh check

Generate security reports every hour

0 /etc/openresty/lua/security_monitor.sh report

Update Redis stats every 10 minutes

/10 * /etc/openresty/lua/security_monitor.sh stats

Clean old penalty records daily

0 2 redis-cli -a "OpenResty_RateLimit_2024!" --scan --pattern "penalty:" | xargs -r redis-cli -a "OpenResty_RateLimit_2024!" del

Test rate limiting and DDoS protection

Test basic rate limiting

Verify that rate limiting works correctly for different endpoints.

# Test general rate limiting
for i in {1..15}; do
    curl -w "%{http_code} - %{time_total}s\n" \
    -H "X-Forwarded-For: 192.168.1.100" \
    http://localhost/ -o /dev/null -s
    sleep 0.1
done

Test API rate limiting

for i in {1..25}; do curl -w "%{http_code} - %{time_total}s\n" \ -H "X-Forwarded-For: 192.168.1.101" \ http://localhost/api/test -o /dev/null -s sleep 0.05 done

Test DDoS detection

Simulate suspicious traffic patterns to verify DDoS detection works.

# Simulate bot-like behavior
for i in {1..60}; do
    curl -w "%{http_code}\n" \
    -H "User-Agent: python-requests/2.28.1" \
    -H "X-Forwarded-For: 10.0.0.50" \
    http://localhost/api/data -o /dev/null -s
done

Test rapid login attempts

for i in {1..10}; do curl -X POST -w "%{http_code}\n" \ -H "X-Forwarded-For: 203.0.113.10" \ -d "username=test&password=test" \ http://localhost/login -o /dev/null -s done

Monitor security logs

Check that security events are being logged correctly.

# View recent rate limiting events
tail -f /var/log/openresty/security/rate_limit.log | jq .

View DDoS detection events

tail -f /var/log/openresty/security/ddos.log | jq .

Check Redis for active penalties

redis-cli -a "OpenResty_RateLimit_2024!" KEYS "penalty:*"

View penalty details

redis-cli -a "OpenResty_RateLimit_2024!" GET "penalty:203.0.113.10"

Verify your setup

# Check OpenResty status
sudo systemctl status openresty

Verify Redis connection

redis-cli -a "OpenResty_RateLimit_2024!" ping

Test configuration syntax

sudo /usr/local/openresty/nginx/sbin/nginx -t

Check listening ports

sudo netstat -tlnp | grep nginx

Verify log files are created

ls -la /var/log/openresty/security/

Test rate limiting response headers

curl -I http://localhost/api/test

Check Lua modules load correctly

sudo /usr/local/openresty/nginx/sbin/nginx -T | grep lua_package_path

Configure monitoring and alerting

For production environments, integrate with your existing monitoring systems. This setup works well with Prometheus and Grafana for metrics visualization and Redis cluster monitoring for backend health checks.

Export metrics to Prometheus

Create a metrics endpoint that Prometheus can scrape for security statistics.

local redis_client = require "ratelimit.redis_client"
local cjson = require "cjson"

local _M = {}

function _M.export_metrics()
    local red, err = redis_client.new()
    if not red then
        ngx.status = 500
        ngx.say("# Redis connection failed")
        return
    end
    
    -- Get penalty count
    local penalty_keys = red:keys("penalty:*")
    local active_penalties = penalty_keys and #penalty_keys or 0
    
    -- Get rate limit stats from logs (simplified)
    local rate_limit_blocks = 0
    local ddos_detections = 0
    
    -- Read recent log entries
    local log_file = io.open("/var/log/openresty/security/rate_limit.log", "r")
    if log_file then
        for line in log_file:lines() do
            local entry = cjson.decode(line)
            if entry and entry.timestamp > (ngx.time() - 300) then -- Last 5 minutes
                if not entry.allowed then
                    rate_limit_blocks = rate_limit_blocks + 1
                end
            end
        end
        log_file:close()
    end
    
    redis_client.close(red)
    
    -- Output Prometheus format
    ngx.header.content_type = "text/plain"
    ngx.say("# HELP openresty_rate_limit_blocks_total Total rate limit blocks")
    ngx.say("# TYPE openresty_rate_limit_blocks_total counter")
    ngx.say("openresty_rate_limit_blocks_total " .. rate_limit_blocks)
    ngx.say("")
    ngx.say("# HELP openresty_active_penalties_total Active IP penalties")
    ngx.say("# TYPE openresty_active_penalties_total gauge")
    ngx.say("openresty_active_penalties_total " .. active_penalties)
end

return _M

Add metrics endpoint to nginx config

Add a metrics endpoint to your OpenResty configuration for monitoring integration.

sudo cp /usr/local/openresty/nginx/conf/nginx.conf /usr/local/openresty/nginx/conf/nginx.conf.bak

Add this location block to your server configuration:

# Add this inside the main server block
location /metrics {
    access_log off;
    allow 127.0.0.1;
    allow 10.0.0.0/8;
    allow 172.16.0.0/12;
    allow 192.168.0.0/16;
    deny all;
    
    content_by_lua_block {
        local metrics_exporter = require "ratelimit.metrics_exporter"
        metrics_exporter.export_metrics()
    }
}
sudo /usr/local/openresty/nginx/sbin/nginx -t
sudo systemctl reload openresty

Performance optimization

Performance considerations: Redis operations add latency to each request. Use connection pooling, implement circuit breakers for Redis failures, and consider using nginx shared dictionaries for frequently accessed data to reduce Redis load.

Optimize Redis configuration

Fine-tune Redis settings for optimal rate limiting performance.

# Add these optimizations to existing config

Optimize for rate limiting workload

tcp-keepalive 60 timeout 300

Memory efficiency

hash-max-ziplist-entries 512 hash-max-ziplist-value 64

Disable unnecessary features

save "" appendonly no

Network optimizations

tcp-nodelay yes

Increase client connections

maxclients 10000
sudo systemctl restart redis-server

Common issues

SymptomCauseFix
502 Bad Gateway errorsRedis connection failureCheck Redis status: sudo systemctl status redis-server
Lua script errors in logsMissing dependencies or syntax errorTest Lua syntax: lua -c /etc/openresty/lua/ratelimit/basic_limiter.lua
Rate limiting not workingNginx configuration issueVerify config: sudo /usr/local/openresty/nginx/sbin/nginx -T | grep lua
High memory usage in RedisKeys not expiring properlyCheck TTL: redis-cli -a password KEYS "rate_limit:*" | head -5 | xargs redis-cli -a password TTL
False positive DDoS detectionThresholds too aggressiveAdjust burst_threshold in ddos_detector.lua
Legitimate users blockedShared IP address (NAT)Implement user-based rate limiting or whitelist ranges

Next steps

Running this in production?

Want this handled for you? Running this at scale adds a second layer of work: capacity planning, failover drills, cost control, and on-call. See how we run infrastructure like this for European teams.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle infrastructure security hardening for businesses that depend on uptime. From initial setup to ongoing operations.