Linux Administration·8 min read·

ProxyJump vs ProxyCommand for SSH Bastions

Prefer ProxyJump for nested SSH hops, and ProxyCommand when you need netcat, TLS wrappers, or a custom transport through a Lucknow bastion VPS.

NB

Netbay Developer Relations

Netbay Engineering

On this page

ProxyJump and ProxyCommand both get you through a bastion. They are not interchangeable, and treating them as synonyms is how people end up with a netcat one-liner that breaks the moment they add a second hop. ProxyJump is an OpenSSH-native hop: the client opens a direct-tcpip channel through an already authenticated SSH session and runs a second SSH protocol on that channel. ProxyCommand is a stdin/stdout pipe to any program you name. Use the first until you cannot. Keep the second for transports SSH does not speak by itself.

This is the next layer after a working ~/.ssh/config. You already have Host aliases. The question is how those aliases reach a private node that has no public address.

Two hop models, one bastion ProxyJump (-J) SSH session to bastion direct-tcpip channel nested SSH to app node native, multi-hop aware default choice ProxyCommand spawn a local program stdin/stdout is the tunnel nc, ncat, corkscrew, custom escape hatch for odd paths when SSH cannot hop alone Same bastion VPS. Different client plumbing. Pick the one you can debug at 2 a.m.

ProxyJump is the hop you want

The -J flag and the ProxyJump keyword are the same feature. The client authenticates to the jump host using that host's own stanza (User, IdentityFile, Port), then asks the jump sshd to open a TCP channel to the next HostName:Port. A second SSH handshake runs on that channel, using the next stanza's key. Nested lists work: ProxyJump bastion,mid,inner. Evaluation is left to right. Each hop can have its own key and user.

bash
Host bastion
  HostName 203.0.113.10
  User deploy
  IdentityFile ~/.ssh/id_ed25519_bastion
  IdentitiesOnly yes

Host mid
  HostName 10.0.0.8
  User deploy
  IdentityFile ~/.ssh/id_ed25519_mid
  IdentitiesOnly yes
  ProxyJump bastion

Host app
  HostName 10.0.1.14
  User deploy
  IdentityFile ~/.ssh/id_ed25519_app
  IdentitiesOnly yes
  ProxyJump mid

Notice mid already jumps through bastion, so app does not list both. You can also write ProxyJump bastion,mid on app and skip the indirection. Pick one style and keep it. Mixing both in the same file makes hop order impossible to review.

On the server, the jump host must allow TCP forwarding. AllowTcpForwarding yes is the default on stock OpenSSH. Disable it only if the box is not a bastion. PermitOpen can restrict which destinations the jump will connect to, which is the right hardening once you know the private CIDR of the app nodes. A Lucknow VPS used as a bastion should permit only that CIDR, not the whole internet through your key.

ProxyCommand is a pipe, not a hop

ProxyCommand runs a local process and treats its stdin and stdout as the encrypted stream. The classic form is ssh -W %h:%p bastion, which is almost ProxyJump implemented in userland. %h and %p expand to the target HostName and Port. %n is the original alias. %r is the remote user. Those tokens are the entire contract. Quote them so a hostile hostname cannot inject a shell.

bash
Host app-via-nc
  HostName 10.0.1.14
  User deploy
  IdentityFile ~/.ssh/id_ed25519_app
  IdentitiesOnly yes
  ProxyCommand ssh -W %h:%p bastion

Host app-via-ncat
  HostName 10.0.1.14
  User deploy
  IdentitiesOnly yes
  IdentityFile ~/.ssh/id_ed25519_app
  ProxyCommand ncat --proxy 127.0.0.1:9050 --proxy-type socks5 %h %p

You need ProxyCommand when the path is not SSH. Examples: corkscrew through an HTTP CONNECT proxy, ncat through a SOCKS proxy, a vendor wrapper that speaks TLS to a broker, or a tiny socat unit that presents a serial-like socket. None of those are nested SSH hops. ProxyJump cannot call corkscrew. ProxyCommand can.

The cost is that OpenSSH no longer understands the hop graph. Multiplexing, jump chaining, and some ControlMaster behaviors get harder. Error messages become whatever the child process printed on stderr. If ncat is missing, you get a broken pipe, not a clean "could not resolve bastion".

When the extra hop is not SSH

A few transports still show up on real fleets. A corporate HTTP proxy that only allows CONNECT to port 443: run sshd on 443 on the bastion, then ProxyCommand corkscrew proxy.example 8080 %h %p. A SOCKS forward you already opened for a browser: ncat --proxy. A jump through a machine that only offers a forced command: you may have to ProxyCommand ssh bastion -W %h:%p anyway, because ProxyJump can fail when the remote account cannot open direct-tcpip.

Test with ssh -v app. The debug log states whether the client used a jump or spawned a command. Timeouts of 60 seconds with no banner usually mean the pipe never connected, not that the key is wrong. Authentication errors after a banner mean the hop worked and the second handshake failed.

Keep both mechanisms out of login shells. A ProxyCommand that calls a script in ~/bin is fine if that script is mode 700 and does not source a noisy profile. A ProxyCommand that calls a script which itself calls ssh without -o BatchMode=yes can deadlock when two password prompts fight for the tty.

Debugging hops without guessing

ssh -G app still prints the expanded ProxyJump or ProxyCommand. ssh -vvv app true then shows the channel setup. On the bastion, journalctl -u ssh -n 50 shows whether the direct-tcpip request was accepted or denied by PermitOpen. If you tightened PermitOpen to 10.0.1.14:22 and the app moved, the jump fails closed. That is the correct failure mode.

Do not enable GatewayPorts on the bastion to "make jumps work". GatewayPorts controls bind addresses for remote forwards, not ProxyJump. Confusing the two is a common copy-paste from tunnel guides.

A short takeaway: ProxyJump is nested SSH and should be the default for every private node behind a Lucknow bastion. ProxyCommand is the escape hatch for non-SSH transports. Name the hop in config, restrict PermitOpen to the private CIDR, and leave netcat for the cases SSH cannot express. Spin up a pair of Ubuntu 24.04 instances on Netbay in under 60 seconds and walk the hop with ssh -v — netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance