Cloudflare Regex Outage (2019)
Open in the interactive app: practice, quiz, mock interview →In one minute
- On 2 July 2019, Cloudflare deployed a new web application firewall (WAF) rule containing a regular expression that backtracked catastrophically. CPU on every machine handling HTTP traffic worldwide went to 100 percent, and customers saw 502 errors for 27 minutes (13:42 to 14:09 UTC; Cloudflare postmortem).
- The dangerous part was
.(?:.=.*): two "match anything" pieces around an=. On some inputs, a backtracking engine tries a number of paths that grows very quickly with the input length. - WAF rules were deliberately deployed globally within seconds, skipping the staged rollout used for software, so they could respond to attacks quickly. The rule was in "simulate" mode, but simulate still runs the regex.
- A protection against excessive regex CPU use had been removed by accident during a refactor weeks earlier, and tests checked correctness, not CPU cost.
- Recovery was slowed because the tools engineers needed (internal access, dashboards) ran on the same edge that was down. Fixes included restoring the CPU guard, profiling rules in tests, moving to regex engines with linear-time guarantees, and staged rollouts for rule changes.
How it works
Timeline (UTC, from Cloudflare's post)
- 13:31 The engineer's pull request with the new XSS-detection rule is merged.
- 13:37 The build passes its tests.
- 13:42 Automatic deployment of the rule begins; within seconds it is everywhere. About three minutes later the first page fires (Cloudflare postmortem).
- Engineers initially suspect an attack, because of the global, simultaneous CPU spike.
- 14:00 The WAF is identified as the cause; an attack is ruled out.
- 14:02 The team proposes a global kill of the WAF.
- 14:07 The global WAF termination is executed.
- 14:09 Traffic and CPU return to normal worldwide.
- 14:52 After confirming the cause and the fix, the WAF is re-enabled globally.
Technical root cause
The rule's regular expression ended with .(?:.=.). In a backtracking regex engine such as PCRE, which Cloudflare's Lua-based WAF used, . first consumes as much as possible and then gives characters back one at a time when the rest of the pattern fails. With two .* pieces in sequence and an = that is not present, the engine tries every way to split the input between them, for every starting position. Cloudflare's post shows the step count rising sharply: 23 steps for x=x, 555 steps for x= followed by 20 x's, and 5,353 steps when a trailing character forces more backtracking (Cloudflare postmortem). Real HTTP requests are much longer, so a single request could cost huge amounts of CPU, and every request was checked.
Contributing factors
- Global, instant deployment for rules. WAF rules were distributed through Quicksilver, which propagates changes worldwide in seconds, without the staged rollout (internal "dogfood" points of presence, then small canary regions) used for software releases (Cloudflare postmortem; compare AWS Builders' Library on staged deployments). That speed is valuable during an attack and catastrophic for a bad rule.
- Simulate mode still executes. Testing a rule in production in "log only" mode still ran its regex on live traffic.
- A removed safeguard. Protection against excessive regex CPU had been removed weeks earlier in a refactor intended to reduce CPU use (Cloudflare postmortem).
- Tests checked behaviour, not cost. The rule test suite verified matches, not performance.
- Shared fate of tools. Cloudflare Access (for internal tools) and the dashboard and API ran through the same edge. Some engineers' credentials had expired, and the bypass procedure was rarely practised (Cloudflare postmortem).
Detection and mitigation
The CPU spike was immediately visible, but the first hypothesis (an attack) cost time. The fastest mitigation was the "global terminate" for the WAF, a kill switch that turned off the whole component, used about 25 minutes after deployment. A plain rollback would have needed the full WAF build to run twice, which was too slow (Cloudflare postmortem).
What changed afterwards
Cloudflare listed: re-introducing the excessive-CPU protection; manually inspecting all 3,868 managed rules for backtracking risks; adding performance profiling of every rule to the test suite; switching to the re2 or Rust regex engines, which guarantee run time linear in the input; staged rollouts for rule changes, keeping an emergency global path for active attacks; an emergency way to run the dashboard and API off the edge; and automated status page updates. Longer term, the WAF moved off the Lua engine to a new firewall engine.
What I would have done as the tech lead
Treat configuration and rules as code with the same rollout safety: staged, with automatic rollback on CPU or error regressions, plus an explicit emergency path that requires a stated reason. Make CPU budgets part of the rule test suite, and use a regex engine with linear-time guarantees for untrusted input by design, not by review. Keep the WAF kill switch fast, documented and practised, and make sure incident tooling does not depend on the system that may be down.
Down the stack
Layer 1The contract
A WAF promises to inspect every request and block attacks while adding little latency, typically well under a millisecond per request. That contract has two halves: correctness (does it match attacks?) and cost (does it stay within a CPU budget?). The test suite checked only the first half.
Layer 2The mechanism
Regex engines come in two families. Backtracking engines (PCRE, the default engines in Python, JavaScript, Java and many others) explore one path at a time and back up on failure; they support features such as backreferences, but some patterns take exponential or high-polynomial time on adversarial input. Automata-based engines (RE2, Go's regexp, Rust's regex) compile the pattern to a state machine and process each input character a bounded number of times, guaranteeing time linear in the input for a fixed pattern, at the cost of not supporting backreferences (Russ Cox, Regular Expression Matching Can Be Simple And Fast). For a system that runs thousands of patterns against untrusted input, the linear-time guarantee is worth far more than backreferences.
Layer 3The machine
Each backtracking step is a few memory reads and comparisons, perhaps a nanosecond or two, so thousands of steps per request sound harmless. But a proxy handles tens of thousands of requests per second per machine, each checked against many rules, and the step count for this pattern grows roughly with the cube of input length. An input of a few kilobytes can need billions of steps, seconds of CPU for one request. When every core spends its time inside the regex engine, the event loop that should serve other requests never runs, so latency and errors hit all traffic, not just the requests that triggered the pattern.
Why it's built this way
- Security rules must deploy fast to respond to attacks, which creates pressure for global, instant pushes.
- Fast global pushes need strong pre-deployment checks, including cost, not only correctness.
- Backtracking engines have unbounded worst cases, so either the engine or a CPU guard must bound them.
- When the edge is down, tools that depend on it are down too, so recovery paths need independence.
- The best fix removes the failure class: linear-time engines plus staged rollout plus a guard.
Diagram
flowchart TD PR["Rule merged 13:31"] --> B["Tests pass: correctness only"] B --> Q["Quicksilver: global push in seconds 13:42"] Q --> S["Simulate mode still runs regex"] S --> RX[".*(?:.*=.*) backtracks on every request"] G["CPU guard removed weeks earlier"] --> RX RX --> CPU["100 percent CPU on all edge machines"] CPU --> E["502s for 27 minutes"] A["Internal tools on same edge"] --> SL["Slower mitigation"] SL --> E K["Global WAF kill 14:07"] --> R["Recovery 14:09"]
Worked example
A team uses a regex to validate email addresses in a signup API: ^([a-zA-Z0-9]+)@example\.com$. A user (or attacker) submits 30 letters followed by an exclamation mark. Each extra letter roughly doubles the work, because the nested quantifier ([a-zA-Z0-9]+) can split the letters into groups in exponentially many ways before concluding there is no match. In Python's re, a 30-character input can take many seconds; at 40 characters it effectively hangs a worker.
Applying Cloudflare's fixes: switch validation to a linear-time engine (the re2 bindings, or Go's regexp in a Go service) or rewrite the pattern without nested quantifiers (^[a-zA-Z0-9]+@example\.com$); add a test that runs each pattern against long adversarial inputs and fails if it takes more than a small budget; cap input length before matching; and roll out validation changes through the same canary process as code.
In the wild
- Cloudflare, 2025: a feature file doubles in size. On 18 November 2025 a database permissions change made a query return duplicate rows, which doubled the Bot Management feature file. It exceeded the proxy's limit of 200 features (about 60 were in use), the proxy panicked, and customers saw HTTP 5xx errors until a known-good file was deployed at 14:30 UTC (Cloudflare postmortem).
- Fastly, 2021: one valid customer config exposes a latent bug. On 8 June 2021 a customer configuration change triggered a bug shipped in a 12 May software deployment. About 85 percent of the network returned errors, monitoring noticed within a minute, and 95 percent recovered within 49 minutes (Fastly summary).
Trade-offs
| Approach | Strengths | Weaknesses | Choose when |
|---|---|---|---|
| Backtracking engine | Rich features (backreferences, lookaround) | Unbounded worst case | Trusted input, small patterns |
| Linear-time engine (RE2, Rust, Go) | Guaranteed run time | Fewer features | Untrusted input, many patterns |
| Step or time budget per match | Bounds damage from any engine | Must decide fail open or closed | Defence in depth |
| Global instant config push | Fast response to attacks | Global blast radius | Emergencies only, with a stated reason |
| Staged config rollout | Catches bad changes on a small slice | Slower | Default for rules and config |
Pitfalls in production
- Treating config and rules as safer than code. Fix: same staged rollout and automatic rollback.
- "Log-only" or "simulate" modes assumed to be harmless. Fix: they still execute; budget and stage them too.
- Refactors that silently remove safeguards. Fix: tests that assert the safeguard exists and works.
- Regexes on untrusted input in backtracking engines. Fix: linear-time engines, input length limits, budgets.
- Incident tools behind the system they help fix. Fix: independent access paths, practised regularly.
Leadership lens
- Inventory every place that pushes config or rules globally, and add staging with an emergency override.
- Add performance budgets to tests for anything that runs per request: regexes, templates, scripts.
- Prefer designs that make whole failure classes impossible (linear-time engines) over reviews that try to catch each instance.
- Practise the kill switch and the "edge is down" access path as part of game days.
Check yourself
Why does
..=.*get so slow on inputs without=?Show answer
A backtracking engine tries every way of splitting the input between the two
.*pieces, at every starting position, before giving up, so work grows roughly with the cube of the input length.Why did "simulate" mode not protect Cloudflare?
Show answer
Simulate mode still evaluated the regex on live traffic and only skipped blocking, so the CPU cost was fully present.
What does a linear-time regex engine give up, and why is it worth it here?
Show answer
Features such as backreferences; in exchange it guarantees time proportional to input length, which bounds the cost of any rule on any request.
Why did mitigation take longer than the kill switch itself?
Show answer
Engineers first suspected an attack, and the internal tools and access they needed ran through the same failing edge, with a rarely used bypass.
Sources and further study
Primary
- Details of the Cloudflare outage on July 2, 2019 (Cloudflare): timeline, regex step counts, Quicksilver and follow-ups.
- Regular Expression Matching Can Be Simple And Fast (Russ Cox): exponential backtracking versus Thompson NFA matching.
Systems docs
- Automating safe, hands-off deployments (AWS Builders' Library): waves, one-box stages, bake time and automatic rollback.
- Summary of the June 8 outage (Fastly): a comparable global config-triggered failure.
Books
- Release It!, 2nd ed., Michael Nygard, Ch 4: Stability Antipatterns, on cascading failures and shared fate.
- Site Reliability Engineering (Google), Ch 15: Postmortem Culture, for how to write and use a review like Cloudflare's.
Other curricula
- Postmortem Culture, SRE Workbook Ch 10 (Google): system-focused action items rather than blame.
Free resources
- Details of the Cloudflare outage on July 2, 2019 (Cloudflare): Cloudflare's own full postmortem, including the regex analysis.
- Regular Expression Matching Can Be Simple And Fast (Russ Cox): why automata-based engines avoid catastrophic backtracking.
- Go regexp package: a widely used RE2-style engine that guarantees linear time in the input.
- ReDoS (OWASP): the attack class and mitigations.