Plenty of production servers are not reachable from the internet at all. They sit on a private network, and the only door is a hardened machine that does nothing else: the bastion, or jump host. You connect to the bastion, and from there to the server you actually want.
On the command line this is a solved problem — one line of ssh config and you forget about it. In a graphical client it usually is not, which is why people behind a bastion end up doing everything over the terminal even when they would rather browse files or tunnel ports.
What ProxyJump actually does
ProxyJump tells SSH: before talking to the target, open a connection to this other host, and tunnel through it. SSH opens a session to the bastion, asks it to forward a TCP connection to the target’s port 22, and then runs a completely normal SSH handshake with the target inside that tunnel.
The important consequence is that the bastion never sees your traffic in the clear. It forwards encrypted bytes. Your authentication with the target, and the target’s host key, are negotiated end to end — the bastion is a pipe, not a man in the middle.
The ssh config side
In ~/.ssh/config you describe the bastion as a normal host, then point the target at it:
Host bastion
HostName bastion.example.com
User jump
IdentityFile ~/.ssh/id_ed25519
Host app-prod
HostName 10.0.4.21
User deploy
IdentityFile ~/.ssh/id_ed25519
ProxyJump bastion
You may also meet the older form, still very common: ProxyCommand ssh -W %h:%p bastion. It does the same job; %h and %p are the target host and port.
Test it from the terminal before involving any other tool:
ssh app-prod
If that works, every SSH-based tool on your Mac can be made to work. If it does not, no GUI client will rescue you — fix the config first.
Chains, and why the order matters
ProxyJump accepts more than one hop. ProxyJump edge,inner means go through edge first, then inner, then the target. Each hop is opened inside the previous one, so the chain is built outwards from your Mac.
In Upshell, closing the connection performs a teardown in reverse order (inner target first, then middle hops, then the gateway) to ensure that no orphaned channels are left active in the background.
The part people get wrong: host keys
Every hop has its own host key, and every hop must be verified against its own known-hosts entry. It is tempting for a client to verify only the final target — after all, that is where your files go — but that is a false sense of safety: a compromised bastion could then present itself as the target and read everything.
If a tool asks you to trust a fingerprint when connecting through a bastion, check which host it is talking about. It should name the hop it is currently opening, not always the destination. This is the same trust-on-first-use model (TOFU) built natively into Upshell.
Doing it from a Mac SSH client
Upshell (Pro) reads ProxyJump straight from your ssh config: import ~/.ssh/config and the bastion is created as its own saved host and linked to the target automatically. You can also set it by hand — open the host editor, choose the jump host from the picker, and connect.
Each hop is verified against its own known-hosts entry, and connection errors say which hop failed: “jump host unreachable” and “target unreachable through the bastion” are different problems with different fixes.
Step by step
- Describe the bastion in ~/.ssh/config — add a Host block for the bastion with its HostName, User and IdentityFile, exactly as if you were going to connect to it directly.
- Point the target at it with ProxyJump — in the target’s Host block, add
ProxyJump <bastion-alias>. Test it from the terminal withssh <target-alias>first. - Import the config into your client — in Upshell, use Import ▸ From ~/.ssh/config. Both hosts are imported and the jump relationship is preserved.
- Authorize the private key once — macOS sandboxing means an app cannot read
~/.sshon its own. On the first connection Upshell asks you to select the key the config points to; it is then stored in your Keychain. - Connect — connect to the target as usual. Upshell opens the bastion first, then tunnels to the target, verifying each host key along the way.
Questions people ask
Is a bastion the same as a VPN?
No. A VPN puts your whole machine on the remote network; a bastion forwards a single connection to a single host. A bastion is narrower, which is exactly why security teams prefer it for server access.
Does the bastion see my files or commands?
No. It forwards encrypted traffic. Your session with the target is negotiated end to end, inside the tunnel.
Can I chain more than one bastion?
Yes. ProxyJump accepts a comma-separated list and each hop is opened inside the previous one. Upshell follows chains of up to 8 hops.