Twenty-request result matrix showing matching ModSecurity and Coraza HTTP 200 and 403 outcomes for clean, SQLi, XSS, query, and JSON cases.
Both fixed stacks matched all 20 expected decisions. This is evidence of behavior on the selected matrix, not a universal accuracy ranking.

    Lab status

    Product
    ModSecurity + NGINX vs Coraza + Caddy
    Status
    Completed hands-on test
    Updated
    2026-07-26

    Evidence policy

    WAFWiki separates verified smoke-test results from architecture diagrams and methodology notes. A smoke test proves one narrow local path; it does not claim full production readiness.

    Environment

    Reproducibility notes

    Host runtime

    Docker Desktop 29.2.0 on macOS, aarch64, 10 CPUs, 7.65 GiB assigned

    Shared rule set

    OWASP CRS 4.25.0, blocking paranoia level 1, inbound anomaly threshold 5

    ModSecurity stack

    ModSecurity 3.0.16, ModSecurity-nginx 1.0.4, NGINX image pinned by tag and digest

    Coraza stack

    Coraza 3.7.0, coraza-caddy 2.5.0, Caddy 2.11.2

    Shared upstream

    mccutchen/go-httpbin:2.21.0, image digest sha256:809250d14e94397f4729f617931068a9ea048231fc1a11c9e3c7cb8c28bbab8d

    Request corpus

    Ten cases per engine: six clean, two SQLi-like, and two XSS-like decisions

    Commands

    How this lab can be reproduced

    Reproduce the complete matrix

    scripts/run-waf-evidence-lab.sh

    The script exits zero only when every observed status matches the expected status column.

    Find any status mismatch

    awk -F, 'NR == 1 || $6 != $5 { print }' \
      docs/lab-evidence/waf-engine-comparison-2026-07-26-requests.csv

    Only the CSV header is printed because the final run contained no mismatch.

    Review the pinned environment

    cat docs/lab-evidence/waf-engine-comparison-2026-07-26.json

    The JSON records host details, component versions, image IDs, results, samples, resource snapshots, and limitations.

    Observed result

    What the hands-on test showed

    Whole matrix

    20 / 20 matched

    Both stacks produced the expected HTTP status for every declared case.

    Clean decisions

    12 / 12 returned 200

    Each engine allowed the same six representative clean inputs.

    SQLi-like decisions

    4 / 4 returned 403

    Query and JSON SQLi-like strings were blocked by both stacks with CRS 942100 evidence.

    XSS-like decisions

    4 / 4 returned 403

    Query and JSON script-tag strings were blocked by both stacks with CRS 941100 evidence.

    Winner from this matrix

    Not established

    Matching outcomes on 20 requests do not prove equal coverage, false-positive rates, throughput, or operational fit.

    shared rules: OWASP CRS 4.25.0
    shared upstream: go-httpbin 2.21.0
    ModSecurity: 10/10 expected statuses
    Coraza: 10/10 expected statuses
    clean: 12 HTTP 200 decisions across both stacks
    controlled SQLi/XSS: 8 HTTP 403 decisions across both stacks

    Evidence context

    The matrix controlled the rule set, upstream, requests, and evidence format.

    OWASP CRS 4.25.0 was used on both paths. The test intentionally did not hide the remaining architectural difference: ModSecurity ran with NGINX while Coraza ran with Caddy.

    Versioned WAF test topology with one client, two WAF stacks, one shared upstream, and CSV JSON log evidence outputs.

    Open evidence

    Download the sanitized result files.

    The files expose the status matrix and versioned summary without publishing raw container logs or unrelated local data.

    Download evidence summary JSON

    Pinned image IDs, component versions, host details, request results, latency samples, resource snapshots, and limitations.

    Limitations

    What this result does not prove

    Related research

    Continue researching ModSecurity + NGINX vs Coraza + Caddy