How to Build a Dynamic IP Blacklist with Nginx, Lua, and Redis
This guide explains how to create a dynamic IP blacklist that blocks malicious crawlers and users by configuring Nginx with Lua scripts and a Redis store, covering requirements, environment setup, design options, configuration files, Lua code, summary of benefits, use cases, and advanced extensions.
Requirement
To block certain crawlers or malicious users, we need a dynamic IP blacklist that rejects requests from listed IPs and can set expiration times.
Environment Preparation
Linux (CentOS7 / Ubuntu etc.)
Redis 5.0.5
Nginx (OpenResty)
Design方案
There are three ways to implement an IP blacklist:
OS level using iptables – simple but requires manual updates.
Web server level using Nginx deny or Lua plugin – dynamic but requires Lua knowledge.
Application level checking IP before processing – easy to code but may affect performance under high concurrency.
We choose the Nginx + Lua + Redis architecture for dynamic management.
Configure nginx.conf
location / {
# if ($request_uri ~ .*\.(html|htm|jpg|js|css)) {
# access_by_lua_file /usr/local/lua/access_limit.lua;
# }
access_by_lua_file /usr/local/lua/access_limit.lua;
alias /usr/local/web/;
index index.html index.htm;
}Configure Lua script
-- /usr/local/lua/access_limit.lua
local pool_max_idle_time = 10000
local pool_size = 100
local redis_connection_timeout = 100
local redis_host = "your redis host ip"
local redis_port = "your redis port"
local redis_auth = "your redis authpassword"
local ip_block_time = 120
local ip_time_out = 1
local ip_max_count = 3
local function errlog(msg, ex)
ngx.log(ngx.ERR, msg, ex)
end
local function close_redis(red)
if not red then return end
local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
if not ok then
ngx.say("redis connct err:", err)
return red:close()
end
end
local redis = require "resty.redis"
local client = redis:new()
local ok, err = client:connect(redis_host, redis_port)
if not ok then
close_redis(client)
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
client:set_timeout(redis_connection_timeout)
local connCount, err = client:get_reused_times()
if connCount == 0 then
local ok, err = client:auth(redis_auth)
if not ok then errlog("failed to auth: ", err); return end
elseif err then
errlog("failed to get reused times: ", err); return
end
local function getIp()
local clientIP = ngx.req.get_headers()["X-Real-IP"]
if not clientIP then clientIP = ngx.req.get_headers()["x_forwarded_for"] end
if not clientIP then clientIP = ngx.var.remote_addr end
return clientIP
end
local cliendIp = getIp()
local incrKey = "limit:count:" .. cliendIp
local blockKey = "limit:block:" .. cliendIp
local is_block = client:get(blockKey)
if tonumber(is_block) == 1 then
ngx.exit(ngx.HTTP_FORBIDDEN)
close_redis(client)
end
local ip_count = client:incr(incrKey)
if tonumber(ip_count) == 1 then
client:expire(incrKey, ip_time_out)
end
if tonumber(ip_count) > tonumber(ip_max_count) then
client:set(blockKey, 1)
client:expire(blockKey, ip_block_time)
end
close_redis(client)Summary
Simple, lightweight configuration with minimal performance impact.
Multiple servers can share the blacklist via a common Redis instance.
Dynamic updates can be made manually or through automation.
Extensions
Application scenarios
Prevent malicious access such as brute‑force, SQL injection, XSS.
Block crawlers and data abuse.
Mitigate DDoS attacks.
Rate‑limit requests from a single IP.
Advanced features
Automatic anomaly detection and banning.
Whitelist mechanism for trusted IPs.
Captcha verification for suspicious traffic.
Statistics and analysis of blacklist data.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
