Every extra hop you route through costs something, and Tor routes through three of them by design. The question worth asking precisely — because most people answer it wrong — is where that cost actually lands. Tor performance is not dominated by the cryptography. Layered AES-CTR/AES-GCM decryption at each relay is computationally trivial next to the surrounding network I/O. The dominant, addressable cost has always been queueing: fixed-size flow-control windows and relay-side socket scheduling that, for most of Tor’s history, capped throughput far below what the underlying links could carry. The last decade of Tor performance engineering — KIST in 2017, RTT-based congestion control in 2022, traffic splitting in 2023 — is a sequence of fixes to that one root cause, not to the encryption.
This article builds the budget honestly: real geographic round-trip time across three hops, cheap per-hop crypto, and expensive relay-side queueing, in that order of actual cost. Then it walks the machinery that closed the gap, generation by generation, and closes on the trade-off nobody gets to opt out of — low latency is what makes Tor usable, and also what leaves timing correlation on the table.

The Latency Budget: Where Three Hops Actually Send Your Packet
Get the hop count right before anything else. An ordinary client circuit to the open internet is three hops — guard, middle, exit. A connection to a v3 onion service is not that: it joins two independently built three-hop circuits at a rendezvous point, roughly six hops of relay-to-relay routing end to end. Every latency number below describes the three-hop client-circuit case unless stated otherwise.

Three hops means real transit time you cannot compress away. If your guard, middle, and exit relays sit on three different continents, each additional hop beyond a direct connection adds genuine fiber-and-satellite propagation delay — physics, not a bug, and not something a faster CPU fixes. That’s the one line item a protocol change can’t shrink; the only lever Tor has over it is relay selection and circuit geography, not the crypto or the flow-control design.
Everything else in the budget is engineering, and that’s where the real gains came from. Tor’s own standing performance-regression instrument, OnionPerf — a continuous benchmark descended from Torperf, running since 2009 — measures exactly this: fetching fixed-size files (commonly 50 KiB, 1 MiB, 5 MiB) over Tor from an ephemeral onion service, timing time-to-first-byte and time-to-last-byte. It’s the tool that would show, empirically, which line item below actually moved when each fix shipped.
| Latency/throughput source | What it is | Relative weight | Status |
|---|---|---|---|
| Extra hop RTT | Real geographic transit from two extra relay hops vs. a direct connection | Fixed, structural — the price of the anonymity set | Not software-addressable |
| Per-hop cryptography | AES-CTR/AES-GCM decrypt of one onion layer, once per relay | Computationally trivial at Tor’s traffic volumes | Not a meaningful contributor |
| SENDME window stalls | A fixed 1,000/500-cell in-flight cap acting as a bandwidth-delay-product ceiling | The dominant, addressable cost on high-BDP paths | Replaced by adaptive windows, May 2022 |
| Relay socket scheduling | Pre-2017 round-robin writes blind to kernel buffer state — bufferbloat, cross-circuit blocking | Congestion down 30%+, latency ~18%, once fixed | Replaced by KIST, default Oct. 2017 |
| Single-path ceiling | One circuit, one path — capped even when a second built circuit has spare room | Addressed by multipath splitting | Conflux, stable Aug. 2023, conditionally deployed |
Read the table in order: the top row is physics, the second is a rounding error, and the bottom three are two decades of one recurring finding — queueing, not encryption, was where Tor’s fixable latency lived.
SENDME: Tor’s Original, Blunt Flow Control
Before any of the fixes below existed, Tor’s only defense against a circuit overrunning a slow relay was a fixed-size acknowledgment window, and that window was Tor’s real speed limit for roughly two decades. Every circuit tracks a package window and a deliver window, each defaulting to 1,000 DATA-bearing cells, tunable via the consensus parameter circwindow. A relay sends a RELAY_SENDME once its deliver window drops by a full 100-cell increment, crediting the sender’s package window another 100 cells — a coarse, all-or-nothing credit, not an adaptive one. Stream-level windows are smaller still: 500 cells, incremented by 50 per SENDME (spec.torproject.org, flow control).
The mechanism is a hard cap on in-flight, unacknowledged data — a hard cap on the circuit’s effective bandwidth-delay product. The Tor Project’s own retrospective is blunt about the consequence: “Because the Bandwidth-Delay Product was artificially limited to 1000 cells, this fixed window size became a speed limit” (Tor Project blog, “Congestion Control Arrives in Tor 0.4.7-stable!”). A 1,000-cell ceiling was reasonable when throughput expectations were modest; it became artificial as the network grew, without the window itself ever changing.
Fixed windows had a security flaw too, and it shipped first: Proposal 289, the “sniper attack” fix. A malicious client or exit could fetch a large file, then stop reading locally while continuing to emit forged or omitted SENDME acknowledgments — tricking the guard into queueing unbounded unread bytes until it ran out of memory and crashed. Authenticated SENDME cells, shipped starting with tor-0.4.1.1-alpha, close that hole with a rolling 20-byte digest of the preceding 100 DATA cells, so a relay can detect a forged or dropped acknowledgment instead of trusting it blindly (torspec, Proposal 289). It didn’t touch the throughput ceiling — it made the ceiling honest before anyone tried raising it.

