| Tested on | RHEL 10.2 OpenSSH client openssh-clients-9.9p1-25.el10_2.x86_64 (commands apply to OpenSSH 7.3+ clients) |
|---|---|
| Applies to | Any Linux, macOS, or Windows host with a modern OpenSSH client |
| Privilege | Normal user on the workstation; SSH access to the jump host and target server |
| Scope | SSH and SCP through one or more SSH jump/bastion hosts using ProxyJump and ProxyCommand. Also explains how an HTTP/SOCKS proxy differs from an SSH jump host. |
This guide shows how to reach one Linux server through another using ProxyJump and ssh -J. You will also see how to copy files with scp -J, save shortcuts in ~/.ssh/config, and fix the errors beginners hit most often.
What is a jump host or bastion host?
A jump host is an intermediate computer that you use as a stepping stone to reach another server that your laptop cannot access directly.
You may also see the term bastion host. In this article, jump host and bastion host mean the same thing: a server that sits between your computer and a private SSH server.
For example, your laptop may be allowed to reach jump.example.com, while the server 10.10.10.20 can only be reached from that jump host.
Why companies use a jump host
Imagine a company has 50 internal Linux servers. Instead of exposing SSH port 22 on all 50 servers to the internet, administrators expose only one carefully controlled SSH server. You connect to that server first, and it provides the network path to the internal machines. That intermediate system is the jump host or bastion host.
This is different from SSH port forwarding, which tunnels application ports through an SSH session you already opened. A jump host is another SSH server that sits on the network path to the target.
What is the target server?
The target server (or just target) is the final server you actually want to log in to. It may have a private IP address or firewall rules that prevent your laptop from reaching it directly. A private server in this context means a machine that is not directly reachable from your workstation or the public internet without going through the jump host.
Your laptop
|
| SSH
v
Jump / bastion host
|
| SSH connection carried to private network
v
Target server (private)For ProxyJump to work:
- Your laptop must reach the jump host
- The jump host must reach the target server's SSH port
- The target does not need to be directly reachable from your laptop
If you use a hostname such as target.internal instead of an IP address, the jump host must usually know how to resolve that name — your laptop only needs to reach the bastion.
Quick answer: SSH through a jump host
ProxyJump tells your local SSH client: connect to the jump host first, and use it as the network path to the final server. The short command-line option for ProxyJump is -J.
For OpenSSH 7.3 and later:
ssh -J jumpuser@jump.example.com admin@10.10.10.20In that command:
jumpuser— your account on the jump hostjump.example.com— the jump hostadmin— your account on the target server10.10.10.20— the target server you want to reach
Equivalent long form:
ssh -o ProxyJump=jumpuser@jump.example.com admin@10.10.10.20Use -J for one-off connections and entries in ~/.ssh/config for hosts you open regularly. OpenSSH defines -J as the command-line shortcut for ProxyJump, including comma-separated routes through more than one jump host.
How the connection works
The jump host does not log you into the final server on your behalf. It carries the network connection from your SSH client to the target server. You do not open a shell on the bastion and run a second ssh command there — your original SSH client keeps control of the target session.
There are two SSH authentications:
- Authentication #1: your client proves who you are to the jump host
- Authentication #2: your client proves who you are to the target server
ProxyJump does not log into the bastion and run ssh target there. A private key stored only on the jump host is not used automatically — keys on your laptop authenticate to the final server.
Run a one-hop login:
ssh -J jumpuser@jump.example.com admin@10.10.10.20I verified the same path on RHEL 10.2 with a local bastion and a target that only the bastion could reach:
ssh -J root@127.0.0.1 -p 2222 targetuser@172.18.0.2 'hostname; echo PROXYJUMP_OK'target.internal
PROXYJUMP_OKDifferent users and ports on the jump host and target
The jump host and target can each use their own settings:
- Username
- SSH port
- SSH key
- Authentication policy
When sshd on the jump host listens on a non-default port, append :port to the jump target:
ssh -J jumpuser@jump.example.com:2222 admin@10.10.10.20Use -p on the final destination when the target server does not listen on port 22:
ssh -J jumpuser@jump.example.com -p 2200 admin@10.10.10.20Configure ProxyJump in ~/.ssh/config
For regular use, save SSH settings in ~/.ssh/config. A Host block is a named group of settings for a hostname or alias — for example Host bastion or Host private-server.
Give the jump host and the target server their own Host blocks:
Host bastion
HostName jump.example.com
User jumpuser
Host private-server
HostName 10.10.10.20
User admin
ProxyJump bastionAfter that, a short alias is enough:
ssh private-serverSeparate SSH keys for jump host and target
An IdentityFile line points SSH at the private key file to use for that Host block.
Host bastion
HostName jump.example.com
User jumpuser
IdentityFile ~/.ssh/jump_ed25519
IdentitiesOnly yes
Host private-server
HostName 10.10.10.20
User admin
IdentityFile ~/.ssh/private_ed25519
IdentitiesOnly yes
ProxyJump bastionCommand-line and destination Host options are not automatically applied to a jump host. OpenSSH applies most command-line flags to the destination connection, not to the jump host — configure bastions in their own Host blocks.
A common trap:
ssh -i ~/.ssh/target_key -J bastion targetThat does not mean “use target_key everywhere.” -i belongs to the destination connection. If the bastion needs another key, give the bastion its own Host block and IdentityFile.
When several Host, wildcard, or Include blocks overlap, print the effective settings before you connect:
ssh -G private-server | grep -Ei 'hostname|user|port|identityfile|proxyjump|proxycommand'user admin
hostname 10.10.10.20
port 22
identityfile ~/.ssh/private_ed25519
proxyjump bastionSSH through multiple jump hosts
Each intermediate SSH server in the route is called a hop.
One jump host:
Laptop → Jump 1 → TargetTwo jump hosts (often called multi-hop SSH):
Laptop → Jump 1 → Jump 2 → TargetOpenSSH accepts multiple jump hosts separated by commas:
ssh -J user1@jump1.example.com,user2@jump2.internal admin@10.20.30.40Equivalent configuration:
Host jump1
HostName jump1.example.com
User user1
Host jump2
HostName jump2.internal
User user2
Host private-server
HostName 10.20.30.40
User admin
ProxyJump jump1,jump2Then:
ssh private-serverPrefer the comma-separated ProxyJump form over nested ProxyCommand chains for modern multi-hop SSH bastions.
Copy files through a jump host with SCP
Modern scp has its own -J option and follows the same jump path as ssh.
Upload a file through a jump host
Push a local file to the target server in one step:
scp -J jumpuser@jump.example.com file.txt admin@10.10.10.20:/tmp/Lab equivalent:
scp -J root@127.0.0.1 -P 2222 /tmp/scp-test.txt targetuser@172.18.0.2:/tmp/scp-via-j.txtThe target returned:
scp via ProxyJumpDownload a file through a jump host
Pull a remote file back to the current directory:
scp -J jumpuser@jump.example.com admin@10.10.10.20:/var/log/app.log .Copy a directory recursively
Add -r when the source path is a directory tree:
scp -r -J jumpuser@jump.example.com ./project admin@10.10.10.20:/tmp/scp -J passes the ProxyJump setting through SSH. You can also use the generic option form when you prefer it:
scp -o ProxyJump=jumpuser@jump.example.com file.txt admin@10.10.10.20:/tmp/Reuse ~/.ssh/config for SSH and SCP
With the private-server stanza from earlier, SSH and SCP share the same alias:
ssh private-serverCopy through that alias the same way:
scp file.txt private-server:/tmp/That is cleaner than repeating -J on every transfer. See the SCP command cheat sheet for recursion, limits, and other copy flags.
SCP through multiple jump hosts
Comma-separated jump hosts work for SCP the same way they do for SSH:
scp -J user1@jump1.example.com,user2@jump2.internal file.txt admin@10.20.30.40:/tmp/Note about modern SCP
Since OpenSSH 9.0, scp uses the SFTP protocol for file transfers by default. The command-line interface and -J / ProxyJump behavior stay the same. Use scp -O only when you specifically need the legacy SCP protocol.
SFTP and rsync through the same jump host
When ProxyJump is already defined for private-server in ~/.ssh/config, other OpenSSH-based tools can reuse the same route without repeating -J:
sftp private-serverrsync over SSH follows the same config alias when you use the remote shell transport:
rsync -av ./project/ private-server:/tmp/project/Tools that use OpenSSH as their transport can reuse the same ProxyJump route from ~/.ssh/config. For example, sftp and rsync over SSH can use the private-server alias directly.
Do you need SSH agent forwarding?
No. Ordinary ProxyJump access does not require ForwardAgent yes. Your local SSH client can authenticate to the final target with a private key that never leaves the workstation.
Avoid these common mistakes:
- Do not copy the target private key to the jump host
- Do not enable agent forwarding just because a bastion sits in the path
If you need agent forwarding for another workflow, treat it as a separate hardening decision.
ProxyJump vs ProxyCommand
| Feature | ProxyJump | ProxyCommand |
|---|---|---|
| Preferred for SSH bastions | Yes | Usually only for special/custom transports |
| Syntax | Simple | More flexible |
| Multiple SSH hops | Built in | Possible but cumbersome |
Requires nc on bastion |
No | Not when using ssh -W; older nc examples did |
| Non-SSH HTTP/SOCKS proxy | No | Yes |
Do not set both ProxyJump and ProxyCommand for the same destination. OpenSSH uses whichever value it obtains first for that connection.
ProxyCommand is an older, more flexible way to tell SSH how to create the connection path. For a normal SSH jump or bastion host, prefer ProxyJump. Keep ProxyCommand when you need a non-SSH proxy or another special connection method.
ProxyCommand: flexible alternative with ssh -W
Before ProxyJump, bastion access often looked like this:
ssh -o 'ProxyCommand=ssh -W %h:%p jumpuser@jump.example.com' admin@10.10.10.20Configuration equivalent:
Host private-server
HostName 10.10.10.20
User admin
ProxyCommand ssh -W %h:%p bastion%hexpands to the target hostname.%pexpands to the target SSH port.ssh -W host:portcarries the target connection through the bastion SSH session.ncon the bastion is not required for this pattern.
SSH jump host vs HTTP or SOCKS proxy
This page is titled ssh-proxy, but not every “proxy” in SSH means a jump host. Readers often mix up two different ideas:
| Type | What it is | Normal OpenSSH method |
|---|---|---|
| SSH jump / bastion host | An SSH server that can reach the target server | ProxyJump / ssh -J |
| HTTP proxy | A web/network proxy (HTTP CONNECT), not an SSH server |
ProxyCommand with nc, ncat, or connect-proxy |
| SOCKS proxy | A generic network proxy, not an SSH jump host | ProxyCommand with a compatible proxy client |
If the intermediate machine accepts SSH connections, use ProxyJump. If it is an HTTP or SOCKS proxy rather than an SSH server, use ProxyCommand with a compatible client.
SSH through an HTTP CONNECT proxy
Generic nc example from the OpenBSD documentation:
ssh -o 'ProxyCommand=nc -X connect -x proxy.example.com:8080 %h %p' user@server.example.comConfiguration:
Host server-via-proxy
HostName server.example.com
User user
ProxyCommand nc -X connect -x proxy.example.com:8080 %h %pnc implementations differ between distributions. On RHEL with Nmap ncat, the proxy flags look like this:
ssh -o 'ProxyCommand=ncat --proxy-type http --proxy proxy.example.com:8080 %h %p' user@server.example.comMatch the proxy client to the package your distribution ships.
Troubleshooting ProxyJump and jump hosts
When a jump login fails before the target is tried, test the SSH connection to the bastion alone (ssh jumpuser@jump.example.com) before you add -J.
| Symptom | Likely cause | Fix |
|---|---|---|
| Jump login fails before target is tried | Bad bastion credentials, key, or firewall to bastion | ssh jumpuser@jump.example.com alone first |
channel 0: open failed: connect failed |
Bastion cannot reach target SSH port | From the bastion, run nc -vz target 22 (or your SSH port) |
Temporary failure in name resolution / stdio forwarding failed after bastion login |
Jump host cannot resolve the target hostname | From the bastion, run getent hosts target.internal (or your target name); use a hostname or IP the bastion can resolve |
channel 0: open failed: administratively prohibited |
Bastion sshd blocks SSH forwarding |
Enable AllowTcpForwarding yes on the bastion, or use a bastion that permits -W forwarding |
| Failure only after bastion auth | Wrong target host, port, DNS, or firewall | Read ssh -vvv to see whether the break is client→bastion or bastion→target |
| Host-key verification warning during ProxyJump | Bastion or target host key is unknown or changed | Use ssh -vvv to see which host failed; verify that host's fingerprint — do not disable StrictHostKeyChecking as a default fix |
Permission denied (publickey) |
Jump and target authenticate separately | Use -vvv to see which hop rejected the key; set per-host IdentityFile and IdentitiesOnly yes |
| Wrong key offered to bastion | Destination -i does not apply to the jump host |
Give the bastion its own Host block with its key |
| Unexpected route through old proxy settings | Overlapping Host / Include stanzas |
ssh -G alias | grep -Ei 'hostname|proxyjump|proxycommand|identityfile' |
scp parses the wrong port |
-p on scp means preserve times, not SSH port |
Use -P for the target SSH port; put the bastion port in -J user@host:port |
Internally, ProxyJump uses SSH TCP forwarding through the bastion. You do not need a shell command on the bastion, but sshd must allow the forwarding channel that carries the target connection — which is why AllowTcpForwarding yes matters when you see administratively prohibited.
When diagnosis is unclear, run one failing connection with ssh -vvv -J … and note where it breaks:
- Client → bastion authentication or forwarding
- Bastion → target TCP connection
- Target SSH authentication after the tunnel is up
From the lab bastion, the private target port was reachable before ProxyJump was tested:
nc -vz 172.18.0.2 2222Connection to 172.18.0.2 2222 port [tcp/*] succeeded!Summary
A jump host or bastion host is an intermediate SSH server that provides the network path to a target server your laptop cannot reach directly. ProxyJump (and the -J shortcut) tells your SSH client to use that path in one command, with separate authentication to the jump host and the target.
Put recurring routes in ~/.ssh/config with separate Host blocks for the bastion and the destination, especially when usernames, ports, or keys differ. Use comma-separated ProxyJump values when you need more than one hop, and scp -J (or the same config alias) to copy files along the same path.
Reserve ProxyCommand for HTTP/SOCKS proxies and other non-SSH connection methods. If a jump connection fails, test the bastion alone, confirm the bastion can reach the target SSH port, then read ssh -vvv to see which hop broke.
References
- ssh(1) —
-JandProxyJump— command-line jump host shortcut - ssh_config(5) —
ProxyJump— config-file jump hosts and comma-separated hops - ssh_config(5) —
ProxyCommand— custom proxy transports - scp(1) —
-J— SCP through a jump host
Frequently Asked Questions
What is the difference between a jump host and a bastion host?
For SSH access, the terms usually mean the same thing: an intermediary SSH server that can reach systems your workstation cannot reach directly.
What is the target server in SSH ProxyJump?
The target server is the final machine you want to log in to. Your SSH client authenticates to the jump host first, then to the target server through that path.
Should I use ProxyJump or ProxyCommand?
Use ProxyJump for normal SSH jump or bastion hosts. Use ProxyCommand when you need a non-SSH proxy such as HTTP or SOCKS, or another special connection method.
Can SCP use ProxyJump?
Yes. Modern scp supports -J, for example scp -J jumpuser@jump.example.com file.txt user@target:/path/. The same ProxyJump stanza in ~/.ssh/config works for both ssh and scp.
Can I use two or more SSH jump hosts?
Yes. Each intermediate server is a hop. Pass comma-separated hops: ssh -J jump1,jump2 target or ProxyJump jump1,jump2 in config.
Does ProxyJump require netcat on the bastion?
No. ProxyJump does not require nc on the bastion. Older ProxyCommand ssh … nc %h %p examples did.
Do I need to copy my private key to the bastion?
No. Keep private keys on the client. Your local SSH client authenticates to the final target through the ProxyJump connection.
Does ProxyJump require agent forwarding?
No. ForwardAgent is not required for ordinary ProxyJump access.
Can SFTP use the same jump host?
Yes. Modern OpenSSH clients support -J for SFTP as well. Tools that honor ~/.ssh/config can reuse the same ProxyJump stanza.

