Overview
A Web Application Firewall (WAF) filters HTTP/HTTPS traffic to protect web applications from common exploits such as SQL injection, cross-site scripting (XSS), and DDoS/CC attacks. By leveraging Nginx’s event-driven architecture and Lua’s lightweight scripting capabilities via the lua-nginx-module, a high-performence, rule-based WAF can be implemented without modifying application code.
Prerequisites and Dependencies
To build a custom WAF, the following components must be compiled and integrated:
- Nginx (v1.13.6 or later)
- PCRE (Perl Compatible Regular Expressions) for URL pattern matching
- Luajit — a Just-In-Time compiler for Lua, providing performance improvements over standard Lua
- ngx_devel_kit (NDK) — provides core extensions for Nginx modules
- lua-nginx-module — enables Lua execution within Nginx request phases
Compilation and Installation
Download and extract all required packages:
wget http://nginx.org/download/nginx-1.13.6.tar.gz
wget https://jaist.dl.sourceforge.net/project/pcre/pcre/8.42/pcre-8.42.tar.gz
wget http://luajit.org/download/LuaJIT-2.0.5.tar.gz
wget https://github.com/simplresty/ngx_devel_kit/archive/v0.3.0.tar.gz
wget https://github.com/openresty/lua-nginx-module/archive/v0.10.13.tar.gz
tar -xzf nginx-1.13.6.tar.gz
tar -xzf pcre-8.42.tar.gz
tar -xzf LuaJIT-2.0.5.tar.gz
tar -xzf v0.3.0.tar.gz
tar -xzf v0.10.13.tar.gz
Install Luajit:
cd LuaJIT-2.0.5
make && make install
Set environment variables for LuaJit paths:
export LUAJIT_LIB=/usr/local/lib
export LUAJIT_INC=/usr/local/include/luajit-2.0
Configure and compile Nginx with Lua modules:
cd nginx-1.13.6
./configure \
--user=www-data \
--group=www-data \
--prefix=/usr/local/nginx \
--with-pcre=/usr/local/src/pcre-8.42 \
--with-http_stub_status_module \
--with-http_gzip_static_module \
--add-module=../ngx_devel_kit-0.3.0 \
--add-module=../lua-nginx-module-0.10.13
make -j4 && make install
Create symbolic links for LuaJit libraries:
ln -sf /usr/local/lib/libluajit-5.1.so.2 /lib64/libluajit-5.1.so.2
ln -sf /usr/local/nginx /usr/local/nginx-main
Testing Lua Integration
Edit /usr/local/nginx-main/conf/nginx.conf to verify Lua functionality:
server {
listen 80;
server_name localhost;
location /test-lua {
default_type 'text/plain';
content_by_lua_block {
ngx.say("Lua is working!")
}
}
}
Test the configuration and start Nginx:
/usr/local/nginx-main/sbin/nginx -t
/usr/local/nginx-main/sbin/nginx
curl http://localhost/test-lua
# Output: Lua is working!
Deploying the WAF Module
Clone a mature WAF implementation:
cd /usr/local/nginx-main/conf
git clone https://github.com/loveshell/ngx_lua_waf.git waf
Directory structure:
waf/config.lua— main configuration filewaf/init.lua— initializes shared memory and rule loaderswaf/waf.lua— main logic handlerwaf/wafconf/— rule sets:args— URL parameter filteringurl— URI pattern matchingpost— POST body inspectionuser-agent— user-agent blockingwhitelist— exempted URLs/IPsblacklist— blocked IPs
Configuration
Edit waf/config.lua to define behavior:
RulePath = "/usr/local/nginx-main/conf/waf/wafconf/"
attacklog = "on"
logdir = "/var/log/nginx/waf/"
UrlDeny = "on"
Redirect = "on"
CookieMatch = "on"
postMatch = "on"
whiteModule = "on"
black_fileExt = {"php", "jsp", "asp"}
ipWhitelist = {"127.0.0.1", "192.168.1.10"}
ipBlocklist = {"10.0.0.5"}
CCDeny = "on"
CCrate = "100/60"
html = [[<h1>Access Denied</h1><p>Your request has been blocked by the WAF.</p>]]
Add WAF directives to Nginx’s http block in nginx.conf:
http {
lua_shared_dict waf_limit 50m;
lua_package_path "/usr/local/nginx-main/conf/waf/?.lua;;";
init_by_lua_file /usr/local/nginx-main/conf/waf/init.lua;
access_by_lua_file /usr/local/nginx-main/conf/waf/waf.lua;
# ... rest of server blocks
}
Create the log directory and assign permissions:
mkdir -p /var/log/nginx/waf/
chown www-data:www-data /var/log/nginx/waf/
Rule Engine Mechanics
The WAF evaluates requests in the following sequence:
- Check if client IP is in whitelist → allow
- Check if cleint IP is in blacklist → block
- Inspect User-Agent headers against known malicious patterns
- Validate URL path against regex rules (e.g.,
/etc/passwd) - Scan query parameters for SQLi/XSS patterns (e.g.,
union select) - Analyze POST bodies for malicious payloads
- Check cookies for session tampering indicators
- Apply CC rate limiting via
lua_shared_dict(tracks request counts per IP) - If any rule matches → return 403 or redirect to warning page
Example rule in wafconf/args.rule:
\.\./
select.*from
benchmark\(
sleep\(
union.*select
\$_(GET|POST|COOKIE)
CC Attack Mitigation
Rate limiting is implemented using Nginx’s shared memory dictionary:
lua_shared_dict waf_limit 50m;
In waf.lua, the module tracks request frequency per IP:
local limit = ngx.shared.waf_limit
local key = ngx.var.remote_addr .. ":" .. ngx.var.uri
local count, err = limit:get(key)
if count and count >= 100 then
ngx.exit(ngx.HTTP_FORBIDDEN)
else
limit:set(key, (count or 0) + 1, 60)
end
Simulate CC attack using Apache Bench:
ab -n 500 -c 20 http://your-server/test-lua
Blocked requests will appear in /var/log/nginx/waf/ as JSON logs, including timestamp, IP, URI, rule matched, and request headers.
Testing and Validation
Test each rule category:
- SQL Injection:
http://server/test-lua?id=1%20UNION%20SELECT%201,2,3 - Path Traversal:
http://server/test-lua?file=../../../../etc/passwd - Bad User-Agent:
curl -A "sqlmap" http://server/test-lua - Blacklisted IP: Add attacker IP to
blacklistfile - Whitelisted IP: Whitelist your dev IP to bypass rules
Verify logs are written and rules trigger correctly before enabling in production.
Alternative: OpenResty
For simplified deployment, use OpenResty — a pre-packaged Nginx with LuaJIT and essential modules:
yum install -y readline-devel pcre-devel openssl-devel
cd /usr/local/src
wget https://openresty.org/download/openresty-1.21.4.2.tar.gz
tar -xzf openresty-*.tar.gz
cd openresty-*/
./configure --prefix=/opt/openresty --with-luajit --with-http_stub_status_module
gmake && gmake install
ln -sf /opt/openresty /opt/openresty-current
Deploy the same WAF structure under /opt/openresty-current/conf/waf/ and reference it in the Nginx config.
Operational Best Practices
- Deploy in monitoring mode first — log all blocked requests without enforcement
- Use configuration management tools (e.g., Ansible, SaltStack) to sync rules across clusters
- Regularly update rule sets from trusted sources (e.g., ModSecurity rules)
- Combine WAF with application-level input validation — WAF is not a substitute for secure coding
- Monitor log volume and false positives — tune rules to avoid blocking legitimate traffic