The Algorithm Bake-Off: Why Tor-Vegas Won
Raising the ceiling meant replacing a fixed window with an adaptive one: Proposal 324, RTT-based congestion control. The design goal was strict backward compatibility — it had to work by upgrading clients and exit relays only, without requiring every middle relay to update first, because a network with no central operator can’t force a synchronized upgrade.
The Tor design team didn’t ship the first adaptive algorithm that worked in a lab — they ran a bake-off in simulation. Two candidates, Tor-Westwood and Tor-NOLA, suffered “ACK compression”: bursts of delayed acknowledgments arriving together made both algorithms wildly overestimate the available bandwidth-delay product, inducing runaway congestion instead of controlling it. The third, Tor-Vegas, matched theoretical predictions and, critically, wasn’t out-competed by legacy (non-upgraded) traffic sharing the same relays during a mixed-version rollout. Tor-Vegas shipped as the default (Tor Project blog, congestion control; torspec, Proposal 324).
Congestion control landed in Tor 0.4.7.7, stable, released May 4, 2022, when the live network was running at roughly 25% utilization; Tor Browser stable picked it up May 31, 2022. Negotiated per-hop rather than requiring a flag day, benefits accrued gradually as exits and onion services upgraded. The Tor Project’s own 2023 year-in-review credits congestion control together with Conflux (below) with roughly a 2x download-speed multiplier and a more stable experience from longer-lived connections (Tor Project, “2023: Year in Review”).

The move here is one anyone who’s replaced a fixed buffer or queue-depth constant with a measured, adaptive one will recognize: a hardcoded “reasonable default” is a future bottleneck, not a permanent constant. Measuring the round-trip signal and sizing the window to it is the identical logic TCP applied at the transport layer decades earlier, reimplemented at the overlay-circuit layer because TCP’s own congestion control has no visibility into circuit-level contention.
KIST: Give the Scheduler Ground Truth Instead of a Guess
Congestion control fixed how much data a circuit was allowed to have in flight. It didn’t fix a separate problem one layer down: how a relay decided when to actually write queued cells to the socket carrying them onward.
Before KIST (Kernel-Informed Socket Transport), Tor’s scheduler wrote to sockets round-robin, one circuit at a time, blind to whether the kernel’s send buffer had room — self-inflicted bufferbloat that caused head-of-line blocking across every unrelated circuit sharing that relay-to-relay TCP connection: one congested circuit could stall a dozen others uninvolved in it. KIST’s fix queries real kernel/TCP state directly — TCP_INFO and TCP_NOTSENT_LOWAT — before writing, choosing among all circuits with writable data rather than serving one socket at a time regardless of readiness (Jansen et al., “KIST: Kernel-Informed Socket Transport for Tor,” ACM Transactions on Privacy and Security 22(1), Dec. 2018; Tor Project blog, “KIST and Tell”).
The measured effect against the prior scheduler: circuit congestion reduced over 30%, network latency down 18%, throughput up nearly 10%. KIST became the default scheduler in Tor 0.3.2.1-alpha, October 3, 2017, on Linux, where the kernel exposes the buffer-state calls it needs; a portable “KISTLite” variant covers platforms without that introspection.

KIST shipped nearly five years before Proposal 324, and the ordering matters: the scheduler fix came first because it was the cheaper, more local win — no protocol negotiation between client and relay, just a relay using sockets it already controlled more intelligently. The lesson generalizes past Tor: when multiplexing many logical streams over few physical connections, don’t schedule against a stale or assumed model of what the transport can carry — query the real one, the way KIST queries TCP_INFO instead of guessing.
Conflux: Splitting One Stream Across Two Circuits
The last piece addresses a ceiling neither SENDME nor KIST nor Proposal 324 can touch alone: a single circuit is still a single path, capped by that path’s own congestion window, even when a second circuit built to the same destination has spare capacity sitting idle.
Conflux (Proposal 329) is dynamic multipath: a client links two independently pre-built circuits to the same exit (or onion service) and splits one logical stream across both, tracking order with per-leg sequence numbers so the receiver can reassemble anything out of order. Two scheduling algorithms are specified. A latency-optimized mode sends almost exclusively on the lower-RTT leg, switching only if that leg’s RTT rises above the other’s. A throughput-optimized mode sends on whichever leg currently has spare congestion-window room, using both concurrently under load. Neither mode operates blind — each leg’s cwnd - inflight value, taken directly from Proposal 324’s state, feeds the scheduler (spec.torproject.org, Proposal 329).
That coupling is why Conflux couldn’t have shipped before congestion control: naive traffic splitting with no shared signal is a known way to worsen tail latency and reordering, not improve it. Conflux works because both legs report into the same live congestion signal, so path choice reflects real-time state, not a static split ratio.

