Web application firewalls in 2026: revisit ModSecurity, OWASP Coraza and BunkerWeb before you put another WAF in front of your app
A web application firewall is one of those things that gets added after a bad week. A penetration test report arrives, or a customer sends a security questionnaire with a line that says “Do you use a WAF?”, and somebody switches one on. A month later, a genuine customer cannot place an order because the word “select” appeared in a delivery note, the WAF is quietly put in detection mode, and nobody looks at it again. The engine was never the problem. The rules, the tuning and the ownership were.
This post compares three open-source ways to run a WAF yourself in 2026: ModSecurity, the original engine, now looked after by OWASP; OWASP Coraza, a Go engine that speaks the same rule language; and BunkerWeb, an NGINX-based reverse proxy that packs ModSecurity and the OWASP Core Rule Set (CRS) with other protections and a web UI. The common thread is CRS. All three, in practice, run the same rule set, so most of what you learn about tuning CRS carries over whichever engine you pick.
A WAF sits on a proxy, so this post builds on reverse proxies in 2026, which compared NGINX, Caddy and HAProxy, and on API gateways in 2026 for authentication and rate limits at the edge. If you run Kubernetes and still have the old ingress controller, read ingress-nginx retirement first, because its ModSecurity option goes away with it. A WAF watches HTTP requests; for what happens inside the container after a request gets through, see runtime security in 2026, and for finding the bug in code before it ships, static code analysis in 2026.
We end with a small drill that ran Coraza with CRS in-process at three paranoia levels, and a plain list of what we did not run.
A scenario to keep us honest
Imagine a company in Belagavi that runs an online ordering and distributor portal for regional food brands: sweet shops, dairies and bakeries across north Karnataka and Goa. The company is invented for this post. It has about 25 engineers and a two-person platform team.
The setup today:
- A PHP storefront where shoppers order, a Node.js JSON API used by the mobile app and by distributors, and an older WordPress marketing site with a blog and a careers page.
- Two VMs running NGINX in front of the storefront and the API, with TLS, caching and a few rate limits. The platform team knows NGINX well.
- The WordPress site runs on a separate VM with Apache and an old ModSecurity 2.9 setup that someone installed years ago with CRS 3.3. It runs in detection-only mode and its audit log fills a disk every few months.
- A small Kubernetes cluster for new services, still using the community ingress-nginx controller.
Four things push the team to revisit this. First, a large retail chain that wants to stock the brands sends a vendor security questionnaire, and two questions ask about a WAF and about protection against the OWASP Top 10. Second, last quarter a researcher reported an SQL injection in an old distributor report endpoint; it was fixed in code, but the team wants a second layer while it reviews similar endpoints. Third, the login page sees bursts of credential stuffing every few weeks. Fourth, CRS 3.3 on the WordPress box reached end of life in the third quarter of 2026, and the ingress controller in Kubernetes is retired.
We will come back to Belagavi in the worked plan.
The short version
- ModSecurity is the original open-source WAF engine. Version 3 (libmodsecurity) is a C++ library used through connectors, mainly NGINX. Version 2 is the Apache module and is still maintained. Since January 2024 the project has been under OWASP, after Trustwave handed it over. Both lines got security releases on 30 September 2026 (3.0.17 and 2.9.15).
- OWASP Coraza is a WAF engine written in Go that reads the same SecLang rules as ModSecurity and says it is fully compatible with CRS 4. It is a library first, with plug-ins for Caddy, HAProxy (through SPOE), Envoy (through proxy-wasm) and others. The latest release, 3.8.1 on 2 October 2026, is a security release that every user is asked to install.
- BunkerWeb is an NGINX-based reverse proxy that ships ModSecurity 3 with CRS switched on by default, plus bot challenges, bans, rate limits, security headers, automatic certificates and a web UI. It is AGPL 3.0 with a paid PRO edition. The latest stable is 1.6.17, released on 21 October 2026.
- OWASP CRS is the rule set behind all three. CRS 4.25 is the first long-term support line for CRS 4, with security fixes until the third quarter of 2027. CRS 3.3 is end of life.
- Your choice of engine follows your proxy. If you run NGINX, ModSecurity 3 is the natural fit. If you run Caddy, HAProxy or Envoy, Coraza is. If you want a separate, all-in-one protective proxy with a UI, BunkerWeb is. In every case, the work that decides success is CRS tuning: paranoia level, anomaly threshold and narrow rule exclusions.
Where things stand on 29 October 2026
ModSecurity. The latest releases are 3.0.17, published on GitHub on 30 September 2026 at 2:31 AM IST, and 2.9.15, published on the same day at 2:17 AM IST. Both are mainly security releases. The 3.0.17 notes list fixes for an HTML entity decoding gap, host verification when downloading remote rules, a pointer problem in the XML body processor, a response body inspection bypass with a mixed-case Content-Type value, a crash on invalid regular expressions, and multipart filename handling, with new multipart variables added. Earlier releases this year came in April and at the end of June or early July. The NGINX connector, ModSecurity-nginx, is at 1.0.4, released on 22 May 2025 at 1:00 AM IST. The engine is under the Apache License 2.0.
OWASP Coraza. The latest release is 3.8.1, published on 2 October 2026 at 7:22 PM IST, two days after 3.8.0 (30 September 2026, 5:59 AM IST). The 3.8.1 notes call it a security release and say all users should upgrade, including those on 3.7 and earlier. It fixes two high severity issues with deeply nested JSON bodies (a process crash and a memory exhaustion path) and completes several fixes from 3.8.0, which itself fixed issues such as audit log forgery through unescaped line breaks in the native log format and a multipart check that did not fire on truncated bodies. The integrations followed: the Caddy plug-in 2.6.2 (8 October 2026, 4:52 PM IST) and the HAProxy agent coraza-spoa 0.9.1 (23 October 2026, 12:28 PM IST) both moved to Coraza 3.8.1. The proxy-wasm filter for Envoy has not had a release since 0.6.0 in July 2025. Coraza is under the Apache License 2.0.
OWASP CRS. The latest stable release is 4.30.0, published on 3 October 2026 at 2:49 AM IST, and the latest long-term support release is 4.25.2, published at 2:36 AM IST on the same day. Both fix three security advisories: command injection payloads placed in the URL path were not inspected by the remote command execution rules, a mixed-case charset parameter could skip a charset check, and a second charset value in a multipart body could hide a disallowed one. 4.25.2 also allows and parses JSON content types such as application/something+json by default. CRS is under the Apache License 2.0, and releases are signed with the project’s GPG key.
BunkerWeb. The latest stable release is 1.6.17, published on 21 October 2026 at 9:33 PM IST, with a 1.6.18 release candidate on 26 October. Releases come often; 1.6.16 came out a week earlier, on 14 October. One fix in 1.6.17 is worth reading if you use it: a URI rewrite in a custom configuration no longer skips the access checks, including bans, rate limits, the bot challenge and ModSecurity. The 1.6.17 build bundles ModSecurity 3.0.17 and ModSecurity-nginx 1.0.4, and its bundled CRS setup file says CRS 4.29.0, one minor release behind CRS upstream. BunkerWeb is under the GNU AGPL 3.0.
The first question: what are you trying to stop?
Before choosing a WAF, write down what you expect it to do. A WAF is good at some jobs and poor at others.
A rule-based WAF with CRS is good at:
- Blocking common, generic attack payloads in requests: SQL injection, cross-site scripting, path traversal, remote file inclusion, command injection, and known scanner signatures.
- Enforcing basic HTTP hygiene: valid methods, sane headers, allowed content types, request size limits.
- Buying time. When a new vulnerability is announced in a library you use, a targeted rule at the edge can protect you while you patch.
- Giving you evidence. The audit log shows what was tried, from where, and which rule caught it.
It is poor at:
- Business logic abuse. A WAF does not know that a distributor should only see its own orders. Broken access control, the top item in the OWASP Top 10, is mostly invisible to it.
- Credential stuffing and bots. Each login attempt looks like a normal request. You need rate limits, bot challenges, multi-factor login and account lockout policies. BunkerWeb packs some of these; ModSecurity and Coraza by themselves do not.
- Volumetric denial of service. That is for your network provider or CDN.
- Encrypted or unusual payloads it cannot parse, and APIs whose bodies it was not told how to read.
So in Belagavi, the WAF can be a reasonable second layer for the injection class of bugs and can answer the questionnaire honestly. It will not, by itself, stop the credential stuffing. That needs rate limits and login changes, which we keep in the plan.
What each tool is
ModSecurity
ModSecurity started in 2002 as an Apache module. The OWASP Developer Guide dates its first release to November 2002, and the Coraza project credits Ivan Ristic with creating it. Over the years it gained its own rule language, SecLang, which describes what to inspect (variables such as ARGS or REQUEST_HEADERS), how to transform it (decode, lowercase), what to match (regular expression, phrase list, libinjection detection) and what to do (log, add to a score, block).
Today there are two lines:
- ModSecurity 2.9, the Apache module. It still gets security releases. If you run Apache httpd, this is still the line to use. The separate version 3 connector for Apache exists, but its repository has not seen changes since 2021.
- ModSecurity 3 (libmodsecurity), a rewrite as a library with no Apache dependency. A web server talks to it through a connector. The NGINX connector is the main one. The README describes it plainly: the library loads SecLang rules and applies them to HTTP data that the connector passes in.
A minimal setup with NGINX looks like this: build libmodsecurity and the ModSecurity-nginx module against your NGINX version, load the module, turn it on in a server block, and include a main configuration file that loads the recommended engine settings and then CRS.
load_module modules/ngx_http_modsecurity_module.so;
server {
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
}
The main.conf file then includes modsecurity.conf (engine settings such as SecRuleEngine and body limits), crs-setup.conf and the CRS rules folder. The hard part is not this file. It is building the module for your exact NGINX version, which is why many teams use a distribution package or a container image that already has it.
OWASP Coraza
Coraza is a WAF engine written in Go. Its repository was created in May 2020, and the project thanks Juan Pablo Tosso for starting it. It is an OWASP production project. Version 3.0.0 came out in May 2023. The README says it supports ModSecurity SecLang rule sets, is fully compatible with CRS 4, and is not compatible with older CRS versions.
Coraza is a library first. A Go program creates a WAF from directives, opens a transaction for each request, feeds it the connection, URI, headers and body, and checks whether there is an interruption. On top of that library, the project maintains integrations, and its README labels their maturity honestly:
- Caddy plug-in: stable, needs a maintainer.
- proxy-wasm filter for Envoy and other proxies with proxy-wasm support: stable, still under development.
- HAProxy SPOE agent (coraza-spoa): experimental.
- A C library, libcoraza, for NGINX and others: experimental.
- A Traefik plug-in through http-wasm: experimental, needs a maintainer.
Because it is Go, Coraza fits well inside a Go service or a Go proxy, with no C library to build. It also supports running in Go’s FIPS 140-3 mode, where two hash transformations stop working and the README tells you to review rules that depend on them.
BunkerWeb
BunkerWeb is not a new engine. It is a full reverse proxy based on NGINX, which ships ModSecurity 3 and CRS together with other protections, and manages them through settings, a scheduler and an optional web UI. The project started in 2019 under the name bunkerized-nginx; the old repository name now redirects to bunkerweb. It is under the GNU AGPL 3.0, and the company behind it sells a PRO edition, a managed cloud service and support.
Out of the box, BunkerWeb turns on ModSecurity with CRS 4 and its plug-ins, HTTPS with automatic Let’s Encrypt certificates, security headers, request and connection limits, automatic bans for clients that cause many errors, bot challenges (cookie, JavaScript, captcha and others) and external block lists. Configuration is a list of settings, for example:
SERVER_NAME=shop.example.in
USE_REVERSE_PROXY=yes
REVERSE_PROXY_HOST=http://storefront:8080
USE_MODSECURITY=yes
USE_MODSECURITY_CRS=yes
MODSECURITY_CRS_VERSION=4
USE_ANTIBOT=javascript
It integrates with plain Linux, Docker, Docker autoconf (labels on containers), Swarm and Kubernetes, where the autoconf service acts as an ingress controller. Its configuration state lives in a database: SQLite, MariaDB, MySQL or PostgreSQL. There is also an official plug-in to use Coraza instead of ModSecurity.
A word on open-appsec
If you search for open-source WAFs, open-appsec will appear. It is Apache 2.0 licensed, its repository describes a machine learning engine rather than a rule set, and its latest tag is 1.1.36 from August 2026. It is a different approach from the three above, which all revolve around CRS, so we do not compare it in depth here. If signature tuning is the reason you are unhappy with WAFs, it is worth a separate trial, but read carefully which parts are open source and which need a registered management service.
OWASP CRS: the common thread
Whichever engine you choose, you will most likely run CRS, and you will spend more time with CRS than with the engine. Three ideas matter.
Anomaly scoring. CRS rules do not block on their own. When a rule matches, it adds to an anomaly score for the request. After all request rules have run, a blocking evaluation rule compares the total with a threshold. The documented defaults are a score of 5 for a critical match, 4 for error, 3 for warning and 2 for notice, with an inbound threshold of 5 and an outbound threshold of 4. So one critical match blocks a request. The CRS documentation warns that raising the threshold above these defaults lets many attacks through, because some attack classes are covered by a single rule. Its recommended tuning method is the opposite of what many teams do: start in blocking mode with a very high threshold, so that almost nothing is blocked, and lower it step by step as you fix false positives.
Paranoia levels. CRS rules are grouped into four paranoia levels. Level 1 is meant for everyone and should rarely cause false positives. Level 2 adds rules suitable when real user data is involved, and you should expect to tune. Level 3 is described as online banking level, with many false positives. Level 4 is for the crown jewels, with a large number of false positives. Paranoia level and anomaly threshold are separate controls: one decides how many rules run, the other how many matches you allow. CRS also has an executing (detection) paranoia level. You can run level 2 rules for logging while still blocking only on level 1, find the new false positives, write exclusions, and then raise the blocking level.
Rule exclusions. A false positive is fixed by an exclusion: remove one rule, or one rule for one parameter, or for one URL. The narrower, the better. Turning off a whole rule category because one form field misbehaves leaves a hole everywhere.
Versions. CRS 4.0.0 was released on 14 February 2024 and brought plug-ins, the early blocking option and regular expressions compatible with the RE2 and Hyperscan engines. Minor releases have come about once a month or two since. On 21 March 2026 the project announced 4.25 as the first long-term support line for CRS 4. It gets security fixes, regression fixes and critical level 1 false positive fixes, but no new rules or features, with quarterly point releases and security fixes until the third quarter of 2027. The project’s security policy supports the two latest stable releases plus the 4.25 LTS line. CRS 3.3 reached end of life in the third quarter of 2026.
Side by side
Licence and governance
- ModSecurity: Apache 2.0. An OWASP project since the handover in January 2024, after Trustwave sponsored it from 2010 to mid-2024. Maintained by volunteers, several of whom also work on CRS and Coraza.
- Coraza: Apache 2.0. An OWASP production project, community maintained. Some integrations openly say they need maintainers.
- BunkerWeb: AGPL 3.0, maintained by a company that sells a PRO edition and services. PRO features are marked in the documentation and UI. AGPL matters if you modify BunkerWeb and offer it as a service to others; for internal use in front of your own apps, it is usually simple, but ask your legal team if you plan to resell.
- CRS: Apache 2.0, an OWASP project, with signed releases.
Where it plugs in
- NGINX: ModSecurity 3 with the ModSecurity-nginx connector is the mature path. BunkerWeb is itself NGINX. Coraza for NGINX through libcoraza is experimental.
- Apache httpd: ModSecurity 2.9.
- Caddy: Coraza plug-in, built into a custom Caddy binary.
- HAProxy: Coraza through the SPOE agent coraza-spoa, which runs as a separate process that HAProxy asks for a verdict. The Coraza README marks it experimental.
- Envoy and proxy-wasm hosts: the Coraza proxy-wasm filter.
- Kubernetes ingress: BunkerWeb can act as an ingress controller. Other controllers and gateways may embed ModSecurity or Coraza; check what your controller supports today, because the old ingress-nginx ModSecurity option is not a long-term answer.
- Inside a Go application: Coraza as a library or HTTP middleware.
Rule language and compatibility
ModSecurity and Coraza both read SecLang and run CRS. Compatibility is high but not perfect. CRS releases sometimes carry fixes that apply only to one engine (a CRS 4.30.0 change, for example, fixes a false positive specific to libmodsecurity 3 and Coraza with a JSON key name). Coraza supports CRS 4 only. When you move rules between engines, run the CRS regression tests (the go-ftw tool) or at least your own test requests.
BunkerWeb lets you choose CRS 3 or 4 through a setting; on new installs, use 4. Its custom configuration feature lets you add ModSecurity exclusions and rules in files.
Body parsing and APIs
Modern apps send JSON. A WAF that does not parse JSON either misses payloads inside it or treats the whole body as one long string and raises false positives. In ModSecurity and Coraza, the recommended engine configuration includes a rule that switches on the JSON body processor when the Content-Type is application/json. If you write your own minimal configuration and leave that rule out, the behaviour changes, as our drill showed. CRS 4.25.2 and 4.30.0 also allow and parse JSON subtypes such as application/something+json by default.
Watch body limits too. If a request body is larger than the inspection limit, the engine either rejects it or, with ProcessPartial, inspects only the first part. BunkerWeb’s documentation states the trade-off clearly: ProcessPartial helps with large uploads but reduces coverage for JSON and form data.
Operations and upgrades
- ModSecurity 3 with NGINX: the connector must be built for your NGINX version. Each NGINX upgrade can mean a rebuild. Distribution packages help, but they may lag behind security releases. Plan who rebuilds and how fast.
- Coraza with Caddy: you build Caddy with the plug-in (for example with xcaddy), so the Coraza version is fixed in your binary. When Coraza ships a security release, you need a new Caddy build. The 3.8.1 release reached the Caddy plug-in six days later.
- Coraza with HAProxy: one more process to run and monitor, the SPOA agent. If the agent is slow or down, your HAProxy and SPOE configuration decides whether traffic passes or fails, so decide that on purpose and test it.
- BunkerWeb: you upgrade one product that brings NGINX, ModSecurity, CRS and its own features together. That is simpler, but you take what the release bundles: on 29 October the stable release bundled CRS 4.29.0 while CRS upstream was at 4.30.0. You also run its scheduler, database and optionally the UI.
- CRS everywhere: pin a version, verify the signature, and upgrade on purpose. The LTS line is the calm choice for most teams.
Logging and evidence
All three write ModSecurity-style audit logs. Coraza and ModSecurity 3 can write JSON, which is much easier to ship to a log store. Audit logs can contain request bodies, which may include passwords and personal data, so choose the logged parts carefully and set retention. Coraza 3.8.0 fixed a log forgery issue in its native audit log format, which is one more reason to use JSON. BunkerWeb shows blocked requests and reports in its UI and can send notifications through plug-ins.
Security of the WAF itself
A WAF parses hostile input all day, so it has its own bugs. In the last month alone, ModSecurity, Coraza and CRS all published security fixes for bypasses or crashes, several of them in multipart and JSON parsing. A WAF that is two years old is not a security control, it is a false sense of one. Treat WAF engine and rule updates like OS security patches, with a target time to apply.
False positives and noise
False positives are the reason most WAFs end up in detection-only mode forever. The usual causes are:
- Free text fields: comments, delivery notes, product descriptions, support tickets. Quotes, semicolons, double hyphens and words like “select” or “from” look like SQL to a rule.
- Rich text editors that post HTML.
- Admin panels and CMS editors, such as WordPress, which post code and markup. CRS has plug-ins with application-specific exclusions for some popular applications.
- Long tokens, base64 blobs and API keys in headers.
- Missing body processors, as above.
The answer is the same in all three: start at paranoia level 1, log in detail, review matches by rule and by parameter, write narrow exclusions, and keep the list of exclusions in version control with a reason for each.
Performance
A WAF adds work to every request it inspects. How much depends on the rules enabled, the paranoia level, body sizes and which phases you inspect. Response body inspection is especially costly. We do not have independent benchmark numbers that compare the three, and we do not quote any here. Coraza’s own benchmark page says it is being renovated and that new results will be published. ModSecurity ships a small benchmark tool in its source tree that runs synthetic transactions with or without CRS. If performance matters, measure on your own traffic with your own rule set, and compare latency at the 99th percentile with and without the WAF. Treat any vendor’s benchmark as the vendor’s own run.
A worked plan for Belagavi
Step 1: Write down the goals. In plain words: block common injection payloads on the storefront and API as a second layer; give honest answers on the questionnaire; slow down credential stuffing on the login page; get the WordPress box off CRS 3.3. Write down what the WAF will not do: access control bugs and business logic stay in code review and testing.
Step 2: Fix login abuse separately. Add per-IP and per-account rate limits on the login endpoint at NGINX, account lockout with a cool-down, and multi-factor login for distributor accounts. This does more for credential stuffing than any SecLang rule.
Step 3: Choose the engine by proxy, with three paths on the table.
- Path A: ModSecurity 3 in the existing NGINX. The team knows NGINX. Use a package or a container image that ships the ModSecurity-nginx module for the exact NGINX version, with ModSecurity 3.0.17 or newer. Files live under /etc/nginx/modsec/: main.conf, modsecurity.conf, crs-setup.conf and the CRS folder.
- Path B: BunkerWeb as a separate protective proxy in front of the existing NGINX, or replacing it. It brings bot challenges and bans that would help with the login problem. But it means learning a new configuration model and running a scheduler and a database, and the team would lose some of its hand-written NGINX configuration.
- Path C: Coraza for the new Kubernetes services, through a gateway or proxy that embeds it, or through Caddy for a small service. This is worth evaluating when the team replaces ingress-nginx, not before.
Belagavi picks Path A for the storefront and API, keeps Path C as an input for the ingress replacement project, and decides against Path B for now because the team would rather add one module to a proxy it knows than replace the proxy. A team without deep NGINX knowledge could reasonably choose B.
Step 4: Install CRS 4.25 LTS, verified. Download the 4.25.2 release, verify the GPG signature against the CRS key, and unpack it under /etc/nginx/modsec/crs/. Copy crs-setup.conf.example to crs-setup.conf and keep it in Git. Choosing the LTS line means security fixes without monthly rule changes until the third quarter of 2027.
Step 5: Turn on JSON parsing and set limits. Make sure the JSON body processor rule from the recommended configuration is present for application/json. Set request body limits that match NGINX’s client_max_body_size, and decide where ProcessPartial is acceptable (large file uploads) and where it is not (JSON APIs).
Step 6: Start in blocking mode with a very high threshold. Set paranoia level 1, the inbound anomaly threshold to a high value such as 10000, and JSON audit logging for relevant requests only. Ship the audit log to the log store. Run for two weeks.
Step 7: Tune with narrow exclusions. Each week, group matches by rule id, URL and parameter. For each false positive, write the narrowest exclusion, for example removing rule 942440 only for the delivery note field on the order notes URL. Put each exclusion in /etc/nginx/modsec/exclusions-before-crs.conf with a comment that says why and who approved it. Lower the threshold step by step: 10000, then 100, then 20, then 10, then 5.
Step 8: Reach the default threshold of 5 at level 1, then consider level 2. For the distributor API, which handles business data, set the executing paranoia level to 2 while blocking at level 1. Review the extra matches for a month, add exclusions, then raise the blocking level.
Step 9: Migrate the WordPress box. Move the Apache site from CRS 3.3 to CRS 4.25 on ModSecurity 2.9.15. Use the CRS WordPress plug-in for known WordPress exclusions, restrict the admin area by IP, and start again from step 6 for that site. Rotate and cap the audit log so it stops filling the disk.
Step 10: Make upgrades routine. Subscribe to the ModSecurity, CRS and NGINX security advisories. Set a target of one week for WAF security releases. Keep a set of known-bad and known-good test requests and run them after every upgrade to confirm that attacks still block and normal orders still pass.
When each is better
- ModSecurity is better when you already run NGINX or Apache, want the most widely deployed engine with the longest history, and can manage module builds or have a trusted package source.
- Coraza is better when your edge is Caddy, HAProxy or Envoy, when you want to embed a WAF in a Go service, or when you want to avoid a C library. Check the maturity label of the integration you need before you depend on it.
- BunkerWeb is better when you want a separate protective reverse proxy that turns on sensible defaults in one go: WAF, bot challenges, bans, limits, headers and certificates, with a web UI for a small team. It suits teams that do not want to assemble those pieces by hand.
When not to change, or not to add a WAF
- Your app is internal only and behind single sign-on. A WAF adds little compared with fixing access and patching.
- Your provider or CDN already runs a managed WAF with CRS and you can set the paranoia level and threshold there. Check that it exposes these controls; the CRS documentation notes that some vendors set the threshold well above the default, which leads to bypasses.
- Nobody will own the tuning. A WAF in detection mode that nobody reviews is just a cost. A WAF in blocking mode that nobody tunes will block customers. If you cannot give it a few hours a week for the first two months, wait.
- Your current setup works and is current. If you run ModSecurity 3.0.17 with CRS 4.25 LTS, tuned, with a working upgrade routine, there is no reason to switch to Coraza or BunkerWeb just because they are newer.
- The real problem is elsewhere. Credential stuffing needs rate limits and multi-factor login. Business logic bugs need tests. A WAF should not be the reason a known bug stays unfixed.
A short history
- 2002: ModSecurity’s first release as an Apache module, created by Ivan Ristic.
- June 2010: Trustwave acquires Breach Security, which owned ModSecurity, and relicenses the code under the Apache licence.
- 2012: an IIS port from Microsoft and an NGINX port appear.
- 2015 to 2018: Trustwave rewrites the engine as libmodsecurity, ModSecurity 3, introduced to the public in 2018.
- 10 November 2016: CRS 3.0.0 is released, with anomaly scoring as the new default and the paranoia level idea, after three years of work on a separate branch.
- 2019: bunkerized-nginx, later renamed BunkerWeb, starts.
- May 2020: the Coraza repository is created, as a Go engine for SecLang rules.
- August 2021: Trustwave announces end of sale for its ModSecurity support and an end-of-life date of 1 July 2024.
- May 2023: Coraza 3.0.0.
- January 2024: OWASP announces it takes custodianship of ModSecurity, with the transition starting on 25 January 2024.
- February 2024: CRS 4.0.0.
- July 2024: Trustwave’s ModSecurity support ends; the project continues under OWASP.
- February 2025: BunkerWeb 1.6.0.
- March 2026: CRS 4.25 becomes the first CRS 4 long-term support line.
- Third quarter of 2026: CRS 3.3 reaches end of life.
- September and October 2026: security releases across the board: ModSecurity 3.0.17 and 2.9.15, Coraza 3.8.0 and 3.8.1, CRS 4.30.0 and 4.25.2, BunkerWeb 1.6.17.
Decision guide
- Do you run NGINX and know it well? Use ModSecurity 3 with the NGINX connector and CRS 4.25 LTS.
- Do you run Apache httpd? Use ModSecurity 2.9 with CRS 4.
- Do you run Caddy, HAProxy or Envoy, or want a WAF inside a Go service? Use Coraza, and check the integration’s maturity label.
- Do you want an all-in-one protective proxy with bot challenges, bans and a UI? Use BunkerWeb, and plan for its scheduler and database.
- Do you need calm, predictable rule updates? Use the CRS LTS line. Do you want the newest detections? Track the latest stable release and test each one.
- Do you mostly suffer from bots and login abuse? Start with rate limits and multi-factor login; a WAF is secondary.
- Is nobody available to tune it? Do not add one yet.
Checklist
- The goals and the non-goals of the WAF are written down.
- The engine is on a current security release (ModSecurity 3.0.17 or 2.9.15, Coraza 3.8.1, BunkerWeb 1.6.17 or newer as of 29 October 2026).
- CRS is version 4, pinned, and the signature is verified. CRS 3.3 is gone.
- The JSON body processor is enabled for JSON APIs, and body limits are set on purpose.
- Paranoia level and anomaly threshold are set explicitly in a file under version control.
- Every rule exclusion is narrow (rule plus parameter plus URL where possible) and has a reason.
- Audit logs are JSON, shipped to a log store, with sensitive parts and retention decided.
- A set of known-bad and known-good test requests runs after each upgrade.
- There is a named owner and a weekly review slot for WAF matches during tuning.
- The fail-open or fail-closed behaviour is decided for any external WAF agent.
- Login abuse is handled by rate limits and multi-factor login, not left to the WAF.
Common mistakes
- Leaving the WAF in detection-only mode forever. Use blocking mode with a high threshold and lower it; that is the documented CRS method.
- Raising the anomaly threshold to stop false positives. It also stops the WAF from blocking attacks that match only one rule. Fix false positives with exclusions.
- Jumping to paranoia level 3 or 4 on day one. Expect many false positives. Use the executing paranoia level to look ahead safely.
- Broad exclusions. Removing the whole SQL injection category for the whole site because of one notes field.
- Not parsing JSON. It causes false positives and blind spots at the same time.
- Forgetting that the WAF has bugs too. Not applying engine and CRS security releases.
- Logging full request bodies with passwords and personal data and keeping them for a year.
- Treating a WAF as a fix for a known vulnerability. It buys time; it does not replace the patch.
- Assuming the same rules behave the same on every engine. Test after any engine change.
The drill we ran
We wanted to see how CRS behaves at different paranoia levels on a few ordinary and hostile requests, without running a web server or touching system packages. So we ran Coraza as a Go library inside a small program. Nothing listened on any port; the program fed requests to the engine in-process as transactions. No sudo, no services, everything in a scratch folder.
- Setup. Go 1.27.2, Coraza 3.8.1 as a Go module, and the CRS 4.25.2 source archive. We verified the CRS signature with GPG in a temporary keyring; it reported a good signature from the CRS release key. The program loaded the engine settings, an explicit paranoia level, crs-setup.conf.example and the CRS rules. Loading took about 90 to 150 milliseconds each time.
- Requests. Nine invented requests for a food ordering site: four normal ones (a search, a delivery note with an apostrophe, a semicolon and a double hyphen, a JSON order whose remarks said “select the fresh batch, not the one from (yesterday)”, and a JSON recipe with a bold tag and an ampersand) and five attacks (SQL injection in a query string and in a JSON field, cross-site scripting in a form, path traversal and command injection in JSON).
- JSON parsing matters. Without the JSON body processor rule, the JSON order was read as a form body, the whole JSON became one parameter name, and the normal order was blocked at paranoia level 1 by an SQL rule (942190). With the JSON processor rule in place, it passed.
- Paranoia level 1. All four normal requests passed. All five attacks were blocked with HTTP 403 by the blocking evaluation rule.
- Paranoia levels 2 and 3. Three of the four normal requests were blocked: the delivery note by an SQL comment sequence rule (942440), the JSON order by rule 942260, and the recipe by an HTML tag rule and an SQL boolean rule. The attacks matched many more rules, for example eight for the query string SQL injection at level 2.
- Detection only and executing level. In detection-only mode, nothing was blocked but the same matches were recorded. With blocking at level 1 and the executing paranoia level at 2, all normal requests passed, their level 2 matches were logged, and all attacks were still blocked. This is the safe way to look ahead.
- Narrow exclusions. At blocking level 2, we added two exclusions: rule 942260 for the JSON remarks field on the orders URL, and rule 942440 for the note field on the notes URL. Those two normal requests then passed. The recipe, which we did not tune, was still blocked. The SQL injection sent in the same JSON remarks field was still blocked, by twelve other rules. No drill processes were left running.
Not run: no ModSecurity (it needs libmodsecurity and an NGINX module built from source), no BunkerWeb (it needs a Docker stack), no Caddy, HAProxy or Envoy integration, no response inspection, no CRS regression suite, no real traffic and no performance comparison. The per-request times we saw on a shared machine were a few milliseconds, from a single run, and are not a benchmark. Treat the results as a demonstration of how paranoia levels and exclusions behave, not as a measure of accuracy.
Unlearn, re-learn
- Unlearn “a WAF makes the app secure”. Re-learn that it is a second layer for common payloads, and that access control and logic bugs are out of its sight.
- Unlearn “ModSecurity is dead”. Re-learn that Trustwave’s support ended, but the project moved to OWASP and shipped security releases in 2026.
- Unlearn “detection-only first, blocking later”. Re-learn the CRS way: blocking from day one with a high threshold, lowered as you tune.
- Unlearn “raise the threshold to stop complaints”. Re-learn that narrow exclusions fix false positives without opening holes.
- Unlearn “higher paranoia is always safer”. Re-learn that a level nobody has tuned blocks customers, and the executing level lets you look ahead safely.
- Unlearn “the engine is the product”. Re-learn that the rules, their version, the body parsing and the tuning decide the outcome, on any engine.
Sources
- ModSecurity: ModSecurity repository and README, ModSecurity releases, ModSecurity-nginx connector, Trustwave transfers ModSecurity custodianship to OWASP (9 January 2024), End of sale and Trustwave support for ModSecurity, ModSecurity end of life and last commercial rules update, OWASP Developer Guide: ModSecurity
- OWASP Coraza: Coraza repository and README, Coraza releases, coraza-caddy, coraza-spoa, coraza-proxy-wasm, Coraza benchmarks page
- OWASP CRS: CRS repository, CRS releases, CRS security policy, Announcing CRS v4.25.0 LTS, backport policy, anomaly scoring, paranoia levels
- BunkerWeb: BunkerWeb repository and README, BunkerWeb releases, BunkerWeb 1.6.17 documentation, bunkerweb-plugins
- open-appsec: open-appsec repository
- Related posts on Revisit.Tech: reverse proxies in 2026, API gateways in 2026, ingress-nginx retirement, runtime security in 2026, static code analysis in 2026
Releted Posts
Reverse proxies in 2026: revisit NGINX, Caddy and HAProxy before you put another proxy in front of your app
Almost every web application we run has something sitting in front of it. A few virtual machines run the actual code, and one or two small servers in front of them accept HTTPS, keep certificates, spread traffic, hide a dead backend and write the access log.
Read moreDNS servers in 2026: revisit CoreDNS, PowerDNS and BIND 9 before you add another DNS record by hand
In most companies, somebody still adds internal DNS records by hand. A new database gets an IP address, an engineer logs in to the DNS server, types one line into a zone file or a web console, and saves.
Read moreStatic code analysis in 2026: revisit Semgrep, Opengrep and SonarQube Community Build before you add another code scanner to CI
Most teams add a code scanner to CI in an afternoon. A YAML job, a default rule pack, a green tick.
Read more