Implement OpenResty rate limiting and API protection with Lua middleware

Advanced 45 min May 17, 2026 42 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Build production-grade API protection using OpenResty's Lua middleware with advanced rate limiting algorithms, request throttling, and comprehensive security policies for high-traffic web applications.

Prerequisites

  • Root or sudo access
  • Basic understanding of NGINX configuration
  • Redis server access
  • Familiarity with Lua scripting

What this solves

OpenResty combines NGINX with Lua scripting to create powerful web application firewalls and API protection systems. This tutorial shows you how to implement sophisticated rate limiting algorithms, request throttling, and security policies that can handle millions of requests per second while blocking malicious traffic patterns.

Step-by-step installation

Install OpenResty with required modules

First, add the OpenResty repository and install the core package with Lua modules.

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 openresty-resty openresty-opm
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
sudo yum install -y openresty openresty-resty openresty-opm

Install Redis for rate limiting storage

Redis serves as the high-performance backend for storing rate limit counters and API protection data.

sudo apt install -y redis-server redis-tools
sudo dnf install -y redis

Install Lua Redis module

Install the lua-resty-redis module that allows OpenResty to communicate with Redis efficiently.

sudo opm get ledgetech/lua-resty-redis
sudo opm get pintsized/lua-resty-redis-connector
sudo opm get openresty/lua-resty-limit-traffic

Configure Redis for rate limiting

Configure Redis with optimized settings for high-throughput rate limiting operations.

# Memory optimization for rate limiting
maxmemory 256mb
maxmemory-policy allkeys-lru

Performance tuning

tcp-keepalive 60 timeout 300 tcp-backlog 511

Persistence settings for rate limit data

save 900 1 save 300 10 save 60 10000

Security

bind 127.0.0.1 requirepass your_redis_password_here protected-mode yes

Create rate limiting Lua module

Create a comprehensive Lua module that implements multiple rate limiting algorithms.

local redis = require "resty.redis"
local limit_req = require "resty.limit.req"
local limit_conn = require "resty.limit.conn"
local cjson = require "cjson"

local _M = {}

-- Redis connection configuration
local redis_config = {
    host = "127.0.0.1",
    port = 6379,
    password = "your_redis_password_here",
    timeout = 1000,
    pool_size = 100
}

-- Rate limiting policies
local policies = {
    api_strict = { requests = 100, window = 60, burst = 10 },
    api_normal = { requests = 500, window = 60, burst = 50 },
    api_premium = { requests = 2000, window = 60, burst = 200 },
    default = { requests = 50, window = 60, burst = 5 }
}

-- Initialize Redis connection
function _M.init_redis()
    local red = redis:new()
    red:set_timeout(redis_config.timeout)
    
    local ok, err = red:connect(redis_config.host, redis_config.port)
    if not ok then
        ngx.log(ngx.ERR, "Redis connection failed: ", err)
        return nil, err
    end
    
    if redis_config.password then
        local res, err = red:auth(redis_config.password)
        if not res then
            ngx.log(ngx.ERR, "Redis auth failed: ", err)
            return nil, err
        end
    end
    
    return red
end

