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
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
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
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
| Symptom | Cause | Fix |
|---|---|---|
| Rate limiting not working | Redis connection failed | Check Redis credentials in /usr/local/openresty/lualib/ratelimit.lua and test with redis-cli ping |
| High memory usage | Too many rate limit keys stored | Reduce key TTL in Lua scripts or implement key cleanup job |
| API keys not validating | Missing API keys in Redis | Use /usr/local/bin/manage-api-keys.lua list username to verify keys exist |
| Rate limits too strict | Wrong policy configuration | Adjust request limits in policies table in ratelimit.lua |
| OpenResty won't start | Lua syntax error | Check syntax with /usr/local/openresty/bin/openresty -t and review error logs |
| Monitoring endpoint blocked | IP not in allowed list | Add your IP to the allow directives in /admin/stats location |
| Performance degradation | Redis not optimized | Tune Redis memory settings and enable key eviction policies |
Next steps
- Set up NGINX monitoring with Prometheus and Grafana for web server observability
- Configure Redis 7 cluster sharding for horizontal scaling with automated failover and monitoring
- Implement OpenResty JWT authentication with OAuth2 integration for secure web applications
- Setup OpenResty web application firewall with advanced security rules and threat detection
- Configure OpenResty load balancing with multiple backend servers and health checks
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Production OpenResty Rate Limiting and API Protection Install Script
# Supports Ubuntu/Debian and RHEL-based distributions
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Default configuration
REDIS_PASSWORD="$(openssl rand -base64 32)"
DOMAIN="${1:-api.example.com}"
# Check if domain argument provided
if [ "$#" -eq 0 ]; then
echo "Usage: $0 <domain>"
echo "Example: $0 api.example.com"
exit 1
fi
# Function for colored output
log() {
echo -e "${GREEN}[INFO]${NC} $1"
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Cleanup function
cleanup() {
error "Installation failed. Cleaning up..."
systemctl stop openresty 2>/dev/null || true
systemctl stop redis-server 2>/dev/null || systemctl stop redis 2>/dev/null || true
}
trap cleanup ERR
# Check if running as root
if [ "$EUID" -ne 0 ]; then
error "Please run as root or with sudo"
exit 1
fi
# Auto-detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
REDIS_SERVICE="redis-server"
REDIS_CONFIG="/etc/redis/redis.conf"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
REDIS_SERVICE="redis"
REDIS_CONFIG="/etc/redis/redis.conf"
;;
fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
REDIS_SERVICE="redis"
REDIS_CONFIG="/etc/redis/redis.conf"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
REDIS_SERVICE="redis"
REDIS_CONFIG="/etc/redis.conf"
;;
*)
error "Unsupported distribution: $ID"
exit 1
;;
esac
else
error "Cannot detect distribution"
exit 1
fi
log "[1/8] Updating system packages..."
$PKG_UPDATE
log "[2/8] Installing prerequisites..."
if [ "$PKG_MGR" = "apt" ]; then
$PKG_INSTALL wget gnupg2 lsb-release curl openssl
# Add OpenResty repository
wget -qO - https://openresty.org/package/pubkey.gpg | apt-key add -
echo "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/openresty.list
apt update
else
$PKG_INSTALL wget curl openssl yum-utils
# Add OpenResty repository
cat > /etc/yum.repos.d/openresty.repo << 'EOF'
[openresty]
name=OpenResty Repository
baseurl=https://openresty.org/package/centos/$releasever/$basearch
gpgcheck=1
gpgkey=https://openresty.org/package/pubkey.gpg
enabled=1
EOF
fi
log "[3/8] Installing OpenResty..."
$PKG_INSTALL openresty openresty-resty openresty-opm
log "[4/8] Installing Redis..."
if [ "$PKG_MGR" = "apt" ]; then
$PKG_INSTALL redis-server redis-tools
else
$PKG_INSTALL redis
fi
log "[5/8] Installing Lua Redis modules..."
export PATH=/usr/local/openresty/bin:$PATH
opm get ledgetech/lua-resty-redis
opm get pintsized/lua-resty-redis-connector
opm get openresty/lua-resty-limit-traffic
log "[6/8] Configuring Redis..."
# Backup original config
cp "$REDIS_CONFIG" "$REDIS_CONFIG.backup"
cat > "$REDIS_CONFIG" << EOF
# Memory optimization for rate limiting
maxmemory 256mb
maxmemory-policy allkeys-lru
# Performance tuning
tcp-keepalive 60
timeout 300
tcp-backlog 511
# Persistence settings
save 900 1
save 300 10
save 60 10000
# Security
bind 127.0.0.1
requirepass $REDIS_PASSWORD
protected-mode yes
# Logging
logfile /var/log/redis/redis-server.log
loglevel notice
# Network
port 6379
EOF
# Set proper ownership
chown redis:redis "$REDIS_CONFIG"
chmod 640 "$REDIS_CONFIG"
log "[7/8] Creating rate limiting Lua module..."
mkdir -p /usr/local/openresty/site/lualib/resty
cat > /usr/local/openresty/site/lualib/resty/rate_limiter.lua << 'EOF'
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 configuration
_M.redis_config = {
host = "127.0.0.1",
port = 6379,
timeout = 1000,
pool_size = 100
}
-- Rate limiting policies
_M.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 }
}
function _M.init_redis()
local red = redis:new()
red:set_timeout(_M.redis_config.timeout)
local ok, err = red:connect(_M.redis_config.host, _M.redis_config.port)
if not ok then
ngx.log(ngx.ERR, "Redis connection failed: ", err)
return nil, err
end
return red
end
function _M.check_rate_limit(client_id, policy_name)
local policy = _M.policies[policy_name] or _M.policies.default
local lim, err = limit_req.new("rate_limit_store", policy.requests, policy.burst)
if not lim then
ngx.log(ngx.ERR, "Failed to instantiate rate limiter: ", err)
return false
end
local key = client_id or ngx.var.binary_remote_addr
local delay, err = lim:incoming(key, true)
if not delay then
if err == "rejected" then
return false, "Rate limit exceeded"
end
ngx.log(ngx.ERR, "Failed to limit req: ", err)
return false, "Rate limiting error"
end
if delay >= 0.001 then
ngx.sleep(delay)
end
return true
end
return _M
EOF
chown nobody:nobody /usr/local/openresty/site/lualib/resty/rate_limiter.lua
chmod 644 /usr/local/openresty/site/lualib/resty/rate_limiter.lua
# Create OpenResty configuration
mkdir -p /etc/openresty
cat > /etc/openresty/nginx.conf << EOF
worker_processes auto;
error_log /var/log/openresty/error.log;
pid /run/openresty.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
lua_shared_dict rate_limit_store 10m;
lua_package_path "/usr/local/openresty/site/lualib/?.lua;;";
init_by_lua_block {
local rate_limiter = require "resty.rate_limiter"
rate_limiter.redis_config.password = "$REDIS_PASSWORD"
}
access_log /var/log/openresty/access.log;
include /etc/openresty/mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
server {
listen 80;
server_name $DOMAIN;
location /api/ {
access_by_lua_block {
local rate_limiter = require "resty.rate_limiter"
local allowed, err = rate_limiter.check_rate_limit(nil, "api_normal")
if not allowed then
ngx.status = 429
ngx.header.content_type = "application/json"
ngx.say('{"error":"Rate limit exceeded","message":"' .. (err or "Too many requests") .. '"}')
ngx.exit(429)
end
}
proxy_pass http://backend;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
}
}
upstream backend {
server 127.0.0.1:8080;
}
}
EOF
chown root:root /etc/openresty/nginx.conf
chmod 644 /etc/openresty/nginx.conf
# Create log directory
mkdir -p /var/log/openresty
chown nobody:nobody /var/log/openresty
chmod 755 /var/log/openresty
log "[8/8] Starting services..."
systemctl enable "$REDIS_SERVICE"
systemctl start "$REDIS_SERVICE"
systemctl enable openresty
systemctl start openresty
# Configure firewall
if command -v firewall-cmd >/dev/null 2>&1; then
firewall-cmd --permanent --add-service=http
firewall-cmd --reload
elif command -v ufw >/dev/null 2>&1; then
ufw allow 80/tcp
fi
# Verification
log "Verifying installation..."
sleep 3
if systemctl is-active --quiet "$REDIS_SERVICE"; then
log "✓ Redis is running"
else
error "✗ Redis is not running"
exit 1
fi
if systemctl is-active --quiet openresty; then
log "✓ OpenResty is running"
else
error "✗ OpenResty is not running"
exit 1
fi
log "Installation completed successfully!"
warn "Redis password: $REDIS_PASSWORD"
warn "Please save this password and update your application configuration"
log "OpenResty is configured for domain: $DOMAIN"
log "Rate limiting is active on /api/ endpoints"
Review the script before running. Execute with: bash install.sh