Circuit-Building DoS Wave: 18.5x Peak, 8.6x Average Circuits on Guard Relays Since June 25

By 1AEO Team • July 10, 2026 • Guard relay fleet (EU + NA) • Tor MetricsPort data, Jun 10 – Jul 10

Technical correction — October 20, 2026: Terminology and counter interpretations have been corrected. Charts and summaries were recomputed from retained June–July telemetry using the method below; the observation window remains historical.

Beginning June 25, 2026, 23:00 UTC our guard relay family recorded a sustained circuit-building DoS wave — open circuits at 18.5x baseline peak and 8.6x on average, while aggregate bandwidth grew much less than the circuit count. This is the July 10, 2026 snapshot, 15 days into the event; the reanalysis retains that cutoff.

Hourly native open-circuit gauge relative to the June10–25 baseline. June25–July10 event mean8.6timesbaseline and sampledpeak18.5timesbaseline. Low-coverage hours are gaps.

Summary

Early event window (June 25, 23:00 UTC – July 10, 23:00 UTC) against the June 10–25, 22:00 UTC baseline mean. Averages and maxima use the retained hourly samples described below:

MetricAvg changeAvg multiplePeak changePeak multiple
Open circuits
tor_relay_circuits_total{state="opened"}
+755%8.6x+1746%18.5x
Onionskin worker assignments
tor_relay_load_onionskins_total{action="processed"}
+686%7.9x+1298%14.0x
Stream creation
tor_relay_streams_total
+1045%11.4x+2682%27.8x
Bandwidth
tor_relay_traffic_bytes{direction="read"}
+22%1.2x+45%1.4x
Read and written byte rates relative to the same June10–25 baseline, sampled hourly. Event means about1.2timesbaseline and peaks about1.4–1.5timesbaseline; low-coverage hours are gaps.

What Is a Circuit-Building DoS?

Building a Tor circuit requires cryptographic handshakes at successive relay hops, and each relay retains state for its part of an open circuit. Built at scale and left idle, circuits load a relay's CPU and RAM while moving almost no traffic. We run Guard-flagged relays, eligible to serve as the first hop. Tor's resource-pressure indicators are documented in the Tor Project's relay overload guide (dropped ntor onionskins, OOM invocations, TCP exhaustion), and the "introduction" load maps to the introduction-point step of the onion service protocol.

Historical reanalysis, June 10 to July 10 2026: worker-assignment rates relative to the June 10 through June 25 22:00 UTC mean. Early event mean 7.9 times baseline; highest hourly sample 14.0 times on June 29 05:00 UTC. Hours with fewer than 883 native reporters are omitted.

Timeline

Times UTC; metrics from MetricsPort, fleet-wide.

When (UTC)What we observed
Jun 22, 12:00–24:00Precursor rise in the open-circuit gauge, then two quiet days.
Jun 25, 23:00Onset: worker-assignment activity and open-circuit counts rise around the onset.
Jun 26–27First major waves: introduce2_rejected reaches about 12,000 rejections/second (highest hourly sample Jun 27 04:00); connection-rate rejections also rose.
Jun 26–28Recorded onionskin queue drops and memory-handler byte removal, alongside changes in consensus flag counts. These measures do not establish a single cause.
Jul 5, 09:00The 18.5x peak: open circuits reach 18.5x baseline, the highest hourly sample in the early window.
Jul 6–10Circuit inventory remained above baseline through the July 10 cutoff.

Recorded Defense Actions and Queue Drops

Tor's DoS counters rose during the event. Introduction rejections reached a highest hourly sample of about 12,000/second at June 27, 04:00 UTC, equivalent to 43.1 million/hour. The sum of recorded onionskin drop rates divided by the sum of assignment rates was 0.097% over the early window; the largest hourly ratio was 7.44%. This dropped-to-assigned ratio is not a percentage of successful circuit completions.

Historical reanalysis, June 10 to July 10 2026: defense-counter hourly-rate equivalents and the recorded onionskin dropped-to-assigned ratio. Introduction peak 43.1 million per hour equivalent; overall drop-to-assignment ratio 0.097 percent and largest hourly ratio 7.44 percent. The ratio is not a circuit-success or connection-loss percentage.