-- Token bucket rate limiting
function _M.token_bucket_limit(key, policy_name, identifier)
    local policy = policies[policy_name] or policies.default
    local red, err = _M.init_redis()
    
    if not red then
        return false, "Redis connection failed"
    end
    
    local bucket_key = "bucket:" .. key .. ":" .. identifier
    local now = ngx.now()
    
    -- Lua script for atomic token bucket operations
    local script = [[
        local key = KEYS[1]
        local capacity = tonumber(ARGV[1])
        local tokens = tonumber(ARGV[2])
        local window = tonumber(ARGV[3])
        local now = tonumber(ARGV[4])
        
        local bucket = redis.call('HMGET', key, 'tokens', 'last_refill')
        local current_tokens = tonumber(bucket[1]) or capacity
        local last_refill = tonumber(bucket[2]) or now
        
        -- Calculate tokens to add based on elapsed time
        local elapsed = now - last_refill
        local tokens_to_add = math.floor(elapsed * (capacity / window))
        current_tokens = math.min(capacity, current_tokens + tokens_to_add)
        
        if current_tokens >= tokens then
            current_tokens = current_tokens - tokens
            redis.call('HMSET', key, 'tokens', current_tokens, 'last_refill', now)
            redis.call('EXPIRE', key, window * 2)
            return {1, current_tokens, capacity}
        else
            redis.call('HMSET', key, 'tokens', current_tokens, 'last_refill', now)
            redis.call('EXPIRE', key, window * 2)
            return {0, current_tokens, capacity}
        end
    ]]
    
    local res, err = red:eval(script, 1, bucket_key, policy.requests, 1, policy.window, now)
    
    if not res then
        ngx.log(ngx.ERR, "Token bucket script failed: ", err)
        return false, "Rate limit check failed"
    end
    
    red:set_keepalive(10000, redis_config.pool_size)
    
    local allowed = res[1] == 1
    local remaining = res[2]
    local total = res[3]
    
    return allowed, remaining, total
end

-- Sliding window rate limiting
function _M.sliding_window_limit(key, policy_name, identifier)
    local policy = policies[policy_name] or policies.default
    local red, err = _M.init_redis()
    
    if not red then
        return false, "Redis connection failed"
    end
    
    local window_key = "window:" .. key .. ":" .. identifier
    local now = ngx.now()
    local window_start = now - policy.window
    
    -- Lua script for sliding window rate limiting
    local script = [[
        local key = KEYS[1]
        local window_start = tonumber(ARGV[1])
        local now = tonumber(ARGV[2])
        local limit = tonumber(ARGV[3])
        local window_size = tonumber(ARGV[4])
        
        -- Remove old entries
        redis.call('ZREMRANGEBYSCORE', key, 0, window_start)
        
        -- Count current requests in window
        local current = redis.call('ZCARD', key)
        
        if current < limit then
            -- Add current request
            redis.call('ZADD', key, now, now .. ':' .. math.random(1000000))
            redis.call('EXPIRE', key, window_size * 2)
            return {1, limit - current - 1, limit}
        else
            return {0, 0, limit}
        end
    ]]
    
    local res, err = red:eval(script, 1, window_key, window_start, now, policy.requests, policy.window)
    
    if not res then
        ngx.log(ngx.ERR, "Sliding window script failed: ", err)
        return false, "Rate limit check failed"
    end
    
    red:set_keepalive(10000, redis_config.pool_size)
    
    local allowed = res[1] == 1
    local remaining = res[2]
    local total = res[3]
    
    return allowed, remaining, total
end

-- API key validation and rate limiting
function _M.validate_api_key(api_key)
    if not api_key then
        return false, "Missing API key", "default"
    end
    
    local red, err = _M.init_redis()
    if not red then
        return false, "Service unavailable", "default"
    end
    
    local key_info = red:hmget("api_key:" .. api_key, "valid", "policy", "user_id")
    red:set_keepalive(10000, redis_config.pool_size)
    
    if not key_info[1] or key_info[1] == ngx.null then
        return false, "Invalid API key", "default"
    end
    
    local policy = key_info[2] or "default"
    local user_id = key_info[3] or api_key
    
    return true, user_id, policy
end

-- Geolocation-based blocking
function _M.check_geo_blocking()
    local ip = ngx.var.remote_addr
    local country = ngx.var.geoip_country_code or "XX"
    
    local blocked_countries = {"CN", "RU", "KP"}  -- Example blocked countries
    
    for _, blocked in ipairs(blocked_countries) do
        if country == blocked then
            ngx.log(ngx.WARN, "Blocked request from country: ", country, " IP: ", ip)
            return false, "Access denied from your location"
        end
    end
    
    return true, "OK"
end

