Coraza + Caddy → AWS WAFv2
Coraza + Caddy ↔ AWS WAFv2: integration to migration path.
No pre-prod catches CRS false positives the way real production traffic does — so Coraza + Caddy goes live at the origin behind AWS WAFv2 first, in detection-only, while AWS keeps blocking at the edge. Only after it has tuned to parity and proved itself in dual-block do we invert AWS to Count and, finally, disassociate the Web ACL — one reversible phase at a time, no cutover day.
The one thing we are loud about up front: Bot Control, Fraud Control, and Shield Advanced are network-effect SaaS features with no OSS parity. Each is an explicit retain-or-replace decision, never a silent loss.
The idea
Prove Coraza on real traffic before AWS stops blocking.
The topology that makes this zero-outage: AWS WAFv2 stays on CloudFront and the ALB at the edge while a Caddy reverse proxy compiled with Coraza sits at the origin, loading OWASP CRS. Coraza scores every request and streams a SIEM-native audit log in detection-only, so we build prod-grade exclusions from real traffic without AWS ever ceding its blocking role. Once Coraza matches AWS's coverage we move both into block, then flip AWS to Count, then disassociate the Web ACL — each step independent and reversible. The non-negotiable gate at the end is locking the origin security group to the CloudFront prefix list, so nobody bypasses Caddy by hitting the ALB directly.
The phases
Six steps. Each one reversible.
Baseline & inventory
We enumerate every Web ACL rule, version, and override action, pull 90-day fire counts via Athena, measure per-route body sizes, and document the origin security-group bypass risk and which paid SKUs (Shield Advanced, Bot Control, Fraud Control) are in use. Read-only.
Coraza + Caddy goes live at origin in DETECT
Caddy compiled with the Coraza plugin stands up on ECS Fargate behind the existing ALB, running CRS at paranoia level 1 in detection-only. The AWS WAF at the edge is untouched and still blocks; Coraza scores and logs.
Tune Coraza exclusions; AWS WAF still blocks
We query Coraza's audit log for anomaly-score hits that AWS WAF did not block — each a candidate false positive — and add per-route exclusions in a git-versioned, CI-tested exclusions file. Still detection-only throughout.
Dual-block: AWS WAF and Coraza both enforce
Coraza flips to blocking via the Caddy admin API with zero connection drops. Both WAFs now block — defence in depth — and we watch the ALB 5xx and 403 rates, correlating any new blocks via the CloudFront request ID.
Invert: AWS WAF to Count, Coraza authoritative
Every AWS WAF rule moves to Count via an infrastructure-as-code change; AWS becomes observability and Coraza is the decision-of-record. We track AWS Count-matched but Coraza-allowed events as candidate coverage gaps.
Retire AWS WAFv2
The Web ACL is disassociated and held read-only for a 30-day evidence window; CloudFront stays for caching and Shield Standard. Critically, the origin security group is locked to the CloudFront origin-facing prefix list only so nobody can bypass Caddy by hitting the ALB directly. Bot Control and Fraud Control are cancelled only if not replaced.
Feature parity
Where Coraza matches AWS WAF, and where it does not.
| Capability | Coraza + Caddy | AWS WAFv2 | Parity |
|---|---|---|---|
| Rule engine | Coraza SecRules (ModSecurity v3 grammar, ~95%) | AWS WAFv2 statement language (ByteMatch, RegexPatternSet) | Partial |
| Managed ruleset | OWASP CRS v4.x (self-operated cadence) | AWS Managed Rules (CommonRuleSet, KnownBadInputs) | At parity |
| Anomaly scoring | OWASP CRS inbound/outbound score, per-PL thresholds | Label-based scoring via manual statement composition | Partial |
| Bot detection / ML | None (CrowdSec plus JA3/JA4 substitute, network-effect gap) | AWS WAF Bot Control (unverified-bot ML dataset) | SaaS only |
| Account-takeover / fraud | App-layer rate-limit plus HIBP substitute | AWS WAF Fraud Control - ATP / ACFP | SaaS only |
| DDoS absorption | CloudFront plus AWS Shield Standard only at edge | AWS Shield Advanced plus DDoS Response Team | SaaS only |
| Rate limiting | Caddy rate_limit plugin (separate from SecRule) | RateBasedStatement (per-5min per IP/header/cookie) | Partial |
| IP reputation | Project Honeypot / CrowdSec Community Blocklist | AWSManagedRulesAmazonIpReputationList (opaque feed) | At parity |
| Body inspection | SecRequestBodyLimit 13 MB at origin | 8 KB ALB / 64 KB CloudFront managed | OSS only |
| TLS / edge integration | Caddy ACME plus reverse proxy in one binary | Native attach to CloudFront / ALB / API Gateway / AppSync | Partial |
| Logging / SIEM | SecAuditEngine JSON, pre-log PII redaction | Kinesis Firehose to S3; field redaction list only | At parity |
| Cost model | Compute plus EFS plus log egress; flat | $5/ACL plus $1/rule plus $0.60/M requests plus add-ons | Partial |
| Compliance (SOC 2 / PCI) | Self-owned CRS cadence plus log custody boundary | Vendor SOC 2 boundary you can outsource | Partial |
What we're honest about
The caveats most vendors leave out.
Bot Control's ML dataset has no OSS parity
AWS WAF Bot Control's unverified-bot detection draws on a fleet-wide signal dataset from AWS-hosted properties — a network-effect feature OSS cannot reproduce. CrowdSec plus JA3/JA4 plus user-agent checks catch script-kiddies only; expect roughly a 20 percent reduction in unverified-bot efficacy if you self-host. We keep Bot Control on a stub Web ACL or move bot management into the app, and we name the gap rather than paper over it.
Fraud Control - ATP is not reproducible in OSS
Account-takeover detection is backed by AWS-fleet-wide attacker telemetry — a stolen-credential signal corpus you cannot rebuild. HIBP covers known-breached passwords, not real-time attacker-tooling signatures. The honest substitute is app-layer rate-limiting plus HIBP k-anonymity plus risk-based MFA plus device fingerprinting — a separate workstream, not a WAF sub-task.
Shield Advanced and the DDoS Response Team
Shield Advanced's DDoS Response Team requires both the subscription and a WAFv2 association on the protected resource; disassociating the Web ACL may remove that mitigation path. Either keep Shield Advanced with a Count-only stub Web ACL to preserve the DRT path at near-zero rule cost, or accept DRT removal and self-operate during a DDoS with CloudFront, Shield Standard, and autoscaling Caddy. We make that go/no-go explicit per SKU.
You operate CRS cadence and own the boundary
AWS publishes Managed Rule versions on its own cadence; with CRS you operate that cadence yourself — Renovate on the CRS repo, a CI canary in detect before promoting to block. Caddy is also the proxy, so Caddy down means the app is down: we run at least three tasks across AZs with a health check that doesn't invoke Coraza. And API Gateway, AppSync, and Cognito can't be fronted by Caddy — those surfaces stay on AWS WAFv2 or accept removal.
Why this beats a flag day
Reversible per phase. Soaked before you disassociate.
Every per-phase change rolls back inside the documented window — under 2 minutes to flip the ALB target group, under 1 minute to revert an exclusion or drop Coraza back to detection-only, under 5 minutes to re-associate the Web ACL. And we never disassociate on optimism: AWS WAF must be Count-only or removed with Coraza's blocked-request rate within plus-or-minus 20 percent of the AWS baseline for at least 30 consecutive days, with the origin-SG bypass audit clean, before the safety net comes down.
See whether your WAF migrates off AWS WAFv2 cleanly.
A call with a senior security engineer. We inventory every Web ACL rule and attachment, map AWS Managed Rules to CRS, decide which paid SKUs — Bot Control, Fraud Control, Shield Advanced — stay or get replaced, and tell you honestly whether the cost case needs the scale before we promise savings.
Map my migration →