Why does a relay drop onionskins in the first place? Tor dispatches ntor-family handshake work to CPU worker threads. After a small-queue allowance, its admission check estimates how long the queue would take to clear and rejects work exceeding the MaxOnionQueueDelay budget. A recorded drop identifies that queue-admission decision, not every circuit failure or a direct measurement of host CPU saturation.

In the cited Tor 0.4.8.16 implementation that gate is have_room_for_onionskin() in onion_queue.c: it takes estimated_usec_for_onionskins() over the queued ol_entries, divides by num_cpus worker threads, and returns "no room" — a drop — once that estimate exceeds MaxOnionQueueDelay. No memory term appears anywhere in the check.

What the Flood Was Made Of

Broken down by handshake type, counted work was overwhelmingly ntor_v3: 89.7% of worker assignments across the early event and 96.6% at the assignment-rate peak. Its average rate was 19.4x its own baseline; ntor averaged 1.3x. This identifies the handshake protocol, not the originating software, attack tool or operator.

Historical reanalysis, June 10 to July 10 2026: ntor and ntor_v 3 worker-assignment rates relative to the total baseline. ntor_v 3 accounts for 96.6 percent at the June 29 05:00 UTC assignment peak and 89.7 percent across the early event. These counters are not successful-circuit counts.
Metrics gotcha for operators: on the drop side, Tor merges ntor_v3 into the ntor onion queue, so tor_relay_load_onionskins_total{action="dropped"} labels every ntor-family drop as type="ntor" — it never reports ntor_v3 drops separately, even when ntor_v3 is what actually overflowed. The tor_relay_load_onionskins_total{action="processed"} counter records the true handshake type; the same metric with action="dropped" records the (collapsed) queue type. Don't read the drop-side type label as the sender's handshake version. In the cited Tor code, action="processed" exposes the worker-assignment counter, incremented before dispatch; action="dropped" records queue-admission rejections. Both omit channels Tor classifies as clients, including unauthenticated bridge connections. Neither measures successful circuit completion.

Same Symptom Class as the 2022–2023 Network DDoS

Circuit creation floods overloading non-exit relays' onionskin processing, plus an onion-service introduction component — the same symptom class the Tor Project documented then:

If You Run Relays: Metrics to Compare

Compare the last two weeks against your June baseline — the relay overload guide explains how to read these:

tor_relay_circuits_total{state="opened"}
tor_relay_load_onionskins_total
tor_relay_streams_total
tor_relay_dos_total (by type)

How We Measured

Each relay exports Tor's MetricsPort into a central Prometheus. Connection-gauge trends were compared with host TCP gauges; that does not independently validate every circuit, stream or handshake metric. The October 20 correction recomputes the historical summary table and the charts identified as a historical reanalysis below. Ratios describe the reporting population, not individually tracked users.

The catch, briefly: our inbound connection gauge (tor_relay_connections{state="opened", direction="received"}) declined between restarts while host TCP counts stayed flatter. See the connection-gauge investigation for the source accounting and comparison limits. Connection-creation counters measure activity; host TCP gauges measure current connection levels.

This page retains its July 10 observation window. Continue with the updated anatomy and the year-long review through July 28.

Historical reanalysis, October 20, 2026: We use only job="tor_relays", excluding duplicate legacy scrape jobs. Counter rates use rate(...[1h]) per series before summation, sampled hourly; circuit counts are hourly gauges. The baseline is the mean from June 10, 00:00 through June 25, 22:00 UTC; the early event runs June 25, 23:00 through July 10, 23:00. Hours with fewer than 883 reporting native instances (90% of the baseline maximum of 981) are omitted, not replaced with zero. Reported maxima are sampled one-hour rates or hourly gauges. The introduction-rotation chart instead uses six-hour increase values and a stated positive-rejection threshold; concentration uses hourly instance rankings. These aggregate queries do not reveal individual users or circuit positions.

Join the Mission