-- Bot detection based on User-Agent patterns
function _M.detect_bots()
    local user_agent = ngx.var.http_user_agent or ""
    
    local bot_patterns = {
        "bot", "crawler", "spider", "scraper", "curl", "wget",
        "python-requests", "postman", "insomnia"
    }
    
    local ua_lower = string.lower(user_agent)
    
    for _, pattern in ipairs(bot_patterns) do
        if string.find(ua_lower, pattern) then
            ngx.log(ngx.INFO, "Bot detected: ", user_agent)
            return true, "Bot traffic"
        end
    end
    
    return false, "Human traffic"
end

-- Request size limiting
function _M.check_request_size(max_size)
    local content_length = ngx.var.content_length
    
    if content_length then
        local size = tonumber(content_length)
        if size and size > max_size then
            return false, "Request too large"
        end
    end
    
    return true, "OK"
end

return _M

Configure OpenResty main configuration

Set up the main OpenResty configuration with Lua path and shared dictionaries for caching.

user www-data;
worker_processes auto;
worker_rlimit_nofile 65535;

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

http {
    include       mime.types;
    default_type  application/octet-stream;
    
    # Lua configuration
    lua_package_path "/usr/local/openresty/lualib/?.lua;/usr/local/openresty/lualib/?/init.lua;;";
    lua_code_cache on;
    
    # Shared dictionaries for caching
    lua_shared_dict rate_limit_cache 10m;
    lua_shared_dict api_keys_cache 5m;
    lua_shared_dict geo_cache 2m;
    
    # Initialize rate limiting module
    init_by_lua_block {
        ratelimit = require "ratelimit"
    }
    
    # Logging format with rate limiting info
    log_format rate_limit '$remote_addr - $remote_user [$time_local] '
                         '"$request" $status $body_bytes_sent '
                         '"$http_referer" "$http_user_agent" '
                         'api_key="$api_key" policy="$rate_limit_policy" '
                         'remaining="$rate_limit_remaining"';
    
    # Basic security headers
    add_header X-Frame-Options DENY always;
    add_header X-Content-Type-Options nosniff always;
    add_header X-XSS-Protection "1; mode=block" always;
    
    # Rate limiting server block
    server {
        listen 80;
        server_name api.example.com;
        
        access_log /var/log/openresty/api_access.log rate_limit;
        error_log /var/log/openresty/api_error.log;
        
        # Variables for rate limiting context
        set $api_key "";
        set $rate_limit_policy "";
        set $rate_limit_remaining "";
        
        # Rate limiting and API protection
        access_by_lua_block {
            local method = ngx.var.request_method
            local uri = ngx.var.uri
            local ip = ngx.var.remote_addr
            
            -- Check request size (max 1MB for API requests)
            local size_ok, size_msg = ratelimit.check_request_size(1048576)
            if not size_ok then
                ngx.status = 413
                ngx.header["Content-Type"] = "application/json"
                ngx.say('{"error": "' .. size_msg .. '"}')
                ngx.exit(413)
            end
            
            -- Geolocation blocking
            local geo_ok, geo_msg = ratelimit.check_geo_blocking()
            if not geo_ok then
                ngx.status = 403
                ngx.header["Content-Type"] = "application/json"
                ngx.say('{"error": "' .. geo_msg .. '"}')
                ngx.exit(403)
            end
            
            -- Bot detection for API endpoints
            if string.match(uri, "^/api/") then
                local is_bot, bot_msg = ratelimit.detect_bots()
                if is_bot then
                    ngx.status = 429
                    ngx.header["Content-Type"] = "application/json"
                    ngx.say('{"error": "Bot traffic not allowed on API endpoints"}')
                    ngx.exit(429)
                end
            end
            
            -- API key validation
            local api_key = ngx.var.http_x_api_key or ngx.var.arg_api_key
            ngx.var.api_key = api_key or "none"
            
            local valid, user_id, policy = ratelimit.validate_api_key(api_key)
            if not valid then
                ngx.status = 401
                ngx.header["Content-Type"] = "application/json"
                ngx.say('{"error": "' .. user_id .. '"}')
                ngx.exit(401)
            end
            
            ngx.var.rate_limit_policy = policy
            
            -- Apply rate limiting based on endpoint
            local rate_limit_key
            local algorithm = "sliding_window"  -- or "token_bucket"
            
            if string.match(uri, "^/api/v1/") then
                rate_limit_key = "api_v1:" .. method
            elseif string.match(uri, "^/api/upload/") then
                rate_limit_key = "upload"
                algorithm = "token_bucket"  -- Better for upload endpoints
            else
                rate_limit_key = "general"
            end
            
            -- Execute rate limiting
            local allowed, remaining, total
            
            if algorithm == "token_bucket" then
                allowed, remaining, total = ratelimit.token_bucket_limit(rate_limit_key, policy, user_id)
            else
                allowed, remaining, total = ratelimit.sliding_window_limit(rate_limit_key, policy, user_id)
            end
            
            if not allowed then
                ngx.var.rate_limit_remaining = "0"
                ngx.status = 429
                ngx.header["Content-Type"] = "application/json"
                ngx.header["X-RateLimit-Limit"] = total
                ngx.header["X-RateLimit-Remaining"] = "0"
                ngx.header["X-RateLimit-Reset"] = ngx.time() + 60
                ngx.header["Retry-After"] = "60"
                ngx.say('{"error": "Rate limit exceeded", "limit": ' .. total .. ', "remaining": 0}')
                ngx.exit(429)
            end
            
            -- Set rate limiting headers for successful requests
            ngx.var.rate_limit_remaining = remaining
            ngx.header["X-RateLimit-Limit"] = total
            ngx.header["X-RateLimit-Remaining"] = remaining
        }
        
        # API endpoints
        location /api/ {
            proxy_pass http://backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-API-Key $api_key;
        }
        
        # Health check endpoint (no rate limiting)
        location /health {
            access_by_lua_block {
                -- Skip rate limiting for health checks
            }
            
            content_by_lua_block {
                ngx.header["Content-Type"] = "application/json"
                ngx.say('{"status": "healthy", "timestamp": ' .. ngx.time() .. '}')
            }
        }
        
        # Rate limiting status endpoint
        location /rate-limit-status {
            content_by_lua_block {
                local api_key = ngx.var.http_x_api_key or ngx.var.arg_api_key
                
                if not api_key then
                    ngx.status = 400
                    ngx.header["Content-Type"] = "application/json"
                    ngx.say('{"error": "API key required"}')
                    return
                end
                
                local valid, user_id, policy = ratelimit.validate_api_key(api_key)
                if not valid then
                    ngx.status = 401
                    ngx.header["Content-Type"] = "application/json"
                    ngx.say('{"error": "Invalid API key"}')
                    return
                end
                
                local red = ratelimit.init_redis()
                if red then
                    local bucket_key = "bucket:api_v1:GET:" .. user_id
                    local bucket = red:hmget(bucket_key, "tokens", "last_refill")
                    local tokens = bucket[1] or "N/A"
                    
                    red:set_keepalive(10000, 100)
                    
                    ngx.header["Content-Type"] = "application/json"
                    ngx.say('{"user_id": "' .. user_id .. '", "policy": "' .. policy .. '", "remaining_tokens": "' .. tokens .. '"}')
                else
                    ngx.status = 500
                    ngx.header["Content-Type"] = "application/json"
                    ngx.say('{"error": "Unable to check status"}')
                end
            }
        }
    }
    
    # Upstream backend servers
    upstream backend {
        server 127.0.0.1:8080 weight=1 max_fails=2 fail_timeout=5s;
        server 127.0.0.1:8081 weight=1 max_fails=2 fail_timeout=5s;
        keepalive 32;
    }
}

