Connect
SSHKit has two entry points; both return an SSHConnection. Prefer the scoped variant — it closes the connection on both the success and failure paths and is the safest default.
Scoped (recommended)
import SSHKit
let configuration = SSHClient.Configuration(
host: "example.com",
username: "deploy",
authentication: .password("secret"),
hostKeyPolicy: .knownHostsFile(
("~/.ssh/known_hosts" as NSString).expandingTildeInPath
)
)
let result = try await SSHClient.withConnection(configuration) { connection in
try await connection.execute("uptime")
}
print(String(data: result.standardOutput, encoding: .utf8) ?? "")
Manual ownership
If you need to keep a connection beyond a single block (long-lived shell, an SFTP session shared with multiple progress updates, etc.), open it manually and close it explicitly. Use do/catch for cleanup — do not silently discard close() errors.
let connection = try await SSHClient.connect(configuration: configuration)
do {
try await doWorkWith(connection)
try await connection.close()
} catch {
do { try await connection.close() } catch let closeError {
logger.warning("close after failure failed: \(closeError)")
}
throw error
}
Open one SSHConnection per concurrent job: SFTP, shell, and a tunnel running together need three connections. See ownership invariants.
Cancellation
Cancelling the surrounding Task tears the connection down: SSHKit calls shutdown(fd, SHUT_RDWR) on the owned socket, the blocked libssh call wakes up, and the worker queue cleans up. The connection is gone — open a fresh SSHConnection for the next attempt.
let task = Task {
try await SSHClient.withConnection(configuration) { connection in
try await connection.execute("sleep 30")
}
}
task.cancel() // shuts the socket down, withConnection throws SSHKitError(.cancelled)