A lightweight ProxyCommand utility that implements OpenSSH's -J (jump host) behavior for SSH clients that lack native support for it, such as WinSCP, PuTTY, or any tool that accepts a ProxyCommand string.
It connects through one or more SSH jump hosts in sequence and bridges the final TCP stream to standard input and output, ensuring the parent SSH client sees a direct connection to the destination.
Many popular SSH graphical clients and utilities do not natively support complex multi-hop SSH routing or the -J flag introduced in newer OpenSSH versions. Passing a ProxyCommand string is the standard workaround, but configuring a sequence of chained commands manually is error-prone and brittle.
This project exists to act as a robust, single-binary ProxyCommand drop-in replacement. It parses standard OpenSSH -J syntax, automatically manages the SSH handshake across all intermediaries, dynamically establishes the required TCP tunnels, and natively handles Multi-Factor Authentication (such as TOTP) at any point in the jump chain without interfering with the parent application's stream.
Via go install:
go install github.com/swarupsengupta2007/go-ssh-jumphost@latestDownload a pre-built binary from the Releases page.
The utility supports several command-line arguments to control its behavior:
-
-J <hosts>- Status: Required
- Format: Comma-separated list of jump hosts (
user@host:port[,user@host:port,...]). - Description: Defines the exact sequence of intermediate jump hosts to traverse.
-
-timeout <duration>- Status: Optional
- Default:
30s - Description: Specifies the dial and SSH handshake timeout per individual SSH hop. Set to
0to disable the timeout completely.
-
-strict-host-key-checking- Status: Optional
- Default:
false - Description: Enforces strict host key verification. Verifies the host key of each connection against the user's
~/.ssh/known_hostsfile or the file specified via the-hosts-fileflag.
-
-no-ssh-config- Status: Optional
- Default:
false - Description: Prevents the utility from loading the OpenSSH configuration file (
~/.ssh/config).
-
-ssh-config <file>- Status: Optional
- Description: Explicitly loads a specific SSH configuration file instead of the user's default
~/.ssh/config. Cannot be used concurrently with-no-ssh-config.
-
-hosts-file <file>- Status: Optional
- Description: Explicitly loads a specific
known_hostsfile, bypassing the default~/.ssh/known_hostslocation and overriding anyUserKnownHostsFiledirective discovered in the SSH configuration file.
-
-I- Status: Optional
- Default:
false - Description: Forces a GUI input dialog for interactive prompts (like passwords or TOTP). This is specific to Windows and falls back to the standard console interface on other platforms.
-
-v- Status: Optional
- Default:
false - Description: Enables verbose logging. All debug and operational logs are strictly written to
stderrto avoid corrupting thestdoutstream used by the tunneling protocol.
Positional Argument:
After all flags, the utility expects a single positional argument representing the final destination host in the format [user@]destination[:port].
Unless the -no-ssh-config flag is passed, go-ssh-jumphost automatically loads the user's ~/.ssh/config file. It applies the discovered configurations for every host involved in the jump chain as well as the final destination.
The following SSH configuration directives are parsed and actively applied by the utility:
HostName: Resolves an alias to a fully qualified domain name or IP address.User: Specifies the login username.Port: Specifies the port number to connect to.IdentityFile: Specifies a file from which the user's identifying private key is read.UserKnownHostsFile: Specifies a custom file for host key verification.StrictHostKeyChecking: Dictates whether host keys should be strictly verified. Values ofyesoraskenable strict checking; all other values disable it.ProxyJump: Specifies a list of intermediate jump hosts implicitly chained before the defined host.
Directives that go-ssh-jumphost recognises but does not implement (e.g., LogLevel, ForwardAgent, Ciphers, Compression, RequestTTY) are strictly ignored. The application executes normally without acting on them.
When determining the final connection parameters, go-ssh-jumphost adheres to the following strict order of precedence:
- Command-Line Arguments: Options explicitly provided via the command-line flags or explicit jump host syntax (e.g., specifying
user@host:portdirectly) take absolute priority. - SSH Configuration File: If a parameter is not defined on the command-line, the utility falls back to the configurations defined in
~/.ssh/config.
Values explicitly set on the command line override similar options in the SSH configuration file. For instance, the --hosts-file flag supersedes the UserKnownHostsFile directive.
The utility natively handles automatic jump host chaining if it is implicitly derived from the SSH configuration file.
If a host listed in the -J argument or the final destination contains a ProxyJump directive in the SSH configuration, its respective jump hosts are automatically expanded and prepended to the host sequence.
This expansion evaluates recursively. It comprehensively handles wildcard patterns on resolved IP addresses identically to OpenSSH behavior with CanonicalizeHostname yes. A ProxyJump none configuration value explicitly suppresses any further expansion.
The project includes intelligent autodetection mechanisms out of the box to streamline the user experience:
- GUI Dialog Autodetection (Windows): Running inside graphical parent applications (like WinSCP or plink) often detaches the standard console. The utility inspects the parent executable name. If invoked by known headless clients, it automatically enforces the
-Iflag behavior, spawning a native Win32 GUI dialog for necessary input (e.g., password or TOTP) to prevent silent hangs without requiring configuration changes. - Terminal Input Management: The utility utilizes raw-mode terminal reading across operating systems to ensure secure password input. On Unix systems, it securely interacts with
/dev/tty. On Windows, it binds to theCONIN$andCONOUT$buffers to safely read input and display prompts, natively bypassing the piped streams used by the ProxyCommand framework.
The utility uses InsecureIgnoreHostKey by default. This is highly suitable for standard ProxyCommand usage, as the outer SSH application performs its own primary host key verification.
To enable verification natively at the intermediate jump-host layer, users must pass the -strict-host-key-checking flag. The project will verify against ~/.ssh/known_hosts, utilize paths defined in UserKnownHostsFile, or leverage the explicit path supplied via -hosts-file.
You can populate the system's known hosts explicitly using ssh-keyscan:
ssh-keyscan -H gw1.example.com >> ~/.ssh/known_hosts
ssh-keyscan -H gw2.internal >> ~/.ssh/known_hostsThe utility attempts authentication methods in the following strict order for all established connections in the chain:
- SSH Agent: Attempted directly via
SSH_AUTH_SOCKon Unix environments or the OpenSSH named pipe on Windows. - Key Files: Used as a fallback if the SSH agent is unavailable. The utility will iterate through standard identity files (
~/.ssh/id_rsa,~/.ssh/id_ecdsa,~/.ssh/id_ed25519) followed by any extra paths parsed fromIdentityFileconfiguration directives. - Keyboard-Interactive: Always appended and executed last after public key methods, providing full support for Password and Time-Based One-Time Passwords (TOTP) scenarios.
In the event of a timeout, failure, or unexpected jump host resolution, append the -v (verbose) flag to your execution command.
Executing with -v activates granular logging isolated entirely to the stderr stream. This logging outputs the full lifecycle of the process:
- Verification of parsed command-line variables.
- Discoveries of configurations, including cryptographic hashes of the evaluated SSH config file.
- Specific evaluations of resolved
ProxyJumpconcatenations. - Explicit
[WARN]entries detailing any ignored, unsupported SSH configuration keys mapped to specific hosts. - Detailed network dial and TCP tunneling lifecycle markers for every individual connection hop.
This output instantly exposes whether configuration overrides were successful or if specific timeouts caused the failure path.
To specify in ~/.ssh/config:
Host myserver
HostName 10.0.0.100
User alice
ProxyCommand go-ssh-jumphost -J [email protected] alice@%h:%p
For a multi-hop scenario:
Host deepserver
HostName 10.0.0.200
User alice
ProxyCommand go-ssh-jumphost -J [email protected],[email protected] alice@%h:%p
- Access Session Advanced Connection Tunnel.
- Set Tunnel type to Local.
- In the ProxyCommand field, enter:
go-ssh-jumphost.exe -J [email protected]:22 alice@%host:%port
Replace alice, gw1.example.com, and ports to match your operational environment.
# Compile development build for the host operating system architecture
make build
# Compile all release binaries to the dist directory
make dist
# Execute unit tests
make test
# Execute static analysis
make vetRelease binaries are generated inside the dist directory and are named by target operating system and architecture. Note that within the Go toolchain, 64-bit ARMv8 (GOARCH=arm64) aligns with the exact linux-arm64 binaries. The linux-armv7 binaries strictly target 32-bit ARMv7 configurations.
Executing the integration suites requires an active Docker and Docker Compose environment:
# Start the test SSH stack
make integration-up
# Execute the test suite
bash test/integration_test.sh
# Terminate and clean up the test environment
make integration-down./go-ssh-jumphost -v -timeout 2s -J [email protected] destinationThis should terminate in approximately two seconds with a clear dial error, preventing the program from hanging indefinitely.
Made with the help of Claude-Code (Sonnet 4.6), Antigravity (Gemini 3.1 Pro) and Github Copilot.