Create API key management script

Create a management script to add, remove, and manage API keys in Redis.

#!/usr/local/openresty/bin/resty

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

-- Redis configuration
local redis_config = {
    host = "127.0.0.1",
    port = 6379,
    password = "your_redis_password_here",
    timeout = 1000
}

-- Connect to Redis
local function connect_redis()
    local red = redis:new()
    red:set_timeout(redis_config.timeout)
    
    local ok, err = red:connect(redis_config.host, redis_config.port)
    if not ok then
        print("Redis connection failed: " .. err)
        return nil
    end
    
    if redis_config.password then
        local res, err = red:auth(redis_config.password)
        if not res then
            print("Redis auth failed: " .. err)
            return nil
        end
    end
    
    return red
end

-- Generate API key
local function generate_api_key()
    local charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
    local key = ""
    for i = 1, 32 do
        local rand = math.random(#charset)
        key = key .. string.sub(charset, rand, rand)
    end
    return key
end

-- Add API key
local function add_api_key(user_id, policy)
    policy = policy or "default"
    local red = connect_redis()
    if not red then
        return false, "Redis connection failed"
    end
    
    local api_key = generate_api_key()
    local key_data = {
        valid = "1",
        policy = policy,
        user_id = user_id,
        created = os.time(),
        last_used = "0"
    }
    
    local ok, err = red:hmset("api_key:" .. api_key, key_data)
    if not ok then
        return false, "Failed to create API key: " .. err
    end
    
    -- Add to user's key list
    red:sadd("user_keys:" .. user_id, api_key)
    red:close()
    
    return api_key, "API key created successfully"
end

-- Remove API key
local function remove_api_key(api_key)
    local red = connect_redis()
    if not red then
        return false, "Redis connection failed"
    end
    
    local user_id = red:hget("api_key:" .. api_key, "user_id")
    if user_id then
        red:srem("user_keys:" .. user_id, api_key)
    end
    
    local ok, err = red:del("api_key:" .. api_key)
    red:close()
    
    return ok, ok and "API key removed" or "Failed to remove key"
end

-- List API keys for user
local function list_user_keys(user_id)
    local red = connect_redis()
    if not red then
        return false, "Redis connection failed"
    end
    
    local keys = red:smembers("user_keys:" .. user_id)
    local result = {}
    
    for _, key in ipairs(keys) do
        local info = red:hmget("api_key:" .. key, "policy", "created", "last_used")
        table.insert(result, {
            key = key,
            policy = info[1],
            created = info[2],
            last_used = info[3]
        })
    end
    
    red:close()
    return result
end

-- Command line interface
local function print_usage()
    print("Usage:")
    print("  " .. arg[0] .. " add  [policy]")
    print("  " .. arg[0] .. " remove ")
    print("  " .. arg[0] .. " list ")
    print("")
    print("Policies: default, api_normal, api_strict, api_premium")
end

-- Main execution
if #arg < 2 then
    print_usage()
    os.exit(1)
end

local command = arg[1]

if command == "add" then
    local user_id = arg[2]
    local policy = arg[3]
    local key, msg = add_api_key(user_id, policy)
    if key then
        print("API Key: " .. key)
        print("User ID: " .. user_id)
        print("Policy: " .. (policy or "default"))
    else
        print("Error: " .. msg)
    end
elseif command == "remove" then
    local api_key = arg[2]
    local ok, msg = remove_api_key(api_key)
    print(msg)
elseif command == "list" then
    local user_id = arg[2]
    local keys = list_user_keys(user_id)
    if keys then
        print("API Keys for user: " .. user_id)
        print("Key\t\t\t\tPolicy\tCreated\tLast Used")
        for _, key_info in ipairs(keys) do
            print(key_info.key .. "\t" .. key_info.policy .. "\t" .. 
                  os.date("%Y-%m-%d", key_info.created) .. "\t" .. 
                  (key_info.last_used ~= "0" and os.date("%Y-%m-%d", key_info.last_used) or "Never"))
        end
    end
else
    print_usage()
end

Start and enable services

Start Redis and OpenResty services and ensure they start automatically on boot.

sudo systemctl enable redis-server
sudo systemctl start redis-server
sudo systemctl enable openresty
sudo systemctl start openresty

Create log directories and set permissions

Set up proper logging directories with correct ownership for the web server user.

sudo mkdir -p /var/log/openresty
sudo chown www-data:www-data /var/log/openresty
sudo chmod 755 /var/log/openresty
Never use chmod 777. It gives every user on the system full access to your files. Instead, fix ownership with chown and use minimal permissions like 755 for directories.

Make API key management script executable

Set proper permissions on the API key management script.

sudo chmod 755 /usr/local/bin/manage-api-keys.lua

Configure advanced rate limiting policies

Create rate limiting monitoring dashboard

Set up a simple monitoring endpoint to track rate limiting metrics.

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

local _M = {}

-- Get rate limiting statistics
function _M.get_stats()
    local red = require("ratelimit").init_redis()
    if not red then
        return {error = "Redis connection failed"}
    end
    
    local stats = {
        total_requests = red:get("stats:total_requests") or 0,
        blocked_requests = red:get("stats:blocked_requests") or 0,
        active_api_keys = red:scard("active_api_keys") or 0,
        timestamp = ngx.time()
    }
    
    -- Get top rate limited IPs
    local blocked_ips = red:zrevrange("stats:blocked_ips", 0, 9, "WITHSCORES")
    stats.top_blocked_ips = {}
    
    for i = 1, #blocked_ips, 2 do
        table.insert(stats.top_blocked_ips, {
            ip = blocked_ips[i],
            count = blocked_ips[i + 1]
        })
    end
    
    red:set_keepalive(10000, 100)
    return stats
end

return _M

Add monitoring endpoint to OpenResty config

Add a monitoring location to track rate limiting performance and statistics.

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

Add this location block inside the server block of your nginx.conf:

# Add this location block inside the existing server block
location /admin/stats {
    allow 127.0.0.1;
    allow 10.0.0.0/8;
    allow 192.168.0.0/16;
    allow 172.16.0.0/12;
    deny all;
    
    content_by_lua_block {
        local monitor = require "monitor"
        local stats = monitor.get_stats()
        
        ngx.header["Content-Type"] = "application/json"
        ngx.say(require("cjson").encode(stats))
    }
}

Test rate limiting configuration

Create test API keys and verify rate limiting is working correctly.

# Create test API keys
/usr/local/bin/manage-api-keys.lua add test_user_1 api_normal
/usr/local/bin/manage-api-keys.lua add test_user_2 api_strict
/usr/local/bin/manage-api-keys.lua add premium_user api_premium

Test configuration

sudo /usr/local/openresty/bin/openresty -t

Reload configuration

sudo systemctl reload openresty

Verify your setup

Test the rate limiting functionality with different scenarios and API keys.

# Check services are running
sudo systemctl status redis-server
sudo systemctl status openresty

Test health endpoint

curl -I http://localhost/health

Test rate limiting with API key (replace YOUR_API_KEY with actual key)

curl -H "X-API-Key: YOUR_API_KEY" http://localhost/api/test

Test rate limit status

curl "http://localhost/rate-limit-status?api_key=YOUR_API_KEY"

Test rate limiting by making multiple requests

for i in {1..10}; do curl -H "X-API-Key: YOUR_API_KEY" http://localhost/api/test sleep 0.1 done

Check monitoring stats (from allowed IP)

curl http://localhost/admin/stats

Check logs for rate limiting activity

sudo tail -f /var/log/openresty/api_access.log

Performance optimization

Configure Redis persistence for production

Optimize Redis settings for high-performance rate limiting in production.

# Add these optimizations to existing Redis config

Memory optimization

maxmemory-samples 5 hash-max-ziplist-entries 512 hash-max-ziplist-value 64 list-max-ziplist-size -2 list-compress-depth 0 set-max-intset-entries 512 zset-max-ziplist-entries 128 zset-max-ziplist-value 64

Network optimization

tcp-keepalive 300 tcp-backlog 511

Latency optimization

lazy-expire-disabled no lazyfree-lazy-eviction yes lazyfree-lazy-expire yes lazyfree-lazy-server-del yes

Logging for monitoring

loglevel notice logfile /var/log/redis/redis-server.log

Configure OpenResty worker optimization

Tune OpenResty for maximum performance with large numbers of concurrent connections.

# Add these optimizations to the main nginx.conf http block
sudo tee -a /usr/local/openresty/nginx/conf/nginx.conf.extra << 'EOF'

Add to http block for production optimization

worker_cpu_affinity auto; worker_priority -5;

Connection optimization

keepalive_requests 1000; keepalive_timeout 30s; sendfile on; tcp_nopush on; tcp_nodelay on;

Lua optimizations

lua_max_pending_timers 8192; lua_max_running_timers 256;

Rate limiting specific optimizations

lua_shared_dict stats_cache 5m; lua_shared_dict temp_ban_cache 10m; EOF

Monitor and optimize rate limiting performance

Set up log rotation for OpenResty

Configure log rotation to prevent disk space issues with high-volume logging.

/var/log/openresty/*.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    sharedscripts
    postrotate
        /bin/kill -USR1 $(cat /usr/local/openresty/nginx/logs/nginx.pid 2>/dev/null) 2>/dev/null || true
    endscript
}

Create performance monitoring script

Set up automated monitoring for rate limiting performance metrics.

#!/bin/bash

Rate limiting performance monitoring script

LOG_FILE="/var/log/openresty/rate-limit-monitor.log" REDIS_CLI="redis-cli -a your_redis_password_here" echo "[$(date)] Starting rate limit performance check" >> $LOG_FILE

Check Redis performance

REDIS_INFO=$($REDIS_CLI info stats | grep -E "total_commands_processed|instantaneous_ops_per_sec") echo "Redis Stats: $REDIS_INFO" >> $LOG_FILE

Check OpenResty connections

NGINX_STATS=$(curl -s http://localhost/admin/stats 2>/dev/null) echo "OpenResty Stats: $NGINX_STATS" >> $LOG_FILE

Check memory usage

MEM_USAGE=$(free -m | grep "Mem:" | awk '{print "Used: "$3"MB, Available: "$7"MB"}') echo "Memory: $MEM_USAGE" >> $LOG_FILE

Check for high rate limiting activity

BLOCKED_COUNT=$($REDIS_CLI get stats:blocked_requests 2>/dev/null || echo "0") echo "Total blocked requests: $BLOCKED_COUNT" >> $LOG_FILE echo "[$(date)] Performance check completed" >> $LOG_FILE echo "---" >> $LOG_FILE
sudo chmod 755 /usr/local/bin/monitor-rate-limits.sh

Set up automated monitoring cron job

Schedule regular performance monitoring to track rate limiting efficiency.

sudo crontab -e

Add this line to run monitoring every 5 minutes:

/5    * /usr/local/bin/monitor-rate-limits.sh

Common issues

SymptomCauseFix
Rate limiting not workingRedis connection failedCheck Redis credentials in /usr/local/openresty/lualib/ratelimit.lua and test with redis-cli ping
High memory usageToo many rate limit keys storedReduce key TTL in Lua scripts or implement key cleanup job
API keys not validatingMissing API keys in RedisUse /usr/local/bin/manage-api-keys.lua list username to verify keys exist
Rate limits too strictWrong policy configurationAdjust request limits in policies table in ratelimit.lua
OpenResty won't startLua syntax errorCheck syntax with /usr/local/openresty/bin/openresty -t and review error logs
Monitoring endpoint blockedIP not in allowed listAdd your IP to the allow directives in /admin/stats location
Performance degradationRedis not optimizedTune Redis memory settings and enable key eviction policies

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.