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.
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:
| Metric | Avg change | Avg multiple | Peak change | Peak multiple |
|---|---|---|---|---|
Open circuitstor_relay_circuits_total{state="opened"} | +755% | 8.6x | +1746% | 18.5x |
Onionskin worker assignmentstor_relay_load_onionskins_total{action="processed"} | +686% | 7.9x | +1298% | 14.0x |
Stream creationtor_relay_streams_total | +1045% | 11.4x | +2682% | 27.8x |
Bandwidthtor_relay_traffic_bytes{direction="read"} | +22% | 1.2x | +45% | 1.4x |
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.
Times UTC; metrics from MetricsPort, fleet-wide.
| When (UTC) | What we observed |
|---|---|
| Jun 22, 12:00–24:00 | Precursor rise in the open-circuit gauge, then two quiet days. |
| Jun 25, 23:00 | Onset: worker-assignment activity and open-circuit counts rise around the onset. |
| Jun 26–27 | First major waves: introduce2_rejected reaches about 12,000 rejections/second (highest hourly sample Jun 27 04:00); connection-rate rejections also rose. |
| Jun 26–28 | Recorded 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:00 | The 18.5x peak: open circuits reach 18.5x baseline, the highest hourly sample in the early window. |
| Jul 6–10 | Circuit inventory remained above baseline through the July 10 cutoff. |
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.
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.
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.
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.
Circuit creation floods overloading non-exit relays' onionskin processing, plus an onion-service introduction component — the same symptom class the Tor Project documented then:
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)
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.