Inside a Tor Circuit: Channels, Cells, Streams, DNS, and TLS
Let’s open example.com. The browser requests a connection to the site and begins exchanging bytes: first the HTML document, then the resources the page needs.
Tor already has a built path:
client → guard → middle → exit
The client is the Tor software on the device. The guard, middle, and exit are relays: servers that forward traffic. The guard is the first relay, the middle is the intermediate one, and the exit is the last Tor relay and opens the connection to the site.
Let’s follow a single browser connection.
- The client creates a stream for
example.com:443. - The stream uses a circuit already built through the guard, middle, and exit.
- The stream’s bytes are divided into relay messages carried inside cells.
- The cells travel over channels connecting each pair of adjacent participants.
The four names now have concrete objects to attach to.
Four Objects, Four Scales
The stream is the logical application flow. A second compatible connection can use another StreamID on the same circuit without merging with the first.
The circuit is the logical path through multiple relays. It is not a single network connection from the client to the exit.
The cell is the message unit Tor transports. Relays do not receive a “web page” as an object; they receive cells associated with local circuits.
The channel is the authenticated connection between two adjacent participants. Our path has one between client and guard, one between guard and middle, and one between middle and exit. The same channel can carry multiple circuits.
In short: a stream uses a circuit; its bytes travel in cells; each cell crosses a sequence of channels. There is no required one HTTP request = one cell correspondence.
Two Identifiers with Two Different Scopes
Every channel must distinguish the cells belonging to the circuits it carries. It does so with a CircID, a circuit identifier valid only on that channel.
This scope matters. The same cell can belong to CircID 17 between client and guard, CircID 81 between guard and middle, and CircID 36 between middle and exit. The numbers are illustrative, but the principle is real: there is no global CircID that crosses the entire path unchanged.
Inside a relay cell, the StreamID instead identifies the application stream to which the message belongs. Its value has meaning within the circuit carrying the cell: the client chooses a nonzero identifier not already in use on that circuit. Two compatible flows can therefore use the same circuit while remaining separate.
The CircID answers “which circuit on this link?” The StreamID answers “which flow inside that circuit?”
A Cell Is Not Simply “512 Bytes”
Many explanations say that a Tor cell is always 512 bytes long. That shortcut is too rigid.
Most cells use a fixed length in the protocol version negotiated between two neighbors, while some commands use variable-length cells. The header and content are also different things. Without specifying the version and the observed layer, an isolated number creates more confusion than precision.
For our model, it is enough to distinguish the cell envelope from the relay message it contains. A relay message can include:
- the command, such as opening, carrying data, or closing a stream;
- the StreamID;
- fields used for recognition and integrity;
- the data length;
- data and padding.
These are Tor protocol fields. They are not HTTP request fields, nor are they an Internet Protocol (IP) packet.
Toward the Exit, the Client Prepares the Layers
Suppose a relay cell must reach the exit. The client prepares the message for the exit and applies the cryptographic transformations required for each relay in the path, from the logical recipient back to the first relay.
The result is sent to the guard over the first channel, using that link’s local CircID.
The guard processes its layer and checks whether the relay message is logically addressed to it. If it does not recognize the message as its own, it forwards it over the next channel using the CircID valid toward the middle.
The middle performs the same operation and forwards toward the exit with another local CircID. Only after processing its own layer does the exit recognize the relay message addressed to it.
The onion is therefore not an opaque packet that preserves the same shape and identifier from beginning to end. Each relay maintains local circuit state, processes its own layer, and produces what must be sent to the next neighbor.
The Response Is Not the Same Animation in Reverse
The return direction uses keys and transformations distinct from those used in the forward direction.
The exit originates the response relay cell. Each relay it crosses adds its protection before sending it to the previous neighbor. The client receives the result from the guard and removes the layers in the prescribed order until it identifies the cell’s logical origin.
The guard and middle do not “rebuild the original onion.” They apply the return-direction cryptographic state associated with their segment.
How a Stream to the Internet Begins
Tor already has an open circuit to a suitable exit. The browser now wants to reach example.com on port 443.
The client assigns a StreamID and sends a RELAY_BEGIN command with the destination and port toward the exit. If it receives a name such as example.com, the exit performs Domain Name System (DNS) resolution—looking up the associated IP address—and opens a separate Transmission Control Protocol (TCP) connection to that address.
The browser communicates with the Tor client through the local proxy interface. Tor carries those bytes as a stream in the circuit; the exit transfers them onto the external TCP connection. No single TCP connection crosses the circuit from browser to site.
If the connection succeeds, the exit responds with RELAY_CONNECTED. If it cannot open the connection, it responds with RELAY_END and a protocol-defined error reason.
In the correct flow, the device’s local DNS resolver should therefore never receive the request for example.com. The name reaches the exit through Tor and is resolved there.
This protection depends on the application. If a program resolves the domain locally before handing it to Tor, that DNS request originates outside the circuit. Tor can protect only the traffic it actually receives.
After Opening, Bytes Travel—not Entire HTTP Requests
Once the stream is open, the client and exit carry bytes through RELAY_DATA messages associated with the StreamID.
A large response requires multiple messages. A single message may contain only some of the bytes the application considers one response. Tor does not need to preserve the boundaries between an HTTP method, headers, body, image, or Cascading Style Sheets (CSS) file as protocol primitives.
This explains how multiple streams can share a circuit without becoming confused: the circuit provides the path, the StreamID separates the flows, and cells carry portions of their data.
Tor Ends at the Exit; TLS Can Continue
The Tor circuit connects the client to the exit. The external TCP connection connects the exit to the site. If the browser uses HTTPS, the browser-to-site TLS session records cross the local connection to Tor, the Tor stream, and the external TCP connection: application-layer encryption protects the web content between the two endpoints.
The two protections have different endpoints:
- Tor’s circuit protection ends at the exit;
- application TLS protection crosses the exit and ends at the site.
The exit must know the network destination required to open the connection. In the standard model, the circuit does not directly give it the client’s original IP address. Correlation, collusion, compromise, and application data belong to the threat model we will address at the end of the series.
With correctly authenticated HTTPS, the exit carries encrypted application bytes without being able to freely read the HTTP content. Without end-to-end application encryption, a malicious exit can instead observe or modify plaintext data.
The site therefore receives the connection from the exit, not directly from the client, while different relays hold different parts of the context. Tor does not replace TLS.
The Flow Is Not an Endless Tap
A sender cannot keep sending data relay cells without accounting for the receiver’s capacity.
Tor uses flow-control windows and SENDME messages: sending consumes credit; receiving valid data periodically triggers a SENDME, which acknowledges receipt and restores credit. In modern modes, these signals also contribute to congestion control. The sender slows down when the path is not clearing cells quickly enough.
The complete congestion protocol contains additional states and details. The invariant is enough here: the circuit transports data under flow control, not as an unlimited sequence of arrows.
We can finally put the famous onion in the right place. It does not wrap an entire web page, and it is not the circuit itself. It is how relay cells are transformed while carrying the bytes of one or more streams over an already built path.
For a normal site, the exit ultimately converts that Tor stream into a connection to the Internet. A .onion, by contrast, needs no public exit: first we must understand how the client discovers a service that does not publish its IP address.
Technical Sources
Internal transport. Tor Specifications, Channels , definition and authentication; Cells — messages on channels , opening and “Interpreting the fields: CircID”; Relay cells , format and StreamID; Routing relay cells , “Forward Direction” and “Backward Direction”; Setting circuit keys , directional keys and digests.
Streams and exits. Tor Specifications, Opening streams and transmitting data , “The begin/connected handshake” and “Transmitting data”; Flow control , windows and SENDME; Proposal 324 — RTT-based congestion control , Finished state. Tor Project Support, HTTPS encryption and Tor , the distinct endpoints of Tor and HTTPS.
Online specifications and documentation consulted August 14, 2026.