Table of Contents

Dependency injection

SshSharp.Extensions.DependencyInjection registers SSH endpoints with Microsoft.Extensions.DependencyInjection, the way AddHttpClient registers HTTP clients: named endpoints from configuration, a factory for new connections, one shared connection per endpoint that is opened again when it drops, and a health check.

dotnet add package SshSharp.Extensions.DependencyInjection

Registering endpoints

builder.Services.AddSshClient("deploy")
    .BindConfiguration("Ssh:Deploy")   // the endpoint's settings, from appsettings.json, environment variables, a secret store ...
    .ValidateOnStart();                // checked when the host starts, not on first use

builder.Services.AddSshClient("db", o => o.ConfigAlias = "prod-db"); // everything from ~/.ssh/config
{
  "Ssh": {
    "Deploy": {
      "Host": "deploy.example.com",
      "UserName": "deployer",
      "PrivateKeyFiles": [ "~/.ssh/deploy_ed25519" ],
      "KnownHostsFiles": [ "/etc/myapp/known_hosts" ],
      "KeepAliveInterval": "00:00:30"
    }
  }
}

AddSshClient(name) returns the endpoint's OptionsBuilder<SshEndpointOptions>, so the usual options calls apply (Configure, PostConfigure, Validate, BindConfiguration). The settings:

Setting Meaning
Host, Port The server (port 22 by default). Required unless ConfigAlias is set.
UserName The user. Required unless ConfigAlias is set.
Password A password, tried after any keys. Bind it from a secret store, not from a checked-in file.
PrivateKeyFiles, PrivateKeyPassphrase Keys to authenticate with (OpenSSH, PEM or PuTTY files; ~/ is the user's home).
KnownHostsFiles Where the server's host key is checked (default ~/.ssh/known_hosts).
AcceptNewHostKeys Record the key of a host not in those files yet (StrictHostKeyChecking accept-new). Off by default: an unknown host fails the connection, and a changed key always does.
ConfigAlias Connect through ssh_config by this alias instead: ProxyJump, IdentityFile, known_hosts and the rest come from there, and Host, Port, UserName, PrivateKeyFiles, KnownHostsFiles, AcceptNewHostKeys and KeepAliveInterval are ignored (ssh_config's ServerAliveInterval applies). Password is still offered, to the destination only, never to a ProxyJump hop.
KeepAliveInterval Keep-alives, so a dead connection is noticed.
ConnectTimeout How long one attempt may take, authentication included (default 30 s); then a TimeoutException.
ReconnectDelay, MaxReconnectDelay The shared connection's wait after a failed attempt (default 1 s, doubling up to 1 min).
ConfigureClientOptions A function from the services and the built SshClientOptions to the options to use: an agent, a custom host key verifier, keys held elsewhere (SshPrivateKey.FromSigner), a proxy. Not used with ConfigAlias.

New connections: ISshClientFactory

public sealed class Deployer(ISshClientFactory ssh)
{
    public async Task RunAsync(CancellationToken cancellationToken)
    {
        await using var client = await ssh.ConnectAsync("deploy", cancellationToken);
        await client.RunCommandAsync("systemctl restart myapp", cancellationToken);
    }
}

Each call opens a new connection, which the caller owns and disposes.

The shared connection: ISshConnectionProvider

public sealed class Status(ISshConnectionProvider connections)
{
    public async Task<string> UptimeAsync(CancellationToken cancellationToken)
    {
        var client = await connections.GetClientAsync("deploy", cancellationToken); // do not dispose: the provider owns it
        return (await client.RunCommandAsync("uptime", cancellationToken)).StandardOutputText;
    }
}

GetClientAsync returns one long-lived client per endpoint, opened on first use and shared by every caller; SSH runs many commands and channels over one connection at once.

  • When it has dropped, the next call opens a new one. Callers at the same moment share that attempt.
  • After a failed attempt, the next one waits (ReconnectDelay, doubling, up to MaxReconnectDelay), so a server that is down is not hammered; a call during that wait fails at once with the last error.
  • Nothing is retried for you: a command that was running when the connection dropped fails, and the application decides whether running it again is safe.
  • The provider disposes the clients when the container is disposed.

Health checks

builder.Services.AddHealthChecks().AddSsh("deploy");

The check pings the endpoint's shared connection, opening it if needed: healthy when it answers, otherwise the registration's failure status (unhealthy by default). Its description names no server software and holds no server text, as health endpoints are often public; the exception is attached for logging, so keep it out of public responses. Its options are those of any health check: a name (default ssh- and the endpoint), the failure status, tags and a timeout.

Logging and testing

Clients log through the container's ILoggerFactory, under the category SshSharp (see Errors and diagnostics).

ISshClientFactory and ISshConnectionProvider are interfaces, so code that takes them can be given a fake in its tests.

Runnable examples: an ASP.NET Core minimal API and a worker service.

See also