Using SSH for a jumphost (aka jump server, jump box, or bastion host) works pretty darn well. In places where you’re already using it, and it’s working well for you and your team, it’s hard to argue that you should ditch it and replace it with WireGuard.
But WireGuard has enough small advantages over SSH that if you’re setting up a new jumphost (or adding a new group of users), you should consider using WireGuard instead of SSH, for the following reasons:
But first, let’s take a quick look at how using an SSH jumphost differs from a WireGuard one. Let’s say you have a scenario where you’re sitting in your local coffeeshop, and you need to connect securely from your laptop to a few servers in some private cloud, including:
A back-end server that you need to login via SSH to administer
A database server against which you need to run some SQL queries
A webserver with a private admin web UI you need to use
With an SSH jumphost, the scenario would look like this:
In this scenario, you would need an SSH client on your laptop, with which you would connect to the jumphost. To connect to the back-end server, you’d connect to the jumphost over SSH using the
ProxyJump configuration directive (or the
-J command-line flag), so that the jumphost would pipe your SSH connection on to the back-end server. You’d typically run a command like this:
ssh -J email@example.com firstname.lastname@example.org
To connect to the database server, you’d connect to the jumphost over SSH with the
LocalForward configuration directive (or the
-L command-line flag), so that your SSH client would pipe your database connection through your SSH connection to the jumphost, and the jumphost would send your database connection on to the database server (some database clients have this functionality built into them, so you don’t have to set up the SSH connection manually with a separate SSH client).
You’d typically run a command like this to set up the SSH connection:
ssh -L 1234:database.internal.example.com:3306 -N email@example.com
And then connect to your database with a command like this (with the MySQL command-line client, for example):
mysql -h localhost -P 1234
To connect to the webserver, you’d do the same thing as with the database: connect to the jumphost over SSH with the
LocalForward directive (or
-L flag), which would pipe your HTTP connection through SSH to the jumphost, and then send it on to the webserver. You’d typically run a command like this:
ssh -L 8080:webapp.internal.example.com:80 -N firstname.lastname@example.org
And then point your web browser to
With a WireGuard jumphost, the scenario would look like this:
With a WireGuard jumphost, you need a WireGuard client on your laptop. You’d use your OS’s network manager UI or dedicated WireGuard app to start up a WireGuard connection to the jumphost. This will automatically forward your network traffic for the hosts at the cloud site through the encrypted WireGuard tunnel (as configured by your WireGuard client’s settings).
To SSH into the back-end server, with your WireGuard tunnel up and running, you’d just run a command like this:
And to connect to the database, you’d just run a command like this:
mysql -h database.internal.example.com -P 3306
And to connect to the webserver, you’d just point your web browser to
http://webapp.internal.example.com/. Based on your WireGuard configuration, the traffic for all three would automatically be routed through the WireGuard jumphost.
Why not use an SSH jumphost? The biggest downside to exposing any SSH server directly to the Internet is that there are lots of ways it can be misconfigured. While OpenSSH itself is battle tested and used everywhere, it’s also often poorly configured and used with bad practices everywhere.
Here’s a catalog of some of the most common bad practices:
Many Linux and BSD distros still ship with their default configuration set to allow the root user to login over SSH. This is not something you want enabled on a jumphost — if combined with a password-authentication misconfiguration, you’re only a bad (or compromised) password away from a complete security disaster. There’s no equivalent misconfiguration possible with WireGuard.
The best practice is to disallow SSH root login with the following
It’s hard for a regular user to come up with and use a password that’s as strong as even the weakest SSH key. But again, many SSH servers are still configured to allow password authentication. This is not something you want to allow on a jumphost — always use SSH keys.
The best practice is to disallow SSH password authentication with the following
There is no equivalent misconfiguration possible with WireGuard — with WireGuard you always must authenticate with a 256-bit X25519 key.
Unlike with a regular server, on a jumphost most users with SSH access should not be allowed to run a shell on the jumphost. Only jumphost administrators need to be able to run commands on it — all other users just need their SSH connections or other network traffic forwarded.
This can be tricky to configure correctly with SSH, but the general best practice is to use the
ForceCommand configuration option in the jumphost’s
/etc/ssh/sshd_config to prevent shell access by default (like by allowing only the
/sbin/nologin program to be executed), and then explicitly grant unrestricted access to the users or groups who need it (with the
ForceCommand no setting):
ForceCommand /sbin/nologin Match Group wheel ForceCommand no
See the Ungleich Blog for a good discussion of SSH Proxying Without Allowing Shell Access, with some other alternatives.
There is no equivalent misconfiguration possible with WireGuard — WireGuard does not allow any kind of shell access.
Many Linux and BSD distros also ship with default configuration allowing X11 display forwarding. However, X11 is notoriously insecure by design, and rarely used over WAN connections.
The best practice is to disallow X11 forwarding over SSH with the following
A better and safer alternative in most cases is to use VNC or RDP over WireGuard.
SSH agent forwarding is also still allowed by many SSH server configurations, and is still frequently used by SSH clients. However, this is insecure, as it allows anyone with root access on the server to intercept and manipulate all forwarded SSH connections.
The best practice is to disallow SSH agent forwarding with the following
Instead of using SSH agent forwarding, use the
ProxyJump configuration directive in your SSH client configuration (or the
-J flag on the SSH command line). Bogdan Popa has a succinct writeup about The Problem With SSH Agent Forwarding, and what to do instead.
There’s no equivalent to agent forwarding (or SSH proxying) with WireGuard. The safe alternative with WireGuard is to tunnel SSH traffic from client to jumphost through WireGuard, and allow the jumphost to forward SSH traffic to the destination SSH server.
For compatibility with older versions of SSH, many SSH configurations allow the use of weak crypto algorithms, such as Triple DES or MD5. A well-funded adversary may be able to defeat encryption composed from those primitives, however, so connections using them are vulnerable to interception or manipulation.
The best practice is to allow only strong cryptographic algorithms by default. If you still have to support a particular host that can’t be upgraded to use a modern version of SSH, you can carve out an exception for that particular host in your SSH config files (on client or server). For your ciphersuite defaults, the recommendations for Secure Secure Shell from stribika, though several years old now, still hold up. The Mozilla OpenSSH Guidelines also have similar recommendations.
WireGuard has no crypto agility — you can only use the one single ciphersuite that all WireGuard clients support — so there’s no way to shoot yourself in the foot with bad cryptographic choices. The crypto primitives WireGuard uses were chosen because they’re all simple, secure, fast, and avoid the mistakes from earlier generations of cryptography.
Many SSH servers are set up with weak keys. There are three common key problems: one, related to the weak ciphesuites problem above, is that many SSH servers include DSA and ECDSA keys (and furthermore, usually ECDSA with the controversial P-256 curve). Both DSA and ECDSA have some weaknesses compared to RSA and Ed25519 keys, which should be used instead where possible. Nicolas Béguier has a good Comparison of SSH Key Algorithms that goes into detail about this.
The second problem is small RSA key sizes. The current NIST Recommendation for Key Management is to use RSA keys of at least 2048 bits or larger; however, many RSA keys 1024 bits or smaller are still in use.
The third problem is that for virtual servers hosted in the cloud, a buggy VM image-creation process can cause the same SSH key to be used by all VMs booted from the same image (like the much-publicized Digital Ocean Duplicate SSH Host Keys bug).
So the best practice for using strong SSH keys is to 1) use only RSA and Ed25519 keys, 2) use at least 2048 bits for RSA keys, and 3) regenerate host keys after the first boot of a VM.
While it is also possible to accidentally bake your WireGuard keys into a VM image, WireGuard is immune from the first two problems: WireGuard only supports X25519 keys (similar to the Ed25519 keys used by SSH), and they only come in one size, 256 bits (larger than the NIST-recommended minimum for elliptic-curve keys, 224 bits).
The final common problem with SSH is that most SSH clients are not initially configured to know which SSH server keys are legitimate. Instead, they are usually configured to prompt the user the first time a server key is encountered, to ask the user whether or not they think the key is legit (or worse, sometimes clients are configured to just accept any unknown key as legitimate). Since all SSH keys look roughly the same to the human eye, it’s easy to trick a user into accepting an illegitimate key; an adversary who is positioned to do so would then be able to intercept and manipulate that user’s connection to the SSH server.
The best practice is to fully-enable strict host-key checking with the following
~/.ssh/config setting on each SSH client:
And then pre-populate each client’s
~/.ssh/known_hosts file with the public SSH key (or CA key) of each server to which the client may connect. Gert van Dijk has a thorough overview of this with SSH Host Key Validation Done Right.
With WireGuard, the equivalent of strict host-key checking is mandatory, and cannot be disabled. Each WireGuard client must be pre-configured with the public keys of all the WireGuard servers to which it is allowed to connect.
Compared to SSH, WireGuard configuration is easy, and difficult to screw up in a way that could create security issues. A typical configuration file that would allow a client to connect to a jumphost would look like this:
[Interface] # client's private WireGuard key PrivateKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEE= # client's private IP address within the WireGuard network Address = 10.0.0.10/32 [Peer] # jumphost's public WireGuard key PublicKey = fE/wdxzl0klVp/IR8UcaoGUMjqaWi3jAd7KzHKFS6Ds= # jumphost's public IP address and WireGuard port Endpoint = 203.0.113.2:51820 # private IP addresses of services that should be routed through the jumphost AllowedIPs = 192.168.200.0/24
The only two security-related things a WireGuard user might screw up are 1) sharing their private WireGuard key with others, or 2) failing to use the basic firewall software on their workstation that blocks inbound or forwarded network connections (like Windows Defender Firewall or the macOS Firewall). Doing 1) would allow someone else to use the user’s credentials to connect to the jumphost; and 2) would allow anyone with root access to the jumphost (or any kind of access, if the jumphost was not properly locked down) to initiate network connections to the user’s workstation (or other hosts on the user’s LAN).
WireGuard has a high performance, in-kernel implementation for Windows, which makes it approximately as fast to use on Windows as it is on Linux (see this earlier Ars Technica story about the pre-release of the new WireGuardNT implementation’s performance, which is now generally available). This implementation is surfaced via a nice graphical Windows app, which makes setting up and turning on a WireGuard connection as easy as using any other Windows app.
So unlike with SSH, Windows users don’t have to resort to running PuTTY or PowerShell (or Linux in a VM, or the Windows Subsystem for Linux) — they can use a native WireGuard implementation optimized specifically for Windows.
WireGuard has a native iOS client and a native Android client, which makes it easy to access services through a jumphost on a mobile phone (or on an iPad or Android tablet). While there are also terminal apps available for iOS and Android that you could use to SSH through an SSH jumphost, the WireGuard client on these platforms allows you to simply establish a generic encrypted tunnel to a WireGuard jumphost, which can then be used by any other app on the device to connect through the jumphost to remote SSH, RDP, VNC, HTTP, or other network services.
You can also copy your WireGuard configuration to a mobile device just by scanning a QR code, which simplifies set-up and key management.
The end result is that a WireGuard jumphost is easier to use, particularly for non-technical users. They don’t need to run any special terminal commands to use it, or fiddle with cryptic entries in an SSH known-hosts or client config file.
For most WireGuard jumphost users, the user experience is simple. It requires a one-time install of the WireGuard software, after which the user opens up the WireGuard app and loads the WireGuard config file sent to them by the jumphost administrator.
Then whenever the user wants to access a service through the jumphost, whether it is to SSH into a back-end server, connect to a database, or open up a web app, they just start up the WireGuard tunnel through their OS’s network UI or WireGuard app, and use their normal desktop application with its normal configuration for a “direct” connection (from the user’s perspective) to the service.
Here are a few more articles with screenshots that show the WireGuard client UI in action: