Why ssh keeps spawning new processes on every connect and how ControlPersist solves it

Why SSH keeps spawning new processes on every connect

Every time I run ssh host, the client forks a new process that talks to the remote sshd.
On the server side, sshd spawns a child for each accepted connection.
The model is simple and works fine for a handful of sessions, but it starts to feel like a drain when you:

  • run a script that opens dozens of SSH connections in a row,
  • use CI/CD pipelines that hit a remote host repeatedly, or
  • have a home‑lab with many small services that need quick SSH access.

Each fork eats a few kilobytes of RAM and a handful of file descriptors.
If you hit the per‑user process limit (ulimit -u) or the system’s max_user_processes, you’ll see “Too many users” or “Connection refused” errors.
Even on a modest machine, the cumulative CPU cost of negotiating the SSH handshake over and over can add up.

ControlPersist solves it

ControlPersist is a client‑side feature that keeps a master SSH connection alive in the background.
Subsequent ssh invocations reuse that socket instead of opening a brand‑new TCP session.

# ~/.ssh/config
Host *
    ControlMaster auto
    ControlPath ~/.ssh/cm_%h_%p_%r
    ControlPersist 10m
  • ControlMaster auto – start a master if none exists, otherwise reuse it.
  • ControlPath – the Unix domain socket that the master listens on.
  • ControlPersist 10m – keep the master alive for 10 minutes after the last client exits.

With this in place, a quick test shows the difference:

$ time ssh -vv remote-host uptime
...
# 0.12s elapsed

$ time ssh -vv remote-host uptime
...
# 0.04s elapsed

The second command reuses the existing socket, so the handshake is skipped and the command runs faster.

Practical usage

  • One‑off scripts: add -o ControlMaster=auto -o ControlPersist=5m to the command line.
    No need to touch ~/.ssh/config.
  • Interactive work: keep the master running in the background by simply opening a terminal and running ssh remote-host.
    Subsequent commands will be instant.
  • Cleanup: when you’re done, close the master with ssh -O exit remote-host.
    Or let the 10‑minute timeout expire.

Caveats

Issue What to watch for Fix
Stale sockets If the master crashes, the socket file remains and new clients fail to connect. ssh -O exit remote-host or delete ~/.ssh/cm_*.
File‑descriptor limits The master keeps file descriptors open for each session. Use ControlPersist 0 to disable persistence, or increase MaxSessions on the server.
Server support Some older sshd versions ignore ControlPersist‑related options. Ensure you’re running OpenSSH ≥ 7.0 (most distros ship newer).
Security The socket is world‑writable by default on some systems. Set chmod 700 ~/.ssh/cm_* or use ControlPath ~/.ssh/cm_%h_%p_%r which creates the socket with the client’s UID.

Security note

The Unix domain socket is the only surface that persists after the client exits.
Make sure it’s owned by you and not group‑writable:

chmod 700 ~/.ssh/cm_*

If you’re using sudo to run SSH commands, the socket will be owned by root, which can be a privilege escalation vector.
Avoid running ssh as root unless absolutely necessary.

Bottom line

ControlPersist turns a stateless SSH client into a lightweight multiplexing proxy.
It cuts down on CPU, memory, and network chatter, and it keeps your process limits from blowing out in busy scripts or homelab setups.
Add the snippet to ~/.ssh/config, test with ssh -vv, and you’ll notice the difference immediately.


See also