SSH Port Latency
Measure SSH-port connect timing and first-byte timing directly from the Traversio library.
Traversio exposes a small public utility API for measuring how an SSH port behaves before you get into key exchange or authentication.
Use SSHClient.measurePortLatency(...) when you want to compare:
- raw TCP connection setup time
- how long it took before the server sent the first observable byte back
This is useful for support screens, diagnostics exports, and server responsiveness checks before a full SSH handshake.
API Shape
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, visionOS 1.0, *)
public static func measurePortLatency(
host: String,
port: UInt16 = 22,
options: SSHPortLatencyOptions = .init()
) async throws -> SSHPortLatencyReportSupporting public types:
SSHPortLatencyOptionsSSHPortLatencyReportSSHPortLatencySampleSSHPortLatencyFailureSSHPortLatencyStatistics
Measure An Endpoint
import Traversio
@available(macOS 10.15, iOS 13.0, *)
func inspectPort() async throws {
let report = try await SSHClient.measurePortLatency(
host: "example.com",
port: 22,
options: SSHPortLatencyOptions(
sampleCount: 5,
connectTimeout: 2,
firstServerByteTimeout: 2,
delayBetweenSamples: 0.1
)
)
print(report.connectRTTStatistics.averageMilliseconds)
print(report.estimatedPathOneWayFromFirstServerByteStatistics.averageMilliseconds)
}The report keeps both the raw timings and the aggregated statistics, so your app can either:
- show only the summary values
- expose every sample attempt
- keep the failures alongside the successful samples in a support export
The Two Main Metrics
The library keeps both of these on purpose. They answer different questions.
report.connectRTTStatistics.averageMilliseconds
This is the average time from connect() start until the TCP socket is ready.
Use it when you want to know:
- how fast the local machine can establish a TCP connection on the current path
- whether a nearby VPN, proxy, or load balancer is accepting the connection quickly
- whether failures are happening before any server-side SSH bytes are seen
This is the most direct TCP-side timing number. It is also the easiest one to misread.
report.estimatedPathOneWayFromFirstServerByteStatistics.averageMilliseconds
This is derived from:
report.firstServerByteAfterConnectStatistics.averageMilliseconds / 2
The library first measures how long it takes, after local connect completion, to receive the first byte sent by the server. It then divides that value by 2 and exposes that number as an estimate.
Use it when you want something closer to:
- "how long did it take before the server actually answered?"
- "is this endpoint really nearby, or did a local proxy only make
connect()look fast?" - "which server feels farther away when VPN or transparent proxying flattens
connect RTT?"
This remains an estimate derived from the first observed server response after connect.
How To Choose
If you are deciding which metric to show in your own tooling, use this rule:
- show
report.connectRTTStatistics.averageMillisecondswhen you care about raw TCP connection setup on the local path - show
report.estimatedPathOneWayFromFirstServerByteStatistics.averageMillisecondswhen you care about the earliest observable server response - show both when you need to explain why a quick TCP connect and a slower first server response can coexist
In support and operations workflows, showing both is usually the most useful choice.
Why The Numbers Can Disagree
connect RTT and first server byte after connect can diverge significantly, and that difference often reflects the network path accurately.
Common reasons:
- a VPN or transparent proxy accepts the TCP connection near the client, so
connect RTTbecomes very small - the remote SSH server is still far away, so the first returned byte arrives much later
- a proxy chain, load balancer, or middlebox finishes the TCP setup before the real server has replied
- the server adds a small delay before sending its identification line
That means this kind of result is normal:
let report = try await SSHClient.measurePortLatency(host: "example.com")
print(report.connectRTTStatistics.averageMilliseconds)
print(report.firstServerByteAfterConnectStatistics.averageMilliseconds)
print(report.estimatedPathOneWayFromFirstServerByteStatistics.averageMilliseconds)You might see a very small connect RTT and a much larger first-byte-derived estimate. That usually means the TCP connection completed near the client, but the real server response still had to travel farther.
What Counts As "The First Server Byte"
At the TCP layer, the earliest server response is SYN-ACK, but stream sockets do not expose packet boundaries to the application.
At the SSH application layer, the earliest bytes are usually from the server identification line:
SSH-2.0-OpenSSH_9.9But RFC 4253 allows the server to send non-SSH- text lines before that identification line. For that reason, this API deliberately talks about the first received server byte, not the banner as a whole.
That gives you the earliest application-visible server response without pretending the library can see individual TCP packets.
Scope
This API intentionally stays narrow:
- it opens a direct TCP socket to
host:port - it works outside
SSHClientConfiguration - it does not route through
connectionProxyorproxyJumpHosts - it stops before authentication, host-key verification, and the full SSH handshake
Use it as a latency utility. Full session success still depends on the later SSH stages.
Related Types
SSHPortLatencyOptions controls:
sampleCountconnectTimeoutfirstServerByteTimeoutdelayBetweenSamples
SSHPortLatencyReport gives you:
samplesfailuresconnectRTTStatisticsfirstServerByteAfterConnectStatisticsestimatedPathOneWayFromFirstServerByteStatistics
Recommended Default
For developer-facing diagnostics pages and support bundles:
- keep
report.connectRTTStatistics.averageMilliseconds - keep
report.estimatedPathOneWayFromFirstServerByteStatistics.averageMilliseconds - explain that the second metric is usually the better approximation of "how long until the server answered", especially on VPN or proxied paths
That gives developers enough context to choose the metric that matches their own environment and support workflow.
For the full type list and signatures, see Public API. For real connection failures, read Diagnostics.