Benchmarks

What it costs, and what it catches.

Light WAF against no WAF and against the reference open-source WAF, ModSecurity v3 with the OWASP Core Rule Set — same machine, same CPUs, pinned images. One script reproduces every number on this page, and the caveats are part of the result.

+0.30 msadded p99 latency, clean GET
12.5×the throughput of ModSecurity + CRS on 2 CPUs
20.77MiBmemory under load (ModSecurity: 102.8MiB)
96.0%gotestwaf attacks blocked, strict profile (default: 76.4%)

Run of 2026-10-28 on an AMD Ryzen 7 5800X 8-Core Processor, Docker 29.9.2 linux/amd64. Each WAF is pinned to the same 2 CPUs; the backend and the load generator run on other cores. Full setup below.

Overhead

Latency and throughput.

Latency is measured at a fixed 250 requests per second, a load every target sustains; added p99 is the target's p99 minus the p99 without a WAF. Throughput is the most the target serves with 64 connections and no rate limit.

Clean GET

A browser-like GET with a query string and eight headers, a cookie among them.

Targetp50p99p99.9added p99max req/svs no WAF
No WAF0.13 ms0.27 ms0.42 ms–50,397–
Light WAF · PL2, threshold 50.35 ms0.56 ms0.90 ms+0.30 ms14,05527.9%
Light WAF strict · PL4, threshold 30.36 ms0.66 ms1.06 ms+0.40 ms13,65627.1%
ModSecurity + CRS · PL1, threshold 51.76 ms2.82 ms4.01 ms+2.55 ms1,1202.2%
ModSecurity + CRS · PL2, threshold 52.40 ms3.81 ms14.9 ms+3.54 ms8001.6%

Clean POST

The same headers plus a ~400-byte JSON order body.

Targetp50p99p99.9added p99max req/svs no WAF
No WAF0.13 ms0.23 ms0.50 ms–50,022–
Light WAF · PL2, threshold 50.40 ms0.62 ms0.99 ms+0.39 ms12,33224.7%
Light WAF strict · PL4, threshold 30.41 ms0.69 ms1.04 ms+0.46 ms12,15324.3%
ModSecurity + CRS · PL1, threshold 52.82 ms7.43 ms16.8 ms+7.19 ms6441.3%
ModSecurity + CRS · PL2, threshold 54.50 ms13.1 ms25.6 ms+12.8 ms4400.9%

Attack

A GET carrying a SQL injection. Every WAF blocks it, so this is the cost of a deny.

Targetp50p99p99.9added p99max req/svs no WAF
No WAF0.13 ms0.24 ms0.44 ms–51,131–
Light WAF · PL2, threshold 50.22 ms0.45 ms0.77 ms+0.21 ms20,39739.9%
Light WAF strict · PL4, threshold 30.22 ms0.43 ms0.70 ms+0.19 ms20,23539.6%
ModSecurity + CRS · PL1, threshold 51.45 ms2.32 ms3.95 ms+2.08 ms1,3052.6%
ModSecurity + CRS · PL2, threshold 52.12 ms4.31 ms7.88 ms+4.07 ms8571.7%

Memory is each WAF container's usage after its load runs:Light WAF · PL2, threshold 5 20.77MiB, Light WAF strict · PL4, threshold 3 25.85MiB, ModSecurity + CRS · PL1, threshold 5 102.8MiB, ModSecurity + CRS · PL2, threshold 5 103.1MiB.

Detection

Attacks blocked, legitimate traffic let through.

Measured with gotestwaf: its attack sets (SQLi, XSS, traversal, RCE, SSTI, XXE, LDAP, NoSQL, mail and SSI injection, CRLF, …, each through several encodings and placements) and its set of legitimate requests that look suspicious. A403 or a 400 counts as blocked for every WAF.

TargetAttacks blockedLegitimate passedgotestwaf score
Light WAF · PL2, threshold 576.4% (516/675)100.0% (141/141)65.7%
Light WAF strict · PL4, threshold 396.0% (648/675)100.0% (141/141)99.0%
ModSecurity + CRS · PL1, threshold 549.2% (332/675)90.8% (128/141)63.5%
ModSecurity + CRS · PL2, threshold 554.7% (369/675)64.5% (91/141)65.4%
ModSecurity + CRS strict · PL4, threshold 375.4% (509/675)0.0% (0/141)68.7%

gotestwaf's overall score averages application and API security; its GraphQL and gRPC sets are not run here (no such backend), so the API part rests on a handful of REST and SOAP cases.

