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)