SSH.NET: what a C# program actually gets when it opens an SSH connection
SSH.NET is a Secure Shell (SSH) library for .NET, optimized for parallelism.
At a glance
- What is it?
- SSH.NET is an SSH-2 client library for .NET, not a command-line tool, and its value sits in how SshClient, SftpClient and ShellStream split the work. It suits teams writing deployment agents and file movers in C#, and its supported algorithm lists still include legacy options such as 3des-cbc.
- Who is it for?
- Adopt SSH.NET when the deployment target is a .NET service that must move files or run commands over SSH without shipping a Windows OpenSSH binary. Do not adopt it for a tool where you cannot control the negotiated algorithm set, because the supported encryption, MAC and key exchange lists still include 3des-cbc, hmac-sha1 and diffie-hellman-group1-sha1.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- sshnet/SSH.NET4,396 ★ · +15
| Repository | 2026-09-23 | 2026-11-14 |
|---|---|---|
| sshnet/SSH.NET | 4.4k | 4.4k |
15 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
A library you embed, not a client you install
Nothing in SSH.NET arrives as an executable you run from a terminal. You take a reference to the SSH.NET NuGet package, reference a few types from the Renci.SshNet namespace, and the SSH protocol handling becomes part of your own program's process. That distinction decides most adoption questions before a line of code is written. A team that wants an scp command on a build agent has nothing to install here. A team writing a Windows service that pushes release artifacts to a Linux host has exactly what it needs.
The audience is narrow and specific: C# and .NET developers who need the protocol, not a shell. Backup agents, deployment tools, device management software and test harnesses that talk to embedded Linux boards all land in the same place. The project describes itself as a Secure Shell (SSH-2) library for .NET, optimized for parallelism, and the repository root is the ordinary layout for a library of this kind: a src directory, a test directory, a solution file and version.json.
There is no host scanning, no password spraying and no policy bypass anywhere in the surface. Every connection requires a host, a credential and an explicit Connect call from your code. Whatever your program does with the session is your program's decision, which is precisely why this kind of library is a normal engineering dependency.
SshClient gives you one channel per command, then throws it away
The unit of work in SSH.NET is the channel. An SshClient holds the transport, the negotiated algorithms and the connection state; a command run through it returns an SshCommand, which the example disposes with a using statement.
Passing a PrivateKeyFile to the SshClient constructor selects public key authentication; passing a string password selects password authentication. Keyboard-interactive authentication, including multi-factor flows, is also supported, though the README shows no example for it, so the call shape has to be taken from the API browser rather than from a snippet on this page.
Beyond command execution, the same client carries the other transport-level features: remote, dynamic and local port forwarding through ForwardedPort, an interactive terminal through ShellStream, and a route to SOCKS4, SOCKS5 or an HTTP proxy. Because parallelism is the stated design goal, the asynchronous variants exist for command execution and for SFTP, which matters when a single service has to hold dozens of sessions at once.
SFTP is a second client type, not a method on SshClient
File transfer goes through SftpClient, a separate type with its own connection. The constructor shape matches SshClient, taking a host, a username and either a password or a PrivateKeyFile, and the class exposes a synchronous operation set: UploadFile writing a local stream to a remote path, ListDirectory returning ISftpFile entries, and asynchronous counterparts for both.
The ISftpFile interface is what you actually consume in application code, and the example prints two properties off it, FullName and LastWriteTime. Those two are enough to build a manifest or a change detection pass, which is the most common reason a deployment tool reaches for this library at all.
SCP support exists as well, and it sits alongside SFTP in the same library rather than in a separate package. One detail worth planning around: the transfer path shown uses File.OpenRead on a Windows-style path, so the calling code is doing local file handling in the host application's framework. The library handles the protocol, not the filesystem.
Adding the package and running the first command
The README gives no dotnet CLI install line. It points at the NuGet package for SSH.NET, so the reference comes from the package feed rather than from a command printed on the page, and the first real run looks like this:
using (var client = new SshClient("sftp.foo.com", "guest", new PrivateKeyFile("path/to/my/key")))
{
client.Connect();
using SshCommand cmd = client.RunCommand("echo 'Hello World!'");
Console.WriteLine(cmd.Result); // "Hello World!\n"
}Three arguments decide the whole connection: the host, the username, and the credential object. A PrivateKeyFile instance means public key authentication, and the path is resolved by your process, so a key on disk is read from where the host application says it is.
What you should see is the command output on the console and a clean exit, with the client disposed by the outer using block and the command by the inner one. The Result property carries the standard output string, which includes the trailing newline the comment shows.
Target frameworks are listed as .NET Framework 4.6.2 and higher, .NET Standard 2.0, and .NET 8 and higher, so the same source can span a legacy Windows service and a current .NET host. Building from source needs nothing beyond an up-to-date .NET SDK.
The develop branch publishes to the GitHub NuGet Registry, not nuget.org
Stable consumers never touch this part. Teams tracking the develop branch do, and the setup carries a real cost that is easy to miss. Pre-release packages are published to the GitHub NuGet Registry rather than to nuget.org, so the machine has to be told about a second package source before the package resolves at all:
dotnet nuget add source --name SSH.NET --username <username> --password <personalaccesstoken> https://nuget.pkg.github.com/sshnet/index.jsonThe token needs the read:packages permission. On non-Windows platforms the README notes you may also have to add --store-password-in-clear-text, which is a plain statement of fact about where the credential lands on disk.
For a build agent, that means a personal access token provisioned as a secret, a per-agent source entry, and a decision about whether your pipeline runs code from a token-gated feed at all. Consuming a moving develop feed is a different risk posture from pinning a version, and the token requirement makes that choice visible rather than accidental.
Compression defaults to none, and zlib is the only alternative
The compression list has exactly two entries: none, marked as the default, and [email protected]. A skim reader misses that because a two-item list looks like a stub rather than a policy. It is a policy. Interactive shell traffic on a fast link rarely benefits, and the default of none means no compression negotiation happens unless you change it.
The consequence for a file mover is direct. An SFTP upload over a high latency link does no compression at all by default, so transferring large text or log archives across a WAN pays the full bandwidth.
Compare that with the cipher list, which has ten entries and no such restraint. The asymmetry is the finding here: the project tightened one algorithm family down to nothing plus a modern option and left the others broad.
3des-cbc, hmac-sha1 and group1 are still in the default negotiation lists
The encryption list includes aes128-ctr, aes192-ctr, aes256-ctr, the GCM variants, [email protected], the CBC modes and 3des-cbc. The key exchange list runs from mlkem768x25519-sha256 and sntrup761x25519-sha512 at the top down through curve25519-sha256, the NIST curves and the Diffie-Hellman groups, ending at diffie-hellman-group1-sha1. The MAC list still carries hmac-sha1 and its ETM variant, and the host key list still includes ssh-rsa.
That is a wide surface. A modern default configuration would present the negotiation, not enumerate forty choices and let the server pick. Anyone building a compliance baseline will want an allow list on the client rather than the built-in set, and the README does not document how to configure one.
The key format matrix has the same character. PuTTY private keys support exactly one cipher, aes256-cbc, while OpenSSH format keys accept the full CBC and CTR set including the GCM modes. ED25519 keys are accepted in PKCS#8 PEM, OpenSSH and PuTTY private key formats.
Where SSH.NET differs from the other approaches
The obvious comparison is not another C# library but a different deployment shape. Wrapping the OpenSSH command-line client, by spawning ssh and sftp as child processes, moves protocol handling out of your process and into a binary you have to ship, locate and monitor. SSH.NET keeps the entire transport in managed code and in your own exception and logging model, which is why it fits a long-running Windows service that already has a logging pipeline.
The Python equivalent, Paramiko, takes the opposite design path. It builds on Python's own thread and select primitives and exposes the same object model of client, transport and channel, so the architectural difference is not the API shape but the runtime underneath. Moving between the two means rewriting calls, not rethinking the session model.
Neither approach removes the algorithm negotiation problem. What SSH.NET changes is who owns it: you own it, in C#, in your process, with your configuration.
Editorial conclusion
Adopt SSH.NET when the deployment target is a .NET service that must move files or run commands over SSH without shipping a Windows OpenSSH binary. Do not adopt it for a tool where you cannot control the negotiated algorithm set, because the supported encryption, MAC and key exchange lists still include 3des-cbc, hmac-sha1 and diffie-hellman-group1-sha1. Before writing any code, verify one thing: open a connection to your real server and confirm which cipher, key exchange and host key algorithm the handshake actually picks.
Frequently asked questions
what is ssh net
SSH.NET is a Secure Shell (SSH-2) library for .NET, distributed as the SSH.NET NuGet package. It is a library you reference from C# code rather than a client you run, and it is described as optimized for parallelism.
how to use ssh net in c#
Create an SshClient with a host, a username and a credential such as a PrivateKeyFile, call Connect, then run work through it. RunCommand returns an SshCommand whose Result property holds the standard output, and SftpClient provides UploadFile and ListDirectory for transfers.
how to use ssh net
Add a reference to the SSH.NET package from the NuGet feed the README links to, then use the types in the Renci.SshNet namespace. Pre-release builds are published to the GitHub NuGet Registry instead, which requires adding that registry as a package source with a token holding read:packages.
is ssh net free
Yes. The project carries the MIT license, so it can be used in commercial and closed source products. The repository also ships a THIRD-PARTY-NOTICES.TXT alongside the license file.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/sshnet-ssh-net)Star history chart for your README
[](https://hysenlabs.com/projects/sshnet-ssh-net)Was this useful?