Attacks blocked, per gotestwaf test case
Test caseLight WAF · PL2, threshold 5Light WAF strict · PL4, threshold 3ModSecurity + CRS · PL1, threshold 5ModSecurity + CRS · PL2, threshold 5ModSecurity + CRS strict · PL4, threshold 3
community/community-128kb-rce1/11/11/11/11/1
community/community-128kb-sqli1/11/11/11/11/1
community/community-128kb-xss1/11/11/11/11/1
community/community-16kb-rce1/11/11/11/11/1
community/community-16kb-sqli1/11/11/11/11/1
community/community-16kb-xss1/11/11/11/11/1
community/community-32kb-rce1/11/11/11/11/1
community/community-32kb-sqli1/11/11/11/11/1
community/community-32kb-xss1/11/11/11/11/1
community/community-64kb-rce1/11/11/11/11/1
community/community-64kb-sqli1/11/11/11/11/1
community/community-64kb-xss1/11/11/11/11/1
community/community-8kb-rce1/11/11/11/11/1
community/community-8kb-sqli1/11/11/11/11/1
community/community-8kb-xss1/11/11/11/11/1
community/community-lfi8/88/88/88/88/8
community/community-lfi-multipart0/20/22/22/22/2
community/community-rce4/44/42/44/44/4
community/community-rce-rawrequests2/33/33/33/33/3
community/community-sqli12/1212/1212/1212/1212/12
community/community-user-agent9/99/96/96/96/9
community/community-xss82/10499/10498/10498/104104/104
community/community-xxe2/22/20/20/22/2
owasp-api/non-crud2/22/22/22/22/2
owasp-api/rest3/77/74/75/77/7
owasp-api/soap1/55/52/53/55/5
owasp/crlf6/76/76/76/77/7
owasp/ldap-injection22/2422/242/244/2418/24
owasp/mail-injection20/2420/243/246/2415/24
owasp/nosql-injection20/5050/5012/5021/5033/50
owasp/path-traversal20/2020/206/208/2014/20
owasp/rce6/66/62/63/65/6
owasp/rce-urlparam9/99/93/99/99/9
owasp/rce-urlpath3/33/31/32/32/3
owasp/shell-injection24/3232/326/3210/3224/32
owasp/sql-injection45/4845/4820/4820/4832/48
owasp/ss-include24/2424/2412/2412/2415/24
owasp/sst-injection24/2424/2411/2412/2415/24
owasp/xml-injection7/77/73/73/77/7
owasp/xss-scripting146/224214/22491/22495/224143/224
Read this first

What these numbers do and don't say.

Light WAF was tuned on gotestwaf

Its detection modules were developed by running gotestwaf and closing the bypasses it found; the CRS was not. That favours Light WAF by construction — read its gotestwaf score as a regression measure more than an independent ranking. An independent dataset is on the to-do list.

Paranoia levels don't line up

PL2 in Light WAF is not PL2 in the CRS: the two rule sets grade their rules differently. Several CRS levels are shown, and both WAFs run their documented settings with no rule exclusions.

Strict trades false positives for coverage

gotestwaf's legitimate set is small (141 requests), so a clean score there is not zero false positives on real traffic. Paranoia 4 with threshold 3 blocks on accumulated weak signals: run it in detection-only mode on your traffic first. The strict CRS, with no exclusions, blocks even the clean benchmark requests, so it appears in the detection results only.

One ModSecurity stack

The numbers are those of ModSecurity v3 — libmodsecurity 3.0.17 with the nginx connector 1.0.4 and CRS 4.30.0, from the official owasp/modsecurity-crs image — not of ModSecurity in general. ModSecurity v2 on Apache, or the CRS on another engine, will perform differently.

Requests only

Light WAF does not inspect responses, so ModSecurity's response-body inspection is off. Both keep their shipped logging. Nothing is tuned to win.

The machine matters

Absolute numbers depend on the CPU, the CPU sets and on Docker running in a VM or natively. Compare targets within one run. This is a WAF's own cost on a loopback network — no TLS, no real application.

Corrections welcome. Maintain one of the compared projects, or think the setup is unfair to it? Open an issue: we'll fix the setup, re-run, and say on this page what changed.

Setup

The run, exactly.

Reproduce it with cd bench && ./run.sh — Docker and bash are the only requirements. The methodology describes every step.

HostAMD Ryzen 7 5800X 8-Core Processor · 16 CPUs visible to Docker · Docker Desktop · kernel 6.18.54.1-microsoft-standard-WSL2
CPU setsWAF 0-1 · backend 2 · load generator 3-5
Load30s per run after a 5s warm-up · 64 connections · fixed-rate runs at 250 req/s with coordinated-omission correction
backendnginx:1.27.5-alpine 6769dc3a703c
lightwafghcr.io/0x00spor3/waf-enterprise:1.0.1 0d1e085d70e9
lightwaf-strictghcr.io/0x00spor3/waf-enterprise:1.0.1 0d1e085d70e9
modsec-pl1owasp/modsecurity-crs:4.30.0-nginx-202610051210 c771ea3ed896
modsec-pl2owasp/modsecurity-crs:4.30.0-nginx-202610051210 c771ea3ed896
modsec-strictowasp/modsecurity-crs:4.30.0-nginx-202610051210 c771ea3ed896
loadgenghcr.io/hatoo/oha:1.16.0 829061114b87
gotestwafwallarm/gotestwaf:0.5.8 1f8556919f24