Library / SDK
ADD-SP/ngx_waf avatar
ADD-SP/ngx_waf

ngx_waf: a firewall compiled into Nginx, and its two release lines

Handy, High performance, ModSecurity compatible Nginx firewall module & 方便、高性能、兼容 ModSecurity 的 Nginx 防火墙模块

1,634 stars191 forksCBSD-3-Clause

At a glance

What is it?
ADD-SP/ngx_waf is a C module that filters requests inside the Nginx process, with its own directive set plus optional ModSecurity compatibility. The catch is that the two release lines do not ship the same features.
Who is it for?
ngx_waf fits teams that already compile Nginx from source and want request filtering without a second proxy in the request path. It does not fit anyone who cannot rebuild Nginx, or who needs one feature set across both release lines, because ModSecurity compatibility, captcha and crawler verification are marked as available only in the latest Current version.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 27 days ago.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Filtering happens inside the Nginx worker, not beside it

Most teams arrive at a web application firewall after an incident, and most of them end up with another process in front of Nginx. ngx_waf takes the other route. It is a C module built into Nginx, so a blocked request never reaches your application and never leaves the machine. The repository is laid out like a normal Nginx module: `src/`, `inc/`, a a `config` file, plus `flex/` and `bison/` directories for a request parser, and a `test/` tree. The presence of a lexer and a yacc grammar is the interesting part, because it means ngx_waf parses request components itself rather than only matching strings against a list.

The protection it offers by default is basic by design: IP and IP range black and white lists, URI lists, and a request body black list. Both IPv4 and IPv6 are covered. What sits on top of that is SQL injection and XSS detection powered by libinjection, and, in the newest line, compatibility with ModSecurity so the OWASP ModSecurity Core Rule Set can be reused. The README describes the module's own list directives as readable configuration rather than generated blobs, which matters for anyone who has to review a firewall config during an audit.

The cost of putting the filter inside the worker process is that you can no longer update it without rebuilding and reloading Nginx, and you inherit whatever Nginx build flags the module was compiled with.

Two release lines, and features that only exist in one of them

The release list is short and the naming is the part worth reading twice. There is an LTS tag, `v6.1.10 LTS`, dated 2025-01-25, and a Current tag, `v10.1.2 Current`, dated 2022-07-09. So the more recent tag by name carries a date more than two years older than the LTS tag. The README repeats a specific caveat three times, and it is the single most important operational fact here: ModSecurity compatibility, captcha support, and authentication-friendly crawler support are each described as available only in the latest Current version.

That splits the project cleanly. If you are on the LTS line you get the directive-based blocking set, libinjection-backed SQL injection and XSS checks, IPv4 and IPv6, and the anti Challenge Collapsar behaviour. If you are on Current you additionally get the ModSecurity rule engine, hCaptcha plus reCAPTCHAv2 and reCAPTCHAv3 challenges, and the crawler verification path.

Crawler verification is worth spelling out because it is a design choice rather than a feature checkbox. The module identifies Google, Bing, Baidu and Yandex crawlers by user agent and IP and lets them through automatically, on the stated goal of avoiding false positives against search engines. Automatic allow decisions based on a spoofable header are exactly the thing a security reviewer will want to read about, so treat this as a decision to confirm with your own logging rather than a default to accept.

The block and allow surface: what each directive actually keys on

Reading the feature list closely gives you the shape of the request inspection. ngx_waf can block a specific IP address, block a specific URL, block a specific query string, block a specific UserAgent, block a specific Cookie, and block a specified request body. Each of those has a matching exceptional allow: a specific IP address can be excepted, a specific URL can be excepted, and a specific Referer can be excepted. Referer appears on the allow side only; the block side lists Referer too.

That gives you the core control model: a default-deny list plus a narrow escape hatch, keyed on whichever part of the request carries the signal you care about. Blocking by query string is the one to think hardest about, because a query string is attacker controlled and also the easiest part of a legitimate request to vary. Blocking by UserAgent is the easiest to evade. Neither observation means the directive is wrong; both mean the directive is a first filter and not a decision.

The README does not enumerate the directive names, their block syntax, or the parameters each one accepts. That reference lives in the documentation, and the README lists three mirrors of it: docs.addesp.com, add-sp.github.io/ngx_waf-docs/, and ngx-waf-docs.pages.dev. Go there before writing a single line of configuration, because the directive vocabulary is not inferable from the feature bullet list.

Captchas as a response, not a redirect

On the Current line, ngx_waf can serve a captcha challenge instead of dropping the request outright. Three providers are named: hCaptcha, reCAPTCHAv2, and reCAPTCHAv3.

The README does not document how a challenge is configured, which site keys are used, how the provider response is verified, or what happens to a client that never returns from the challenge. No configuration snippet exists in the repository text to copy. This is the weakest documented area of the project and it deserves a specific check before you rely on it: confirm that the challenge is served before the request reaches your upstream, and confirm what the module does when the provider call fails, because a captcha gateway that fails open is worse than no captcha gateway.

Running the project's own test suite against your build

ngx_waf ships a Perl driven test suite built on Test::Nginx, the data driven scaffold used for Nginx C module and OpenResty library development. The README gives the full sequence, and it is worth running if you compile the module yourself, because it exercises the module against a real Nginx rather than a mock. The declared test cases live in the `test/` tree under `test/test-nginx`, where the files carry a `.t` extension.

Here is the sequence exactly as the README gives it:

shell
## It will take a lot of time, but it only needs to be run once.
cpan Test::Nginx

