Two handshakes · one connection

How far away is the browser that speaks TLS to us?

Every HTTPS connection carries two handshakes. The TCP handshake is answered by whatever connected to us; through a proxy, that is the proxy. The TLS handshake cannot be answered by a proxy – it has to reach the real browser. We time both on our own socket and divide. No JavaScript is needed for the measurement itself.

Timing your TCP and TLS handshakes…

How It Works

1

The TCP clock. Linux keeps a running estimate of the round trip time of every TCP connection, and we read its minimum straight from the socket (TCP_INFO). It measures the distance to whatever completed the three-way handshake. On a direct connection that is your machine. Through an HTTP or SOCKS proxy, a residential proxy network or a TLS-terminating gateway it is the proxy – and proxy exits tend to sit in well-connected places, a few milliseconds from our servers.

2

The TLS clock. Our engine terminates TLS itself, with nothing in front of it, so it sees the moment your ClientHello arrives and the moment the handshake completes. A proxy relays these bytes but cannot answer them: it does not hold a certificate for our name, and a browser would refuse one it forged. So the TLS handshake always round-trips to the real browser, however many hops sit in between.

3

Divide, don't measure. On a direct connection both clocks reach the same machine, so the TLS handshake takes about one round trip plus a little cryptography: a ratio near 1–2×. Behind a relay the TCP clock stops at the proxy while the TLS clock continues to the browser, and the ratio climbs to 3× and far beyond. Because it is a ratio, your distance from us and the jitter on the path cancel out; only an extra terminating hop does not. A ratio above 3.0 raises an eyebrow, above 3.5 it makes a claim – and the two handshakes must differ by at least 20 ms, so that on a very short path the browser's crypto time alone cannot look like a relay.

4

A slow client is not a proxy. An overloaded phone stalls between our reply and its Finished, which inflates the TLS clock exactly like a relay would. What tells them apart is the wait before the ClientHello: a proxy completes our TCP handshake first, confirms the tunnel to the browser, and only then does the ClientHello travel back through the chain – one hidden round trip, 94–455 ms in live traffic. A stalled direct client sends its ClientHello within 0–16 ms of connecting. Some proxy networks ship the ClientHello together with the connect request and show no wait, but their exits sit in datacenters, where no browser lives. A high ratio is therefore called relayed only with a real wait or a hosting exit; otherwise it stays suspicious.

5

Explain before you accuse. Zscaler, Cisco Umbrella, iCloud Private Relay and similar gateways terminate TLS by design and score 20–40×. Their addresses carry an ipapi.is egress-service flag, which is checked before any threshold: they are reported as a gateway, not counted as a proxy.

6

What it cannot see. A full-tunnel VPN carries the browser's own TCP stack, so both handshakes reach the same machine and the ratio stays near 1 – VPNs are the job of the latency and VPN tests, not this one. A resumed TLS session needs fewer round trips and lowers the ratio, which only makes the test more conservative. When WebRTC is available, the UDP egress test answers first and this test is the fallback.

Credit. This test implements the method published by Justin at proxy.kyc.red: comparing the TLS handshake time against the kernel's TCP RTT of the same connection. His write-up includes the reasoning and measured ratios across sixty residential proxy exits. Read it at https://proxy.kyc.red/.