Skip to content

WAF operations

Day-to-day use of the WAF control plane GUI. Log in with your single sign-on account; you see only the apps your team owns (global admins see everything).

Instant changes vs. deployed changes

Change How it goes live Rollback reverts it?
IP ban / allow list entries Instantly, via the Runtime API No: change it back in the GUI
WAF mode (detect/block), SPOA fail mode Instantly No
Geo policy, ASN policy Instantly (toggles) or within about a minute (app editor Save) No
Bot actions (spoofed claims, AI categories, unverified) Within about a minute No
Apps, hostnames, origins, headers, rule-set paranoia/threshold, exclusions, rate limits Preview → Apply (a deploy) Yes
Generated robots.txt, or an AI category change while robots.txt is on Preview → Apply Yes

Instant changes are written to the database first, then pushed to every node. The control plane re-syncs every node to the database every minute, so a node that missed a change (restart, unreachable) catches up by itself. The GUI shows such a change as pending until it does.

Deploying

  1. Make the change in the GUI and save. The app shows Unpublished changes.
  2. Deploys → Preview. This runs Ansible in check/diff mode against the nodes and shows what would change. Nothing on the nodes is modified.
  3. Open the successful preview and click Apply. Nodes are updated one at a time; each new config is checked with haproxy -c and a post-deploy health check. A failure restores the previous config and stops before the next node.
  4. Rollback to any earlier deployed config version from the Deploys page.

Deploy logs are shown in the GUI and in the CI pipeline.

Adding an app

  1. Apps → New app: name (immutable, used as the service label), hostnames, origin address, port and TLS mode (plain, or verify with an optional SNI name).
  2. Start in WAF mode detect, so attacks are logged but not blocked, and watch the logs for false positives for a few days. Add rule exclusions for them, then switch to block.
  3. Request a certificate (below), then Preview and Apply.
  4. Point the app's DNS at the WAF's public address.

Origins must only accept traffic from the WAF: firewall them so the WAF nodes are the only sources allowed to reach the origin's address and port.

Certificates

Certificates → Issue. The control plane obtains a Let's Encrypt certificate using either:

  • HTTP-01: answered by HAProxy on port 80 (port 80 must reach the WAF); or
  • DNS-01: via the DNS provider's API, for names that can't be validated over HTTP.

Issued certificates are stored encrypted in the database and loaded into HAProxy live, with no deploy needed. Renewal is automatic, 30 days before expiry. Manual upload is also supported.

Blocking and allowing IPs

IP lists: add a CIDR as ban or allow, globally (admin only) or for one app, with an optional expiry and a reason. An allowlisted source bypasses every WAF deny decision (geo/ASN, CrowdSec, bans, bots, rate limit and the OWASP rules) but is still subject to the body-size limit.

Bot detection

  • Each app's Bots section sets the action for spoofed bot claims (log/block), each AI crawler category (training, search, user-triggered fetch: allow/log/block) and unverified bots.
  • Real search-engine crawlers coming from their vendor's published ranges are always allowed.
  • The bot lists are refreshed nightly by a scheduled CI job. If a vendor list fails to download, the job goes red and the previous data is kept.
  • An optional per-app generated robots.txt disallows every AI category set to block.

Logs

What Where (on each WAF node)
Request log, one JSON line per request /var/log/haproxy.log
OWASP rule matches journalctl -u coraza-spoa
CrowdSec decisions sudo cscli decisions list
Who changed what GUI → Audit log
# live request log, readable
sudo tail -f /var/log/haproxy.log | grep --line-buffered '{"ts"' | sed -u 's/^[^{]*//' \
  | jq -c '{app,src,country,status,waf_action,waf_rule_ids,bot_id,bot_verified,path}'

# one request, by the X-Request-ID the origin received
sudo grep <request-id> /var/log/haproxy.log

Key log fields: waf_action (allow, detect, block, ip_ban, crowdsec_ban, allowlist, ratelimit, bot_log, bot_block, spoa_error, …), policy_src (geo/asn), waf_rule_ids (OWASP rules that matched), bot_id / bot_verified / bot_category, tls_ja3 / tls_ja4.

Scheduled jobs

Job Frequency What
Geo/ASN maps Weekly Rebuild the GeoLite2 country and ASN maps and push them to the nodes
Bot lists Nightly Refresh vendor bot IP ranges and the ai.robots.txt crawler list

Both are serialised with deploys, so they never run at the same time as one.

Backups

The control plane dumps its database daily and keeps two weeks of dumps. After restoring a dump, Preview and Apply so the nodes match the database. Restore steps are in the WAF repository's runbook.