# You need to specify a temporary directory.
export MODULE_TEST_PATH=/path/to/temp/dir

export MODULE_PATH=/path/to/ngx_http_waf_module.so

cd ./test/test-nginx
sh ./init.sh
sh ./start.sh ./t/*.t

Two conditions are worth noting. First, the README warns that the run takes a long time, which is normal for this scaffold. Second, parts of the suite require the standard `proxy`, `rewrite` and SSI modules to be enabled when you build Nginx. If your build is minimal, some cases will fail for reasons that have nothing to do with the firewall code, so rebuild with those modules before you read a failure as a bug in ngx_waf.

The `MODULE_PATH` line is optional and only applies when you have the dynamic module installed. The README also warns that `MODULE_TEST_PATH` is destructive: if the directory exists it is removed and recreated.

The parser is generated, so a clean build needs flex and bison

The top level Makefile has a single target, and it explains how the request grammar gets built. The target depends on the lexer and the yacc grammar, then runs flex over `flex/lexer.lex` and bison over `bison/parser.yacc` with the generated header landing at `inc/ngx_http_waf_module_parser.tab.h` and the C file at `src/ngx_http_waf_module_parser.tab.c`.

makefile
parser: flex/lexer.lex bison/parser.yacc
	@flex flex/lexer.lex
	@bison --defines=inc/ngx_http_waf_module_parser.tab.h -L C -o src/ngx_http_waf_module_parser.tab.c bison/parser.yacc

This is the sort of detail that separates a project you can build in a container from one you can only build on a prepared image. flex and bison are build time dependencies for anyone regenerating the parser. Distribution packaging that ships the generated files avoids them; a from source workflow that always regenerates does not.

The runtime side tells a similar story. The credits list libcurl for multiprotocol transfer, cJSON for JSON parsing, libsodium for crypto, and uthash for hash tables. That is a real dependency surface to audit for a component sitting in front of every request, and it is spread across several vendored C libraries rather than a small standard library dependency.

Where it stops short, and what to use instead

The clearest limitation is the LTS versus Current split described earlier. An organisation that standardises on one release line cannot assume a feature from the README's feature list is present, because three headline items are scoped to Current alone. The second limitation is the deployment model: this is not a packaged Nginx module from a distribution repository, so every environment you run needs a custom build and a custom reload procedure.

The real alternative is a separate WAF layer. ModSecurity is the project whose rules and Core Rule Set ngx_waf can reuse in its Current version. You give up the in process speed advantage and gain an upgrade path that does not require recompiling the proxy. The second alternative named in the README is ngx_lua_waf, a WAF built on lua-nginx-module under OpenResty. Same data path, different language: you write the logic in Lua instead of C, which makes rules editable without a compiler in the loop but ties you to an OpenResty deployment.

For teams that must run a large, frequently revised rule set such as the OWASP Core Rule Set, that last distinction usually decides the question before performance does.

Maintenance pace, licence, and what an upgrade costs

The repository is not archived and the last push was on 2026-09-26, so the source is moving. The release tags tell a different story: the newest tagged Current build is dated 2022-07-09 while the LTS tag is dated 2025-01-25, so a recent commit does not by itself mean a recent release you can pin. Before adopting, find out how the project intends Current and LTS to relate, because that determines whether you are waiting for tagged releases or tracking master.

Licensing is BSD 3-Clause. For an organisation choosing a firewall, that is the permissive end of the scale and carries none of the copyleft obligations that would affect how you link it. That is a factual note about the licence identifier, not advice on how to comply with it; your own review process still applies.

The upgrade cost is dominated by rebuild and configuration migration rather than by the module itself. If you configure it with generated rules, plan for regenerating them against each new version and for re-running the Test::Nginx suite on the new build before it reaches a production worker.

Editorial conclusion

ngx_waf fits teams that already compile Nginx from source and want request filtering without a second proxy in the request path. It does not fit anyone who cannot rebuild Nginx, or who needs one feature set across both release lines, because ModSecurity compatibility, captcha and crawler verification are marked as available only in the latest Current version. Before adopting it, check the documentation site listed in the README for the full directive reference and confirm which branch your build actually contains.

Frequently asked questions

What is ngx_waf and does it replace ModSecurity?

ngx_waf is a C module for Nginx that blocks requests by IP, URL, query string, UserAgent, Cookie, Referer and body, and adds libinjection based SQL injection and XSS checks. It does not replace ModSecurity so much as offer a compatible mode in its latest Current version, where the OWASP ModSecurity Core Rule Set can be used.

Does the LTS version of ngx_waf support captcha and crawler verification?

The README states that captcha support (hCaptcha, reCAPTCHAv2, reCAPTCHAv3) and authentication friendly crawler support are available only in the latest Current version. The listed LTS tag is v6.1.10 dated 2025-01-25, so those three features should not be assumed present there.

How do I test ngx_waf against my own Nginx build?

The project ships a Perl test suite based on Test::Nginx. Install Test::Nginx with cpan, set MODULE_TEST_PATH to a temporary directory and optionally MODULE_PATH to the dynamic module, then run sh ./init.sh and sh ./start.sh ./t/*.t inside test/test-nginx. Parts of the suite need the proxy, rewrite and SSI modules enabled when building Nginx.

Official sources

  1. ADD-SP/ngx_waf on GitHub
  2. License: BSD-3-Clause
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/add-sp-ngx-waf.svg)](https://hysenlabs.com/projects/add-sp-ngx-waf)

Was this useful?