A DDoS post-mortem without a reconstructed attack timeline is a missed opportunity. The questions you need to answer -- which endpoint was targeted first, at what rate, which defenses fired and when, how long before user impact -- require correlating logs across at least three layers: the edge/CDN, the WAF, and the origin application. Each layer uses different timestamps, different request identifiers, and different verbosity defaults.
This post walks through the reconstruction process for a typical L7 DDoS attack, using Cloudflare Logs as the edge layer and Nginx as the origin. The methodology applies with minor substitutions to other stacks.
Step 1: Anchor the Timeline with Edge Logs
Start with the edge log, not the application log. Edge timestamps are synchronized to NTP and represent when the request arrived at the scrubbing/CDN layer. Application server timestamps may drift, and under load, the log write timestamp lags the actual request processing time. Export Cloudflare logs for the attack window via the Logs API or R2 integration. The key fields are: EdgeStartTimestamp, ClientIP, ClientRequestPath, EdgeResponseStatus, WAFAction, WAFRuleID.
# Extract per-minute request rate by path from Cloudflare log export
jq -r '[.EdgeStartTimestamp[:16], .ClientRequestPath] | @tsv' cf_logs.json \
| awk '{count[$1" "$2]++} END {for(k in count) print count[k], k}' \
| sort -rn | head -30
Step 2: Identify the Target Endpoint and Onset Time
Sort the edge log by request rate per path per minute. The attack onset is the minute where one or more paths show a rate increase exceeding 5x the prior 10-minute average. In most L7 attacks, the target path is apparent immediately: a single path or small set of paths accounts for 80%+ of the anomalous traffic. Note the onset timestamp, the target paths, and the source IP distribution (is it one IP, a /24, or a globally distributed botnet?).
Step 3: Check WAF Fire Time Against Onset
The delta between attack onset (from edge logs) and the first WAF block action is the detection-to-mitigation gap. For automatic mitigation rules, this should be under 60 seconds. For manually applied rules, this is the time from detection to operator response. In the edge log, filter on WAFAction = "block" and find the earliest timestamp with the attack characteristics. Compute first_block_time - onset_time. This number belongs in your post-mortem as a key metric.
Step 4: Correlate with Origin Application Logs
After establishing the edge timeline, match against origin application logs using the common request identifier. Cloudflare injects the CF-Ray header; Nginx logs it if configured with $http_cf_ray. Match CF-Ray values to find which requests reached the origin after bypassing or preceding WAF rules. The origin log tells you: when did response times begin to degrade, which worker processes became saturated, and whether any requests that should have been blocked reached the origin due to a misconfigured rule.
Step 5: Measure the Impact Window
Define impact as the period where origin P99 latency exceeded 3x the pre-attack baseline. Extract this window from your APM tool or from origin access log response times. The impact window starts when origin saturation begins (not when the attack begins -- there is typically a ramp-up period where the attack is absorbed) and ends when the mitigation rule fires and blocks the attack traffic at the edge.
What to Document in the Post-Mortem
- Attack onset time and first source IP observed
- Target endpoint(s) and peak request rate
- WAF rule that mitigated the attack (or absence of one)
- Detection-to-mitigation gap in seconds
- Impact window duration and peak error rate
- Source distribution: single IP, subnet, or distributed botnet
- Whether the attack used residential proxies or datacenter IPs
Understand Your Gaps Before the Next Attack
DDactic's assessment identifies which attack patterns your current configuration would miss, and how long your detection-to-mitigation gap would be under different attack scenarios.
Run a Free Scan