Karna is an OWASP Core Rule Set-compatible WAF engine that runs natively inside
Kong Gateway, no Apache, no separate ModSecurity daemon, no extra hop. This benchmark measures it
against the two reference ModSecurity stacks (Apache + ModSecurity 2 and
Nginx + ModSecurity 3) and OWASP Coraza (the Go WAF, on Caddy), on identical hardware (Hetzner CCX dedicated, 2 vCPU each),
the same OWASP CRS Paranoia Level 1 ruleset, and identical traffic generated with
k6 at 20 virtual users. Every figure is the best of five warm runs. On every request all four WAFs
returned the same HTTP status, full detection parity, so the numbers compare throughput, not leniency. Each scenario
reports sustained requests per second; higher is better.
Headline results
Real-world traffic
1569rps
vs Nginx + ModSec3+24%
vs Apache + ModSec2+156%
vs Caddy + Coraza+365%
Blocking attacks
3326rps
vs Nginx + ModSec3+105%
vs Apache + ModSec2+290%
vs Caddy + Coraza+483%
API with attacks
815rps
vs Nginx + ModSec3+19%
vs Apache + ModSec2+329%
vs Caddy + Coraza+344%
Overview
Requests per second, all scenarios
All four WAFs on identical hardware, OWASP CRS PL1. Scenario 06 reflects Karna's early-exit block path. Higher is better.
Karna
Nginx + ModSec3
Apache + ModSec2
Caddy + Coraza
Karna ÷ Nginx + ModSec3
Ratio per scenario. 1.0 = parity, above 1 = Karna faster.
Karna ÷ Apache + ModSec2
Ratio per scenario. 1.0 = parity, above 1 = Karna faster.
Karna ÷ Caddy + Coraza
Ratio per scenario. 1.0 = parity, above 1 = Karna faster.
Scenario detail
Karna1542 rps
Nginx + ModSec31263 rps
Apache + ModSec2643 rps
Caddy + Coraza377 rps
Payload
GET with 3 query args; only the page param varies per VU. ~90 B request, no body, the minimal benign request.
What it tests
Hot-path efficiency. Karna's per-request value caches (RE2::Set gate, transform memo, resolve-once arg cache) stay warm when args repeat. This is the engine's overhead floor on the most common pattern: cacheable benign GET traffic.
Karna1310 rps
Nginx + ModSec31139 rps
Apache + ModSec2392 rps
Caddy + Coraza319 rps
Payload
GET with 5 fully random query args on every request. No body, no two requests sharing a value.
What it tests
Worst case for caching: every value is unseen, forcing full CRS evaluation from scratch each time. The honest no-cache number, realistic for high-cardinality APIs. The gap from Scenario 01 isolates the caching contribution.
Karna20.1 rps
Nginx + ModSec314.4 rps
Apache + ModSec21.9 rps
Caddy + Coraza1.8 rps
Payload
POST with 950 form fields, 100 B each. ~103 KB body, at the edge of ModSec's SecArgsLimit.
What it tests
The rules × args scaling cost: body parse, flatten into a 950-entry arg table, per-arg evaluation. Apache+ModSec2's interpreted PCRE collapses to ~2 rps. Karna's compiled RE2 + Aho-Corasick engine sustains 11× the throughput, and leads Nginx+ModSec3 by 40%.
Karna228 rps
Nginx + ModSec3220 rps
Apache + ModSec2201 rps
Caddy + Coraza107 rps
Payload
POST with a single JSON object nested 400 levels deep. ~2.4 KB body, depth is the stress, not size.
What it tests
JSON parser nesting-depth handling and recursive flatten. All three converge near 200-228 rps: the ceiling is parse depth, not engine speed. A genuine tie, any WAF here handles deeply-nested JSON at roughly the same rate.
Karna541 rps
Nginx + ModSec3580 rps
Apache + ModSec2486 rps
Caddy + Coraza277 rps
Payload
POST multipart/form-data: 2 text fields + 3 file parts (10 KB each). ~30 KB body, a realistic file upload.
What it tests
Multipart parsing: boundary scan, per-part header parsing, file-content extraction and scanning. Karna's hardened parser closes ~12 known multipart bypass classes; the −7% vs Nginx is the cost of that added inspection. Still 11% ahead of Apache.
Karna3326 rps
Nginx + ModSec31623 rps
Apache + ModSec2852 rps
Caddy + Coraza570 rps
Payload
GET rotating 4 attack classes round-robin: XSS (941), SQLi (942), LFI (930), RCE (932) in the querystring. Every request expects a 403.
What it tests
Block-path throughput. Karna blocks on the first terminal rule hit and returns immediately; CRS anomaly scoring keeps evaluating the full ruleset to accumulate a score, even on obviously malicious requests. Karna absorbs roughly 2× the attack volume of Nginx before saturating.
Operational note
A WAF that clears only 852-1623 attack rps becomes the DoS target itself. A sustained XSS flood at 2000 rps overwhelms Apache+ModSec2 entirely; Karna carries that volume with headroom to spare.
Karna1569 rps
Nginx + ModSec31270 rps
Apache + ModSec2612 rps
Caddy + Coraza337 rps
Payload
70% benign GET, 20% benign urlencoded POST, 10% attack GET (XSS/SQLi/LFI). ~10% of requests blocked with 403.
What it tests
Pass-path and block-path interleaved in one run, the single figure closest to production traffic. Karna holds +24% vs Nginx and +156% vs Apache, consistent with the per-path results above.
Karna815 rps
Nginx + ModSec3688 rps
Apache + ModSec2190 rps
Caddy + Coraza184 rps
Payload
POST to /api: JSON body + querystring + Cookie + Authorization header on every request. 70% fully benign; 30% poison exactly one vector with SQLi/XSS/traversal/RCE.
What it tests
Full multi-vector inspection, body, query string, cookie and auth header scanned on every request, not just one parser. The most realistic modern-API scenario. Karna inspects all four surfaces and still leads Nginx by 19%; Apache's interpreted per-vector PCRE drops to 190 rps, 4.3× slower.