$RodHat_
Console Tips

Your ssh is reconnecting every single time. It doesn't have to.

Published by

Your ssh is reconnecting every single time. It doesn't have to.
Photo: AI-generated — no human photographer / RodHat AI Cover

Every ssh you run does a TCP handshake, a key exchange, a host key check, and an authentication round trip before it prints a single byte. Over a WAN with a bastion in the middle that’s most of a second. Run a loop over forty hosts and you’ve spent thirty seconds doing arithmetic that a laptop finishes in microseconds.

OpenSSH has shipped the fix since 2004 and it’s off by default.

The four lines

~/.ssh/config:

Host *
    ControlMaster auto
    ControlPath ~/.ssh/cm/%C
    ControlPersist 10m
mkdir -p ~/.ssh/cm && chmod 700 ~/.ssh/cm

That’s the whole thing. First connection to a host opens a real session and leaves a control socket behind. Every subsequent connection to the same host/port/user notices the socket and rides the existing TCP connection — no handshake, no re-auth. ControlPersist 10m keeps the master alive ten minutes after your last session closes, so it survives the gap between “run a command” and “run the next command.”

The speedup is not subtle. First hop, 700ms. Every hop after that, the round-trip time and nothing else.

Use %C in the ControlPath, not %h/%p/%r. %C is a hash of the whole tuple, which keeps the path short — and control paths live in sun_path in a Unix socket address, which has a hard limit around 104 bytes. Long hostnames plus a long home directory blows past it, and the error you get is not helpful.

Manage the masters

ssh -O check host      # is there a master?
ssh -O exit  host      # tear it down
ssh -O stop  host      # stop accepting new sessions, let existing ones finish

-O exit is the one you want when you’ve changed something — keys, network, the host itself — and ssh keeps cheerfully using a stale connection to a box that rebooted.

ProxyJump, and why the old ProxyCommand line is wrong

If you still have this in your config, from a blog post someone wrote in 2012:

ProxyCommand ssh -W %h:%p bastion

Replace it:

Host prod-*
    ProxyJump bastion

ProxyJump (-J) is native since OpenSSH 7.3. It does the same thing without spawning a shell to do it, it composes — -J bastion1,bastion2 chains — and critically it applies your host-key checking to both hops properly. ProxyCommand with -W fed the inner connection through a shell, which meant your %h went through shell quoting, which meant a hostile hostname got interesting.

Multiplexing stacks with it. Set ControlMaster on the bastion too, and forty connections through it share one authenticated session to the bastion and one each to the targets.

The config that makes it worth it

Config is matched top to bottom and first setting wins, which is backwards from what most people assume. Put specifics first, Host * last, always.

Host bastion
    HostName bastion.example.net
    User rod
    IdentityFile ~/.ssh/id_ed25519_prod
    ControlPersist 4h

Host prod-*
    ProxyJump bastion
    User deploy
    IdentityFile ~/.ssh/id_ed25519_prod
    IdentitiesOnly yes

Host *.internal 10.*
    StrictHostKeyChecking accept-new

Host *
    ControlMaster auto
    ControlPath ~/.ssh/cm/%C
    ControlPersist 10m
    ServerAliveInterval 30
    ServerAliveCountMax 3
    HashKnownHosts no

Four of those deserve a note:

IdentitiesOnly yes — without it, ssh offers every key in your agent, in order, and a host with MaxAuthTries 3 disconnects you before it reaches the right one. This is the actual cause of ninety percent of “it says permission denied but the key is right there.”

accept-new — trusts a host key the first time, but still screams if a known key changes. That’s the useful half of StrictHostKeyChecking no without the half that makes MITM silent. Never set it to plain no.

ServerAliveInterval — kills the session when the connection is genuinely dead instead of leaving you staring at a frozen terminal, and keeps NAT tables from timing you out mid-thought.

HashKnownHosts no — heresy, I know. Hashed known_hosts protects the list of machines you connect to from someone who already has your home directory, which is a threat model where you have bigger problems. What it costs you is the ability to grep the file when a key changes, and I grep that file constantly.

The one caveat

Multiplexed sessions share a TCP connection. That means they share its fate: kill the master and every session on it dies, and a single slow session with a big scp running can head-of-line block the interactive shell you’re trying to type in. If you’re moving real data, -o ControlMaster=no on that one invocation and let it get its own connection.

Everything else, let it share. Once you’ve felt an ssh open in twenty milliseconds you won’t go back.