How to Dynamically Block IPs with Nginx, Lua, and Redis

This guide explains how to use Nginx, Lua (via OpenResty), and Redis to automatically block any IP that exceeds 60 requests per minute, storing the blacklist in Redis with a default one‑day ban and providing performance optimizations and testing steps.

Liangxu Linux
Liangxu Linux
Liangxu Linux
How to Dynamically Block IPs with Nginx, Lua, and Redis

Background

Frequent brute‑force requests and malicious crawlers can overload databases and services. The requirement is to block any IP that makes more than 60 requests within one minute, adding it to Redis and denying traffic for a default period of one day.

Technical Choice

The solution uses Nginx with the Lua module (via OpenResty) and Redis. A Lua script runs on each request, checks a Redis blacklist, counts accesses, and bans the IP when the threshold is exceeded.

Environment Setup

Install OpenResty – provides Nginx with built‑in Lua support.

# Ubuntu/Debian
sudo apt-get install openresty
# CentOS
yum install openresty

Install Redis

sudo apt-get install redis-server   # Debian
sudo yum install redis               # RedHat

Nginx Configuration

nginx.conf – add a shared memory dictionary and point to the Lua script.

http {
    lua_package_path "/usr/local/openresty/lualib/?.lua;;";
    lua_shared_dict ip_limit 10m;

    server {
        listen 80;
        server_name _;
        location / {
            access_by_lua_file /usr/local/lua/ip_block.lua;
            root /var/www/html;
        }
    }
}

Lua Script (ip_block.lua)

File location – /usr/local/lua/ip_block.lua

Script content

local redis = require "resty.redis"
local red = redis:new()

-- Redis connection parameters
local redis_host = "127.0.0.1"
local redis_port = 6379
local redis_timeout = 1000   -- ms
local redis_auth = nil       -- no password

-- Blocking parameters
local block_time = 86400     -- 1 day
local time_window = 60       -- 1 minute
local max_requests = 60

local function get_client_ip()
    local headers = ngx.req.get_headers()
    return headers["X-Real-IP"]
        or headers["x_forwarded_for"]
        or ngx.var.remote_addr
end

local function connect_redis()
    red:set_timeout(redis_timeout)
    local ok, err = red:connect(redis_host, redis_port)
    if not ok then
        ngx.log(ngx.ERR, "Redis connection failed: ", err)
        return nil
    end
    if redis_auth then
        ok, err = red:auth(redis_auth)
        if not ok then
            ngx.log(ngx.ERR, "Redis auth failed: ", err)
        end
    end
    return ok
end

local client_ip = get_client_ip()
local counter_key = "limit:count:" .. client_ip
local block_key = "limit:block:" .. client_ip

-- Check existing block
local is_blocked = red:get(block_key)
if tonumber(is_blocked) == 1 then
    ngx.exit(ngx.HTTP_FORBIDDEN)
end

connect_redis()
local current_count = red:incr(counter_key)
if current_count == 1 then
    red:expire(counter_key, time_window)
end

if current_count > max_requests then
    red:setex(block_key, block_time, 1)
    red:del(counter_key)
    ngx.exit(ngx.HTTP_FORBIDDEN)
end

red:set_keepalive(10000, 100)

Performance Optimizations

Redis connection pool – reuse connections with set_keepalive.

Shared memory cache – store hot IPs in lua_shared_dict to reduce Redis lookups.

Asynchronous logging – write block events with a non‑blocking timer.

ngx.timer.at(0, function()
    local log_msg = string.format("%s - IP %s blocked at %s",
        ngx.var.host, client_ip, ngx.localtime())
    local log_file = io.open("/var/log/nginx/blocked_ips.log", "a")
    log_file:write(log_msg, "
")
    log_file:close()
end)

Verification & Testing

Manual block test – use ab -n 100 -c 10 http://your-domain.com/ then check Redis keys with redis-cli keys "limit:block:*".

Automatic un‑block – after 24 hours verify the key expires with redis-cli ttl "limit:block:1.2.3.4".

Extended Ideas

Distributed blocking – share the Redis blacklist across multiple Nginx nodes for cluster‑wide protection.

Visualization – use Grafana + Prometheus with prometheus-redis-exporter to monitor real‑time block statistics.

Dynamic thresholds – store per‑path limits in a Redis hash and read them in the Lua script.

local rule_key = "limit:rule:" .. ngx.var.uri
local custom_rule = red:hget(rule_key, "max_requests")
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendredisNGINXrate limitingLuaOpenRestyIP blocking
Liangxu Linux
Written by

Liangxu Linux

Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.