Traversio

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 -> SSHPortLatencyReport

Supporting public types:

  • SSHPortLatencyOptions
  • SSHPortLatencyReport
  • SSHPortLatencySample
  • SSHPortLatencyFailure
  • SSHPortLatencyStatistics

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.averageMilliseconds when you care about raw TCP connection setup on the local path
  • show report.estimatedPathOneWayFromFirstServerByteStatistics.averageMilliseconds when 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 RTT becomes 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.9

But 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 connectionProxy or proxyJumpHosts
  • 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.

SSHPortLatencyOptions controls:

  • sampleCount
  • connectTimeout
  • firstServerByteTimeout
  • delayBetweenSamples

SSHPortLatencyReport gives you:

  • samples
  • failures
  • connectRTTStatistics
  • firstServerByteAfterConnectStatistics
  • estimatedPathOneWayFromFirstServerByteStatistics

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.

On this page