Conflux shipped in the first stable 0.4.8 release, 0.4.8.4, August 23, 2023. State this precisely rather than assuming it: Conflux is negotiated end-to-end, active only when both client and exit (or onion service) support it, and additionally gated by consensus parameters — cfx_enabled functions as a network-wide kill switch, cfx_low_exit_threshold ties activation to exit-side adoption. That gating exists because the Tor Project kept the option to disable multipath in case single-circuit side-channel mitigations don’t extend cleanly to linked, multi-circuit traffic. Point releases well after ship — 0.4.8.13 (October 24, 2024) among them — fixed Conflux-related circuit-build performance regressions, evidence the feature was still being hardened over a year after going stable. Do not read “stable since August 2023” as “on for every circuit by default.” It shipped stable; it is not unconditionally enabled — verify the current consensus-parameter state before assuming full deployment.
| Generation | Mechanism | What it fixed | Shipped | Status |
|---|---|---|---|---|
| Fixed SENDME windows | Baseline flow control (1,000-cell circuit, 500-cell stream) | Original in-flight-data cap, present since Tor’s earliest design | Original protocol | Superseded as primary throughput limiter; ack mechanism remains |
| Authenticated SENDME | Proposal 289 | The sniper attack — forged/omitted acks forcing unbounded queueing | tor-0.4.1.1-alpha | Deployed, standard |
| KIST scheduling | Kernel-Informed Socket Transport (Jansen et al., ACM TOPS 2018) | Blind round-robin writes causing bufferbloat, cross-circuit blocking | Default 0.3.2.1-alpha, Oct. 2017 | Deployed default (Linux full; KISTLite elsewhere) |
| RTT congestion control | Proposal 324 / Tor-Vegas | The fixed-window bandwidth-delay-product ceiling | Stable 0.4.7.7, May 2022 | Deployed default; benefit scales with adoption |
| Traffic splitting | Proposal 329 / Conflux | Single-path ceiling despite spare capacity on a second circuit | Stable 0.4.8.4, Aug. 2023 | End-to-end negotiated, consensus-gated (cfx_enabled); regression fixes into 2024–2025 |
So, Which Component Actually Dominates?
Walk back through the table and the answer is unambiguous, and it isn’t the one most people assume. Every hop applies one symmetric stream-cipher operation to strip one encryption layer — cheap at Tor’s traffic volumes, and never the bottleneck. What cost users milliseconds, and at the extreme stalled transfers outright, was the machinery around the crypto: an acknowledgment window sized for a smaller network, a scheduler blind to the kernel’s own send-buffer state, and — even after both were fixed — a single circuit unable to use a second one’s spare capacity.
The general lesson underneath the specific Tor history: for any system layering logical connections over a shared transport, the substrate’s queueing behavior is usually the real latency source, and you cannot fix it by making the computation faster. Tor could have shipped a faster cipher at any point in twenty years and none of KIST’s, Proposal 324’s, or Conflux’s measured gains would have shown up — those gains came from three separate fixes to three separate queueing and scheduling problems, shipped years apart, each addressing a different layer: the flow-control window, the socket scheduler, and the number of paths available at all.
The Trade-Off Nobody Gets to Opt Out Of
None of this engineering exists in a vacuum, and it’s worth closing on the tension it sits inside rather than pretending performance work is a free lunch. Tor is explicitly a low-latency anonymity network, designed from the start for interactive use — unlike high-latency mix networks that deliberately batch and delay messages to defeat timing analysis. That choice makes Tor usable for ordinary browsing at all, and it’s also, precisely, what keeps end-to-end timing correlation on the table as a documented, published limitation. Tor’s founding design paper says so directly, in Section 3: it does not claim to completely solve end-to-end timing or intersection attacks. An adversary watching traffic simultaneously at both the point a circuit begins and the point it exits can, in principle, correlate the two ends by timing and volume alone, without breaking a single cryptographic primitive (Tor design paper, USENIX Security 2004).
Every fix this article walked through — SENDME, Tor-Vegas, KIST, Conflux — makes Tor’s traffic more consistent and predictable in the good sense: fewer stalls, steadier throughput, an experience worth using daily instead of abandoning. But consistent, predictable traffic is also exactly the signal a well-positioned observer needs for timing correlation to work. This isn’t a flaw the performance work introduced — the trade-off was already latent in choosing low latency over a high-latency mix network back in 2004 — but every improvement here sharpens both edges of it at once. Low latency isn’t a side effect Tor tolerates in exchange for anonymity; it’s a design decision with a cost attached, made deliberately.
❓ Frequently Asked Questions
Does Tor’s slowness come from the encryption? No. Every relay performs one stream-cipher operation (AES-CTR/AES-GCM) per cell to strip one onion layer — trivial at Tor’s traffic volumes. The dominant, addressable cost was queueing: fixed SENDME windows and a socket scheduler blind to kernel buffer state, fixed years apart, neither one a cryptography problem.
How many hops does my Tor traffic actually take? A client circuit to the ordinary internet is three hops — guard, middle, exit. A .onion connection is different: it joins two independently built three-hop circuits at a rendezvous point, roughly six hops end to end. “Three hops” describes one common circuit type, not a blanket protocol fact.
Is Conflux (traffic splitting) on by default now? Not unconditionally. It shipped stable in Tor 0.4.8.4 (August 2023), but it’s negotiated end-to-end between client and exit, and gated by consensus parameters, including a network-wide cfx_enabled kill switch. Point releases into 2024–2025 kept fixing Conflux-related regressions, indicating ongoing hardening rather than a finished, universal default.
What was the “sniper attack,” and how did Tor fix it? A malicious client or exit could keep sending SENDME acknowledgments for data it had stopped reading, tricking the entry guard into queueing unbounded unread bytes until it ran out of memory. Proposal 289’s authenticated SENDME cells embed a rolling digest of recent data cells so a relay can detect a forged or omitted acknowledgment instead of trusting it blindly.
Why did Tor need its own congestion control instead of relying on ordinary TCP? Because a Tor circuit is a logical connection multiplexed, alongside unrelated circuits, over a shared relay-to-relay TCP connection. TCP’s congestion control governs that connection as a whole and has no visibility into which circuit is contending for bandwidth. Proposal 324 applies RTT-based congestion control at the circuit layer specifically because TCP can’t see that contention.
Does a faster Tor mean weaker anonymity? Not automatically, but the two are coupled. Tor’s deliberately low-latency design is what makes it usable for everyday browsing — and low, consistent latency is also what makes end-to-end timing correlation possible for an adversary watching both ends of a circuit. Every performance improvement that smooths traffic sharpens that same trade-off rather than escaping it.
📚 Further Reading
- Understanding Internet Bandwidth for Engineers and Architects — the bandwidth-delay product behind why a fixed SENDME window becomes a throughput ceiling.
- Measuring Network Performance — “normal” network performance measurement, without an anonymity layer to fight.
- Reactive Extensions (RX) — the same backpressure pattern SENDME implements, in a very different domain.
- Postgres as a Vector Database: What Benchmarks Say — another case study in measuring what actually dominates a system’s cost.
- The Best Books on Distributed Systems — the queueing-theory background behind this article’s protocol history.
- For how Tor measures itself without becoming a deanonymization tool — the bandwidth-authority trust problem left out here — see this series’
measuring-an-unmeasurable-networkarticle. - Primary sources: flow control, Proposal 289, Proposal 324, Proposal 329, congestion control announcement, KIST and Tell, Jansen et al., ACM TOPS 2018, 2023 Year in Review, 2004 USENIX Tor design paper.
📚 Recommended Resources
Disclosure: some links below are Amazon affiliate links. If you buy through them, I earn a small commission — at no extra cost to you — which helps keep this site running.
Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems
Martin Kleppmann & Chris Riccomini · Book · 2nd edition
The canonical text on the exact trade-off this article walks through at the circuit layer: backpressure, queueing, and why a fixed buffer or window quietly becomes a throughput ceiling as a system scales past the load it was sized for.
Distributed Systems
Andrew S. Tanenbaum & Maarten van Steen · Book · 3rd edition
The broader systems grounding behind congestion control, flow control, and scheduling — the general theory underneath Tor’s SENDME windows, KIST, and Conflux, stripped of the anonymity-specific constraints.
Distributed Algorithms
Nancy A. Lynch · Book
The algorithmic rigor behind protocols like Proposal 324’s Tor-Vegas: how you evaluate competing designs for correctness and stability before picking one as a network-wide default, rather than shipping the first one that works in a demo.
Free and worth more than any book here: the primary sources this article cites — the Tor specifications at spec.torproject.org and the Tor Project’s own engineering retrospectives on blog.torproject.org.