SSH ProxyCommand turns your SSH client into a smart tunnel that jumps through intermediate hosts to reach private targets. Instead of manually managing bastion hosts or VPNs, ProxyCommand executes a custom command and routes traffic through its standard input and output.
This approach keeps configuration declarative and portable, letting you reference jump hosts with one line in ~/.ssh/config. The following sections break down how ProxyCommand works with real examples, common patterns, and troubleshooting tips.
| Use Case | ProxyCommand Technique | Example Host Alias | Typical Latency Impact |
|---|---|---|---|
| Reach internal server via bastion | nc through SSH dynamic port forward | bastion-user@bastion-host | Low to moderate, single extra hop |
| Jump through multiple internal hosts | ProxyCommand chaining with intermediate netcat | internal-server via jump1 and jump2 | Moderate, cumulative per hop |
| Access private service via local forward | local forward port with ProxyCommand execlocalhost:8443 to private:443 | Minimal when local machine is fast | |
| Use proxy command with control master | Combine ControlPath with ProxyCommand for reuseShared multiplexed sessions | Reduces connection setup after first hop |
How ProxyCommand Works Under the Hood
When you define ProxyCommand in your SSH config, SSH runs the command before establishing the encrypted session. The command must arrange for an ongoing stream from the client to the target host:port by the time SSH starts the handshake.
A classic pattern uses nc (ncat or netcat) to open a TCP connection through the bastion. SSH waits for the command to become ready on its standard input/output, so timing and error handling in the command affect login and file transfer reliability.
Setting Up ProxyCommand With Netcat Style Examples
Start with simple bastion access by specifying a ProxyCommand that uses ssh to reach the jump host and nc to forward traffic. Use ProxyJump as a modern shortcut, but explicit ProxyCommand gives more control over timeouts, netcat variants, and fallback behavior.
Consider host key checking, strict host key checking, and user separation when you chain ProxyCommand across multiple intermediate hosts. Each hop can have its own identity file and config rules.
Basic Internal Server Via Bastion
Host internal-server
HostName 10.0.3.50
User deploy
ProxyCommand ssh -W %h:%p bastion-user@bastion-host
This uses the -W shorthand to forward without invoking a shell on the bastion, keeping the configuration compact and secure.
Multi Hop With Intermediate Netcat
Host internal-server
HostName 10.0.5.10
ProxyCommand ssh bastion1 nc %h %p
ProxyCommand ssh bastion1 ssh bastion2 nc %h %p
Each ProxyCommand is evaluated in order, enabling layered traversal through controlled internal zones.
Modern Alternatives and ControlMaster Synergy
OpenSSH includes ProxyJump, which is a shorthand for a single bastion hop and automatically manages ControlMaster sessions. You can still use ProxyCommand for more complex topologies or when you need fine-grained netcat version tuning.
ControlMaster reuses existing connections, reducing latency for ProxyCommand based setups. Combine ControlPersist with ProxyCommand to avoid repeated authentication and speed up frequent connections to the same internal targets.
Troubleshooting and Best Practices
When ProxyCommand fails, start by verifying the intermediate command manually in your terminal. Test the exact ProxyCommand line outside SSH to isolate netcat version issues, host resolution, and SSH key permissions.
Set ConnectTimeout and ServerAliveInterval to bound hang scenarios. Use different identity files per bastion or internal host with IdentityAgent or IdentitiesOnly to prevent key selection issues.
Refining Your SSH Access Patterns
- Prefer ProxyJump for straightforward bastion access with ControlMaster reuse
- Use explicit ProxyCommand when you need nc variants, chained hops, or custom logging
- Always set ConnectTimeout and ServerAliveInterval to bound hangs
- Separate IdentityFile per bastion and internal host to avoid key mismatches
- Test ProxyCommand lines manually before embedding them in SSH config
- Leverage ControlPersist to reduce authentication overhead across sessions
- Document host aliases and required bastion paths for team onboarding
FAQ
Reader questions
Why does my ProxyCommand with nc hang on certain hosts?
Some netcat versions buffer aggressively or require explicit shutdown behavior. Try ncat with --send-only or use the -W flag via ssh -W as a ProxyCommand alternative to avoid nc compatibility issues.
Can I use ProxyCommand through more than two intermediate hosts?
Yes, chain multiple ProxyCommand lines or embed a small script that loops through jump hosts. Keep an eye on ControlMaster settings so that multiplexing does not block due to mismatched paths.
How do I specify different keys for bastion versus internal hosts?
Use separate Host blocks with distinct IdentityFile directives. When ProxyCommand invokes ssh to the bastion, that inner ssh picks the correct key from the inner host matching rules.
What is the difference between ProxyCommand and ProxyJump for my workflow?
ProxyJump is simpler and handles ControlMaster automatically, while ProxyCommand gives you flexibility to use nc, socat, or custom scripts. Choose ProxyJump for single bastion setups and ProxyCommand for multi hop or nonstandard forwarding.