ssh --help dumps forty flags in alphabetical order, which is the least useful way to learn them. You reach for flags by what you’re trying to do — connect as a different user, forward a port, hop through a bastion, debug a refusal — so here’s the same set organized by task, with a note on which ones to stop typing and put in your ssh_config instead.
The rule underneath all of it: flags are for one-offs; config is for permanence. Anything you pass twice belongs in the file.
Connection basics
The flags that answer “who, where, and with what key.”
-p <port>— connect to a non-standard port.ssh -p 2222 user@host. (Lowercase-p; uppercase-Pis scp’s port flag, which trips everyone up once.)-l <user>— the login user, an alternative to theuser@prefix.ssh -l deploy hostequalsssh deploy@host; you’ll see-lin scripts and older docs.-i <identity>— point at a specific private key.ssh -i ~/.ssh/work_ed25519 user@host. The most useful flag when auth fails: it forces the exact key instead of letting the agent guess.-F <configfile>— use a different config file.ssh -F ./project.sshconfig web1, or-F /dev/nullto ignore your personal config and test with zero customization.-v/-vv/-vvv— verbosity, and the flag that ends most mysteries. One-vshows the auth methods tried and keys offered;-vvvshows the full handshake. When something fails, run it again with-vvvbefore you theorize.
The -o escape hatch
This is the flag worth understanding deeply, because it subsumes dozens of others. -o sets any ssh_config option inline. Anything you could write in ~/.ssh/config under a Host block, you can pass as -o Option=value — not a short list of blessed options, but the entire config vocabulary, available for a single connection.
A few that come up constantly:
ssh -o IdentitiesOnly=yes -i ~/.ssh/right_key user@host # offer only this key
ssh -o StrictHostKeyChecking=accept-new user@host # trust new hosts, still catch changes
ssh -o ServerAliveInterval=30 user@host # keepalive for a flaky link
ssh -o ProxyJump=bastion.example.com db.internal # hop through a bastion inline
-o IdentitiesOnly=yes is the fix for Too many authentication failures when your agent offers six keys and a strict server cuts you off after five. -o StrictHostKeyChecking=accept-new is the sane middle ground — auto-trust a host you’ve never seen, but still scream if a known host’s key changes.
The point: you don’t need a special flag for each behavior. If you know the config directive, you know the flag — it’s -o Directive=value. Once you’re passing three or four every time, move them into config for good.
Forwarding and tunnels
The port-plumbing flags. The mental model — which side opens the port — gets its own read in SSH port forwarding explained; here’s the flag summary.
-L <local>:<host>:<port>— local forward. Open a port on your machine that tunnels to a destination reachable from the server.ssh -L 5432:localhost:5432 db.example.combrings a remote database to your127.0.0.1:5432.-R <remote>:<host>:<port>— remote forward. Open a port on the server that tunnels back to your side — the reverse-tunnel trick for reaching a machine behind NAT.-D <port>— dynamic forward. A SOCKS proxy on your machine; destination chosen per connection.ssh -D 1080 jump.example.com, then point a browser at127.0.0.1:1080.-N— don’t run a remote command. Pair with any forward when you want only the tunnel and no shell.-f— backgroundsshafter it connects. Combine with-Nfor a fire-and-forget tunnel:ssh -fN -L 5432:localhost:5432 db.example.com.-g— let other machines on your LAN use a local (-L/-D) forward, not just127.0.0.1. A small exposure — you’re opening the tunnel to your whole network, so use it deliberately.
Jump hosts and the agent
Reaching the next hop, and the flag you should be cautious with.
-J <jumphost>— ProxyJump. Tunnel through a bastion and authenticate end-to-end from your laptop to the target.ssh -J bastion.example.com db.internal. The bastion only shuttles encrypted bytes; your key never lands on it — the right way through a gateway. Full case: ProxyJump: reach servers behind a bastion.-A— agent forwarding. Exposes a socket on the remote host that proxies signing requests back to your local agent, so a process there can authenticate as you to the next server. Convenient and riskier than it looks: anyone with root on that host can borrow your identity while the session is open. Use-Jfor “reach the next hop” and keep-Afor hosts you fully trust — the reasons are in agent forwarding is convenient and dangerous.
Between the two: -J gets your shell to a host behind the bastion without lending anyone your keys; -A lends your keys’ live use to the box you connected to. Not the same trade.
Session control
The small flags that fix specific annoyances.
-t— force a pseudo-terminal. Needed for a remote command that expects an interactive TTY:ssh -t host 'sudo systemctl status nginx'gives you a real terminal for thesudoprompt and colored output. Double it (-tt) to force one when there’s no local TTY.-q— quiet mode. Suppresses warnings and banners; useful in scripts where you want only the command’s own output.-C— request compression. A real win on slow, high-latency links; negligible or slightly negative on a fast LAN.-o BatchMode=yes— never prompt. If auth would need a password or passphrase, fail immediately instead of hanging. This is what makes SSH safe inside cron and CI — a stuck prompt in an unattended script hangs forever.
The compact reference
| Flag | Does | ssh_config equivalent |
|---|---|---|
-p 2222 | Non-standard port | Port 2222 |
-l user / user@ | Login user | User user |
-i ~/.ssh/key | Specific identity | IdentityFile ~/.ssh/key |
-F file | Alternate config file | — (this is the config) |
-v … -vvv | Verbose / debug | LogLevel DEBUG3 |
-o Opt=val | Any config option inline | (the option itself) |
-L a:host:b | Local forward | LocalForward a host:b |
-R a:host:b | Remote forward | RemoteForward a host:b |
-D 1080 | Dynamic (SOCKS) forward | DynamicForward 1080 |
-N | No remote command | RequestTTY no (+ no command) |
-f | Background after connect | — (one-off only) |
-g | Open forward to your LAN | GatewayPorts yes |
-J host | Jump through a bastion | ProxyJump host |
-A | Forward the agent | ForwardAgent yes |
-t | Force a TTY | RequestTTY force |
-q | Quiet | LogLevel QUIET |
-C | Compression | Compression yes |
-o BatchMode=yes | Never prompt | BatchMode yes |
The right column is the tell. Almost every flag has a config equivalent, and that’s the workflow: use the flag once to prove a connection works, then move the ones you’ll repeat into ~/.ssh/config so ssh web1 carries all of them with no flags at all. The flags with no config equivalent — -f, -F, the debug verbosity — are the genuinely one-off ones.
Where a client keeps the state for you
Flags and config both live in your dot-files, per machine, and that hurts at scale: settings copied across laptops by hand, tunnels re-run after every sleep, no way to see which of forty hosts is up. Termalin keeps the same facts a flag would carry — port, user, key, jump host, keepalives, saved local/remote/dynamic forwards — attached to the host as structured data, organized into folders and tags, synced across your machines end-to-end encrypted. It verifies each server on first connect, shows live status per host, and starts tunnels with the connection instead of a retyped command. The flags teach you what’s happening; a client stops you retyping them every morning.
Termalin is a free, cross-platform SSH client that stores per-host settings, keys and tunnels for you — download it, or read how it handles your keys safely.