SSH Through a Jump Host with ProxyJump, ProxyCommand & SCP

Deepak Prasad
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.
Short on time?Beta Get the gist of this 13-minute read in seconds, then ask follow-up questions

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:

bash
ssh -J jumpuser@jump.example.com admin@10.10.10.20

In that command:

  • jumpuser — your account on the jump host
  • jump.example.com — the jump host
  • admin — your account on the target server
  • 10.10.10.20 — the target server you want to reach

Equivalent long form:

bash
ssh -o ProxyJump=jumpuser@jump.example.com admin@10.10.10.20

Use -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:

bash
ssh -J jumpuser@jump.example.com admin@10.10.10.20

I verified the same path on RHEL 10.2 with a local bastion and a target that only the bastion could reach:

bash
ssh -J root@127.0.0.1 -p 2222 targetuser@172.18.0.2 'hostname; echo PROXYJUMP_OK'
output
target.internal
PROXYJUMP_OK

Different 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:

bash
ssh -J jumpuser@jump.example.com:2222 admin@10.10.10.20

Use -p on the final destination when the target server does not listen on port 22:

bash
ssh -J jumpuser@jump.example.com -p 2200 admin@10.10.10.20

Configure 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:

sshconfig
Host bastion
    HostName jump.example.com
    User jumpuser

Host private-server
    HostName 10.10.10.20
    User admin
    ProxyJump bastion

After that, a short alias is enough:

bash
ssh private-server

Separate SSH keys for jump host and target

An IdentityFile line points SSH at the private key file to use for that Host block.

sshconfig
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 bastion

Command-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:

bash
ssh -i ~/.ssh/target_key -J bastion target

That 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:

bash
ssh -G private-server | grep -Ei 'hostname|user|port|identityfile|proxyjump|proxycommand'
output
user admin
hostname 10.10.10.20
port 22
identityfile ~/.ssh/private_ed25519
proxyjump bastion

SSH through multiple jump hosts

Each intermediate SSH server in the route is called a hop.

One jump host:

Laptop → Jump 1 → Target

Two jump hosts (often called multi-hop SSH):

Laptop → Jump 1 → Jump 2 → Target

OpenSSH accepts multiple jump hosts separated by commas:

bash
ssh -J user1@jump1.example.com,user2@jump2.internal admin@10.20.30.40

Equivalent configuration:

sshconfig
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,jump2

Then:

bash
ssh private-server

Prefer 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:

bash
scp -J jumpuser@jump.example.com file.txt admin@10.10.10.20:/tmp/

Lab equivalent:

bash
scp -J root@127.0.0.1 -P 2222 /tmp/scp-test.txt targetuser@172.18.0.2:/tmp/scp-via-j.txt

The target returned:

output
scp via ProxyJump

Download a file through a jump host

Pull a remote file back to the current directory:

bash
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:

bash
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:

bash
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:

bash
ssh private-server

Copy through that alias the same way:

bash
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:

bash
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:

bash
sftp private-server

rsync over SSH follows the same config alias when you use the remote shell transport:

bash
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:

bash
ssh -o 'ProxyCommand=ssh -W %h:%p jumpuser@jump.example.com' admin@10.10.10.20

Configuration equivalent:

sshconfig
Host private-server
    HostName 10.10.10.20
    User admin
    ProxyCommand ssh -W %h:%p bastion
  • %h expands to the target hostname.
  • %p expands to the target SSH port.
  • ssh -W host:port carries the target connection through the bastion SSH session.
  • nc on 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:

bash
ssh -o 'ProxyCommand=nc -X connect -x proxy.example.com:8080 %h %p' user@server.example.com

Configuration:

sshconfig
Host server-via-proxy
    HostName server.example.com
    User user
    ProxyCommand nc -X connect -x proxy.example.com:8080 %h %p

nc implementations differ between distributions. On RHEL with Nmap ncat, the proxy flags look like this:

bash
ssh -o 'ProxyCommand=ncat --proxy-type http --proxy proxy.example.com:8080 %h %p' user@server.example.com

Match 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:

bash
nc -vz 172.18.0.2 2222
output
Connection 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


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.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Omer Cakmak

Linux Administrator

Highly skilled at managing Debian, Ubuntu, CentOS, Oracle Linux, and Red Hat servers. Proficient in bash scripting, Ansible, and AWX central server management, he handles server operations on OpenStack, KVM, Proxmox, and VMware.

  • Debian
  • Ubuntu
  • Linux
  • Red Hat Enterprise Linux
  • Shell Script
  • System Administration