How to Use an SSH Jump Host with ProxyJump

When a server is on a private network, you may need to connect through another machine before you can reach it. This intermediate server is called a jump host or bastion host. You could log in to the bastion and run ssh again from there, but using keys for the second connection would require a private key on the bastion or agent forwarding.
OpenSSH solves this with ProxyJump. You tell your local client which jump host to go through, and it connects to the internal server in one command. Your private keys stay on your own machine, and the session to the internal server is encrypted end to end.
This guide explains how to connect through a jump host with ssh -J, save the route in ~/.ssh/config, copy files and forward ports through the bastion, and restrict a jump account so it can do nothing except relay connections.
How ProxyJump Works
When you connect with ProxyJump, your client first opens an SSH connection to the jump host. It then asks the jump host to open a TCP connection to the internal server, usually on port 22, and starts a second SSH connection through it.
Both SSH connections start on your computer. You authenticate to each server separately, and the bastion relays the encrypted connection to the internal server. You do not need to copy private keys to the bastion or enable ForwardAgent, which would let someone with root access on the bastion use your forwarded agent to authenticate elsewhere while it is available.
The examples in this guide use a jump host at bastion.example.com and an internal server at 10.0.0.50. Replace these addresses and the admin username with your own. Your computer must be able to reach the bastion, and the bastion must be able to reach the internal server’s SSH port.
We will use public key authentication, so your public key must be authorized for the account you use on each machine. You can use a different key for each server. See how to generate SSH keys if you have not set them up. ProxyJump also works with password authentication when the servers allow it.
Connect with ssh -J
The -J option takes the jump host as its argument, followed by the destination:
ssh -J [user@]jumphost[:port] [user@]destinationTo log in to the internal server as admin through the bastion, run:
ssh -J admin@bastion.example.com admin@10.0.0.50The first time you connect, SSH asks you to confirm the host key of each machine. Verify both fingerprints with the server administrator or through a trusted console before accepting them. After authentication, you get a shell on 10.0.0.50.
The bastion opens the TCP connection to 10.0.0.50, so your computer does not need a route to that private address. If you use an internal hostname instead, it is normally resolved on the bastion.
If the jump host listens on a non-standard port, add it after a colon:
ssh -J admin@bastion.example.com:2222 admin@10.0.0.50For the destination port, use the normal -p option, which applies only to the final host.
Chain Multiple Jump Hosts
Some networks put more than one bastion in front of the servers, for example an internet-facing jump host and a second one inside a management network. Separate the jump hosts with commas, in the order the connection should pass through them:
ssh -J admin@bastion.example.com,admin@10.10.0.5 admin@10.20.0.80SSH connects to bastion.example.com, from there to 10.10.0.5, and from there to the final server. Each hop is still authenticated from your machine.
Options Do Not Apply to the Jump Host
Options such as -i for a key file and most -o settings apply to the destination, not the jump host. If the bastion needs a particular key, put its settings in ~/.ssh/config. SSH reads the configuration file for each hop. Otherwise, the jump connection may fail with Permission denied (publickey) even though the same -i option works when you connect to the bastion directly.
Configure ProxyJump in ~/.ssh/config
The SSH config file lets you save the route under a short name. Open the file on your local computer:
nano ~/.ssh/configDefine the bastion, then reference it from the internal host with ProxyJump. This example uses a separate key for each server:
Host bastion
HostName bastion.example.com
User admin
IdentityFile ~/.ssh/id_ed25519_bastion
IdentitiesOnly yes
Host db1
HostName 10.0.0.50
User admin
IdentityFile ~/.ssh/id_ed25519_internal
IdentitiesOnly yes
ProxyJump bastionSet each IdentityFile to an existing private key on your computer. If you use the same key for both accounts, use the same path in both blocks. IdentitiesOnly yes keeps SSH from trying unrelated keys offered by your agent.
ProxyJump bastion refers to the Host bastion block, so the jump connection uses its hostname, user, and key. The db1 block supplies the settings for the internal server. You can now connect with:
ssh db1To check which jump host SSH will use for a name, print the effective configuration with ssh -G:
ssh -G db1 | grep -i proxyjumpproxyjump bastionThe output confirms that db1 uses the bastion alias. ssh -G only prints the client settings; it does not connect to either server.
Use a Wildcard for Private Addresses
When every server on a private network sits behind the same bastion, a wildcard pattern saves you from writing a block for each one:
Host 10.0.0.*
User admin
IdentityFile ~/.ssh/id_ed25519_internal
IdentitiesOnly yes
ProxyJump bastionNow ssh 10.0.0.50 and ssh 10.0.0.51 go through the bastion automatically. The pattern matches the address you type in the command. It does not match the db1 alias just because its HostName is 10.0.0.50, so keep ProxyJump bastion in that alias’s block.
Place more specific Host blocks above wildcard ones, because SSH uses the first value it finds for most options.
If one host matches the wildcard but should be reached directly, override it with ProxyJump none in a block above the wildcard:
Host 10.0.0.1
ProxyJump noneCopy Files Through the Jump Host
scp and sftp accept the same -J option as ssh. To upload a local file through the bastion, run:
scp -J admin@bastion.example.com backup.sql admin@10.0.0.50:/tmp/Replace backup.sql with your local filename. When the route is in ~/.ssh/config, you do not need -J. scp
, sftp, and rsync over SSH use the same host settings:
scp backup.sql db1:/tmp/
sftp db1
rsync -avz ./site/ db1:/var/www/site/The destination directory must be writable by your remote user. rsync must be installed on your computer and the internal server; it does not need to be installed on the bastion.
For rsync without a config entry, pass the jump host through the remote shell option:
rsync -avz -e "ssh -J admin@bastion.example.com" ./site/ admin@10.0.0.50:/var/www/site/For more transfer options, see the guide on using rsync over SSH .
Forward a Port Through the Jump Host
ProxyJump combines with SSH port forwarding
. Suppose a PostgreSQL server on db1 listens on 127.0.0.1:5432. The following command makes it available on port 15432 of your computer:
ssh -N -o ExitOnForwardFailure=yes -L 127.0.0.1:15432:127.0.0.1:5432 db1-L forwards 127.0.0.1:15432 on your computer to 127.0.0.1:5432 as seen from db1. Binding to 127.0.0.1 keeps the local port accessible only from your computer. Using 15432 also avoids a conflict if you already run PostgreSQL locally on port 5432.
-N tells SSH not to start a remote shell. ExitOnForwardFailure=yes makes SSH exit if it cannot create the listener, for example because port 15432 is already in use. It does not check whether PostgreSQL is reachable.
Because db1 has ProxyJump bastion in the config file, the tunnel passes through the bastion without any extra options. The SSH server on db1 must also allow local TCP forwarding. Connect your database client to 127.0.0.1:15432 and press Ctrl+C to close the tunnel when you are done.
Use ProxyCommand on Older Clients
ProxyJump and -J were added in OpenSSH 7.3. If you have to work from an older client, use ProxyCommand with ssh -W, which does the same thing in a more verbose way:
Host db1
HostName 10.0.0.50
User admin
ProxyCommand ssh -W %h:%p bastion%h and %p expand to the hostname and port of db1, and -W tells the bastion connection to forward standard input and output to that address. This example uses the bastion alias defined earlier. Replace the existing ProxyJump line when trying ProxyCommand; the two options compete, and whichever is set first takes effect. On current clients, use ProxyJump.
Lock Down the Jump Host
A dedicated jump account does not need to run commands on the bastion. The following example assumes you have already created a jump user and installed your public key in that user’s ~/.ssh/authorized_keys. Use a separate account for administering the bastion.
Before changing the configuration, confirm that you can authenticate as jump with your key. Then, from your administrative account on the bastion, open the SSH server configuration:
sudo nano /etc/ssh/sshd_configAdd the following block at the end of the file, after the global settings and any existing Match blocks. List the internal servers the account may reach in PermitOpen:
Match User jump
AuthenticationMethods publickey
AllowTcpForwarding local
PermitOpen 10.0.0.50:22 10.0.0.51:22
AllowStreamLocalForwarding no
X11Forwarding no
AllowAgentForwarding no
PermitTunnel no
MaxSessions 0Here is what each setting does:
AuthenticationMethods publickey- Requires public key authentication for this account.AllowTcpForwarding local- Allows the forwarding that ProxyJump needs and blocks remote forwarding (-R) from the bastion.PermitOpen- Limits TCP forwarding to the listed hosts and ports. SSH compares the destination literally, so use the IP address or hostname sent by the client after anyHostNamesubstitution.AllowStreamLocalForwarding no- Disables Unix socket forwarding, whichPermitOpendoes not restrict.X11Forwarding noandAllowAgentForwarding no- Disable forwarding that a relay account never needs.PermitTunnel no- Disables tunnel-device forwarding.MaxSessions 0- Prevents shell, command, and subsystem sessions, including SFTP on the bastion, while still allowing TCP forwarding. ProxyJump does not request a session on the bastion, so it keeps working.
The Match User jump block applies only to the jump account. Earlier matching blocks may take precedence for the same settings, so check the effective configuration as well as the file syntax.
Check the syntax before applying the change, so a typo does not break SSH access to the bastion:
sudo sshd -tIf the command prints nothing, reload the service:
sudo systemctl reload sshOn Fedora and RHEL, use sudo systemctl reload sshd instead. From your computer, test a connection through the restricted account:
ssh -J jump@bastion db1You should get a shell on the internal server. A direct shell or SFTP connection to the bastion as jump should fail. If you use the bastion alias, change its User setting to jump so that ssh db1 uses the restricted account too.
Troubleshooting
“channel 0: open failed: administratively prohibited: open failed”
The jump host refused to open the connection to the next hop. The full error looks like this:
channel 0: open failed: administratively prohibited: open failed
stdio forwarding failed
Connection closed by UNKNOWN port 65535Check AllowTcpForwarding, DisableForwarding, and PermitOpen on the bastion. The key entry in authorized_keys can also block forwarding with options such as restrict or no-port-forwarding.
To inspect the effective server settings, run this command on the bastion. Replace 192.0.2.10 and client.example.com with the client address and hostname as seen by the bastion, and use the account you connect with:
sudo sshd -T -C user=jump,host=client.example.com,addr=192.0.2.10 | grep -E '^(allowtcpforwarding|disableforwarding|permitopen|maxsessions) 'For the restricted account above, expect allowtcpforwarding local, disableforwarding no, maxsessions 0, and the two allowed destinations in permitopen. A key-level restriction still applies even when these server settings allow forwarding.
“Host key verification failed” on the jump host
SSH could not verify the bastion’s host key. Connect to the bastion directly and compare the displayed fingerprint with one obtained from the server administrator or a trusted console. If the key has changed, verify the reason before updating known_hosts. Put any per-host settings in the bastion’s Host block; command-line settings for the destination generally do not apply to the jump connection.
“Permission denied (publickey)” on the internal server
The jump worked, but the final server rejected your key. Check the destination username and IdentityFile, and confirm that the corresponding public key is authorized on the internal server. The SSH public key troubleshooting guide
covers key permissions and other common causes.
The connection hangs before the internal server responds
Run ssh -v db1 to see which hop stalls. If the connection to the bastion succeeds, check the internal address, SSH port, and firewall rules between the two servers. From a separate administrative account on the bastion, you can test TCP connectivity with nc -zv 10.0.0.50 22. The restricted jump account cannot run that command.
Conclusion
Save the route and per-host keys in ~/.ssh/config so you can use the same alias for SSH sessions, file transfers, and port forwarding. For more client options, see the ssh command guide
.
Tags
Linuxize Weekly Newsletter
A quick weekly roundup of new tutorials, news, and tips.
About the authors

Dejan Panovski
Dejan Panovski is the founder of Linuxize, an RHCSA-certified Linux system administrator and DevOps engineer based in Skopje, Macedonia. Author of 1000+ Linux tutorials with 20+ years of experience turning complex Linux tasks into clear, reliable guides.
View author page