Building a Web Application Firewall with Nginx and Lua

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 file
  • waf/init.lua — initializes shared memory and rule loaders
  • waf/waf.lua — main logic handler
  • waf/wafconf/ — rule sets:
  • args — URL parameter filtering
  • url — URI pattern matching
  • post — POST body inspection
  • user-agent — user-agent blocking
  • whitelist — exempted URLs/IPs
  • blacklist — 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:

  1. Check if client IP is in whitelist → allow
  2. Check if cleint IP is in blacklist → block
  3. Inspect User-Agent headers against known malicious patterns
  4. Validate URL path against regex rules (e.g., /etc/passwd)
  5. Scan query parameters for SQLi/XSS patterns (e.g., union select)
  6. Analyze POST bodies for malicious payloads
  7. Check cookies for session tampering indicators
  8. Apply CC rate limiting via lua_shared_dict (tracks request counts per IP)
  9. 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 blacklist file
  • 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

Tags: nginx Lua WebApplicationFirewall CCAttack OpenResty

Posted on Fri, 03 Jul 2026 17:00:20 +0000 by EriRyoutan