← back to latest

See the SSH settings a host will actually use before you connect

ssh -G devbox | grep -E '^(user|hostname|port|identityfile|proxyjump) '

anatomy

ssh
The OpenSSH client. Before it opens a socket it assembles one final value for every option from the command line, `~/.ssh/config`, and `/etc/ssh/ssh_config`, in that order. This command stops it right after that step.
-G
Print the configuration after evaluating `Host` and `Match` blocks, then exit. No DNS lookup, no TCP connection, no authentication. You get every option ssh knows about, around 80 lines, including the defaults you never set.
devbox
The name exactly as you would type it to connect. It matters: `Host` patterns match against this string, so `ssh -G devbox` and `ssh -G 10.0.4.17` can resolve to completely different settings for the same machine.
| grep -E
Filter the dump with an extended regex so the alternation `a|b|c` works without backslashes. Option names come out lowercased and followed by a single space, which is what makes a plain line-anchored match reliable.
'^(user|hostname|port|identityfile|proxyjump) '
The five settings behind most failed logins: who you log in as, where you really connect, which port, which keys get offered, and which jump host sits in the middle. The trailing space is doing real work: without it, `^user` also matches `userknownhostsfile` and drags a line of known_hosts paths into the result.

Sample output

user ada
hostname 10.0.4.17
port 2222
identityfile ~/.ssh/id_ed25519_work
proxyjump bastion

This came from a config with a Host devbox block setting User ada, HostName, IdentityFile, and ProxyJump, followed by a Host * block setting User deploy and Port 2222. The user is ada because the first value found wins, and devbox came first. The port is 2222 because devbox never set one, so the catch-all filled it in. If the server only listens on 22, this line is your bug, and you found it without a single connection attempt.

When you would reach for it

A connection that worked yesterday now asks for a password, logs in as the wrong user, or times out on a port you never chose. Your config has grown to a few hundred lines with wildcards, Include files, and a Host * block someone pasted from a blog post, and reading it top to bottom no longer tells you what ssh will do. Ask ssh instead. It is also the check to run before handing a host alias to rsync, scp, or git, since they all go through the same config.

Gotchas

  • For each option, the first value obtained wins and later matches are ignored. That is the reverse of most config formats, where the last line overrides. Put specific Host blocks at the top and Host * at the bottom, or your catch-all silently beats everything below it. The -G output shows the winner, so a wrong value there means an earlier block claimed it.
  • The output tells you what won, not where it came from. Add -v to see which lines applied: ssh -vG devbox prints lines like /home/ada/.ssh/config line 7: Applying options for * to stderr before the dump, which points at the block to fix.
  • -G is not a dry run for Match exec. The command in Match ... exec "..." actually runs during evaluation, because ssh needs its exit status to decide whether the block applies. Keep those commands side-effect free.
  • Match host compares against the name after HostName has been substituted, so Match host devbox never fires once devbox points at 10.0.4.17. Use Match originalhost devbox to test the name you typed. -G is the fastest way to see which one you have.
  • On Alpine the default ssh may be Dropbear’s client, which has no -G. Install openssh-client to get the OpenSSH one. macOS 15 ships OpenSSH, so the flag works out of the box.

Variants

$ ssh -G -o Port=22 devbox | grep '^port '

Test an override without touching the file. Options given on the command line beat anything in the config, and -G shows the result

$ diff <(ssh -F /dev/null -G devbox) <(ssh -G devbox)

Every setting your config files change from the compiled-in defaults. -F /dev/null skips both ~/.ssh/config and the system-wide /etc/ssh/ssh_config, which on Debian quietly turns on HashKnownHosts and SendEnv LANG LC_*. Process substitution feeds both dumps to diff without a temp file

$ ssh -G devbox | awk '$1 == "proxyjump" {print $2}' | xargs ssh -G | grep -E '^(user|hostname|port) '

Resolve the jump host too. A ProxyJump alias goes through the same config, so a bad port on the bastion breaks the connection to devbox even when the devbox block is correct

lineage

SSH started as a reaction to a break-in. In 1995 Tatu Ylönen, a researcher at Helsinki University of Technology, found a password sniffer on the university network and wrote a replacement for rlogin and telnet that encrypted the whole session. He released it as free software, and it spread across the internet within months. Later versions came with more restrictive licensing, so in 1999 the OpenBSD developers took the last freely licensed release, 1.2.12, cleaned it up, and shipped it as OpenSSH in OpenBSD 2.6. The config file's first-match-wins rule has been there essentially from the start, which is why generations of dotfiles put `Host *` at the bottom. The `-G` flag arrived much later, in OpenSSH 6.8 in 2015, once `Match` blocks and `Include` had made the file complicated enough that people needed ssh to explain its own decisions.