#3891·bunkerweb

ModSecurity audit logging: ~37 lines / ~5 KB written per blocked request, SecAuditLogType not configurable

Author: Ayushsinha322Created Sep 8, 2026Updated Sep 18, 2026
Labelsenhancementcorenext minor

Affected area

core / all-in-one — ModSecurity audit logging.

What happened?

With MODSECURITY_SEC_AUDIT_ENGINE=RelevantOnly (the default), every blocked request writes a full audit record. Measured on bunkerity/bunkerweb-all-in-one:1.6.9, that is 37.3 lines and 5,109 bytes of audit log per blocked request.

Under sustained blocking this is a large amount of I/O generated directly by attacker-controlled traffic, and there is currently no way to tune it short of turning audit logging off entirely. Two things compound the default:

  1. SecAuditLogType Serial is hardcoded in src/common/core/modsecurity/confs/server-http/modsecurity-rules.conf.modsec (line 126 on dev). Serial means all worker threads serialise on a single writer.
  2. In the container images the audit log is the container's stderr. src/bw/Dockerfile symlinks /var/log/bunkerweb/modsec_audit.log to /proc/1/fd/2, and in the all-in-one image logstream.sh pipes it through tail -F | tr -d '\000-\010\013-\037\177' | sed "s/^/[MODSEC] /" — two extra processes touching every one of those lines.

I want to note up front that MODSECURITY_SEC_AUDIT_LOG was added on dev since 1.6.9, so the log path is now configurable — that already helps, and this issue is not asking for it again. What remains unconfigurable is the writer type and the volume.

How to reproduce?

Origin: nginx:alpine with location / { return 200 "ok"; }. BunkerWeb as a reverse proxy in front of it, rate/ban features disabled so a single-source generator isn't simply IP-banned:

bash
docker run -d --name bw-bench -p 18092:8080 \
  -e SERVICE_UI=no -e SERVER_NAME=localhost \
  -e USE_REVERSE_PROXY=yes -e REVERSE_PROXY_HOST=http://$OIP:80 -e REVERSE_PROXY_URL=/ \
  -e USE_MODSECURITY=yes -e USE_MODSECURITY_CRS=yes -e SECURITY_MODE=block \
  -e USE_BAD_BEHAVIOR=no -e USE_LIMIT_REQ=no -e USE_LIMIT_CONN=no -e USE_BLACKLIST=no \
  -e USE_DNSBL=no -e USE_BUNKERNET=no -e USE_WHITELIST=no -e USE_GREYLIST=no \
  bunkerity/bunkerweb-all-in-one:1.6.9

Attack targets must carry Host: localhost, otherwise BunkerWeb serves its default landing page with neither the reverse proxy nor CRS engaged, which silently looks like "not blocked":

GET http://127.0.0.1:18092/?id=1%27%20OR%20%271%27=%271
Host: localhost

Then, per rate:

bash
docker exec bw-bench sh -c ': > /var/log/bunkerweb/modsec_audit.log'
vegeta attack -duration=15s -rate=300 -timeout=30s -connections=200 -targets=attack.txt | vegeta report
docker exec bw-bench sh -c 'wc -lc < /var/log/bunkerweb/modsec_audit.log'

Measurements

15 s attack runs, 3-target mix (SQLi, XSS, path traversal), all returning 403:

Attack rate Blocks Audit lines written Audit bytes written
300 req/s 4,500 168,000 23.0 MB
350 req/s 5,250 196,000 27.1 MB
1000 req/s 15,000 560,000 76.6 MB

Latency at 300 req/s, audit logging on vs off, identical config otherwise:

SecAuditEngine p50 p99
RelevantOnly (default) 42 ms 459 ms
Off 27 ms 84 ms

So audit logging costs roughly 5x p99 at that rate.

What this is not

I want to be precise here, because I initially assumed the opposite and was wrong.

Audit logging is not a throughput-collapse or availability problem. I ran a 2x2 varying the audit engine and the deny-response size independently. With audit logging fully enabled and writing 76.6 MB during the run, an instance whose deny response was small handled 1,000 attack req/s at p50 5.5 ms with zero drops. Disabling audit logging on an otherwise-default instance did not prevent collapse. The dominant factor was elsewhere.

This issue is therefore about cost and tunability only, not availability. I did not want to file a performance claim that a controlled test doesn't support.

Suggested improvements

  1. Make SecAuditLogType configurable (Serial / Concurrent), so high-volume deployments can escape the single serial writer. This is the main ask.
  2. Consider documenting the per-blocked-request cost next to MODSECURITY_SEC_AUDIT_ENGINE, so operators can size disk and choose deliberately. Roughly 5 KB and 37 lines per blocked request at the default SecAuditLogParts is useful to know before an attack rather than during one.
  3. Optionally, a way to sample or cap audit records under sustained blocking — though I appreciate ModSecurity offers no native mechanism for this, so it may not be practical.

Happy to send a PR for (1) and (2) if the direction is welcome — I'd branch from dev.

BunkerWeb version

1.6.9 (measured). Claims re-verified against dev at the time of filing.

What integration are you using?

Docker (all-in-one image).

Notes

I work on WAF engineering commercially, so I'll state that plainly. The numbers above are BunkerWeb measured against itself — there is no comparative or competitive data in this report.