Local Port Forwarding
Bind a local port on your machine and forward it to one fixed remote TCP target over SSH.
Local port forwarding is the classic answer to this question:
"A service is running on the server. How do I open it locally for debugging?"
If a remote HTTP server is listening on 127.0.0.1:3000, and you want to visit it locally as http://127.0.0.1:8080, this is the mode you want.
Mental Model
browser / URLSession / local tool
-> local listener on your machine
-> SSH tunnel
-> one fixed remote targetThe local port is what other local software connects to.
The remote target is fixed when you create the forward.
Example: Read A Remote HTTP Service Locally
import Foundation
import Traversio
@available(macOS 26.0, iOS 26.0, *)
func readInternalStatus(configuration: SSHClientConfiguration) async throws -> String {
try await SSHClient.withConnection(configuration: configuration) { connection in
try await connection.withLocalPortForwarding(
targetHost: "127.0.0.1",
targetPort: 3000,
localHost: "127.0.0.1",
localPort: 8080
) { forward in
let url = URL(
string: "http://\(forward.localHost):\(forward.localPort)/health"
)!
let (data, _) = try await URLSession.shared.data(from: url)
return String(decoding: data, as: UTF8.self)
}
}
}The same pattern works if you want to:
- open the forwarded URL in a browser
- point a REST client at a remote-only API
- let a local database tool connect to a remote database with one fixed host and port
How It Differs From Dynamic Forwarding
Both local and dynamic forwarding create something on your local machine.
The difference is who chooses the target:
- local forwarding: Traversio fixes the target when the forward starts
- dynamic forwarding: each local client chooses the target later through SOCKS
So if you already know the remote target is always 127.0.0.1:3000, local forwarding is simpler.
If you want one local proxy that can reach many different destinations, use Dynamic Port Forwarding instead.
What The Closure Receives
The closure gets an SSHLocalPortForward:
localHostlocalPorttargetHosttargetPort
That tells you exactly which local endpoint is active and which remote TCP service it maps to.
Availability And Lifetime
Important lifecycle rules:
- this helper is currently available on Apple 26+ only
- the local listener exists only inside the
withLocalPortForwarding(...)body - once the body exits, Traversio stops forwarding new traffic through that scope
- the underlying
SSHConnectionmust still be alive for the tunnel to work
Shutdown Behavior
The local helper uses a best-effort shutdown contract.
That means:
- Traversio stops bridging data when the scope ends
- late local accepts are closed as quickly as possible
- the bound port may remain connectable for a brief window while shutdown completes
Accepted connection failures are isolated per connection too:
- if one accepted local client cannot open its remote
direct-tcpipchannel, Traversio closes that local client connection - the listener stays available for later local clients in the same forwarding scope
Support Status
As of April 6, 2026:
- the local forwarding data path is live-validated against a real local OpenSSH 9.6 target
- it is a closure-scoped helper
openDirectTCPIPChannel(...)is the expert path for raw per-connection control