One open connection for many commands instead of a handshake per call
Anyone connecting via SSH to the same server ten or twenty times in a row inside a deployment script pays the full price of a TCP handshake, key exchange and authentication every single time. ControlMaster shares a single established SSH connection over a local Unix socket with every further call, making Bash automation noticeably faster.
Table of Contents
- 1. The problem: every SSH connection costs a full handshake
- 2. ControlMaster, ControlPath and ControlPersist in the SSH configuration
- 3. Speeding up repeated SSH calls in a deployment script
- 4. Controlling the master connection explicitly: check, close, renew
- 5. Choosing ControlPath correctly: the path length limit and the %C hash
- 6. Security implications of a shared socket
- 7. Error handling: orphaned sockets and dropped connections
- 8. Multiplexing also benefits scp, rsync and Git over SSH
- 9. When ControlMaster pays off and what the alternatives are
- 10. Summary
- 11. FAQ
1. The problem: every SSH connection costs a full handshake
A single ssh call goes through a TCP handshake, a Diffie-Hellman key exchange to negotiate the session encryption, and finally the actual authentication, whether by public key or password. On a local network that barely registers, but over an internet connection with noticeable latency, or against a server with a slow public key check, that overhead quickly adds up to several hundred milliseconds per connection.
In a deployment script that uploads application code, runs migrations, clears caches and restarts the service one after another, five separate ssh calls mean five full handshakes in a row, even though the network path, keys and target server never change between calls. That repeated negotiation is exactly what SSH multiplexing eliminates entirely.
2. ControlMaster, ControlPath and ControlPersist in the SSH configuration
The three relevant options typically live in ~/.ssh/config, but can also be set per call with -o. ControlMaster auto tells ssh to establish a master connection on the first connection to a host and to check for an existing master connection on every subsequent one. ControlPath sets where the Unix domain socket for that master connection is placed on the filesystem, and ControlPersist determines how long the master connection stays open after the last channel using it closes, before shutting down automatically.
Without ControlPersist, the master connection would close as soon as the first interactive session ends, even if a script wants to open another connection right after. With a value like ControlPersist 10m, the socket stays open in the background for ten minutes, with no visible active session, and serves any new connection during that window instantly.
# ~/.ssh/config
Host deploy-*
ControlMaster auto
ControlPath ~/.ssh/sockets/%C
ControlPersist 10m
ServerAliveInterval 30
ServerAliveCountMax 3
3. Speeding up repeated SSH calls in a deployment script
Once ControlMaster is set up in the configuration, the actual Bash script does not need to change at all: every ssh and scp call automatically uses the existing master connection, as long as ControlPath resolves to the same host, port and user. The first call in the script still establishes the connection from scratch, every further call in the same run benefits from the already open socket and skips the full handshake.
With several commands run in sequence, that difference becomes very noticeable: instead of negotiating keys and going through authentication again for each of five deployment steps, ssh simply routes the new session through the existing, already authenticated channel, often cutting the total runtime of a multi step deployment by more than half.
#!/usr/bin/env bash
set -euo pipefail
HOST="deploy-prod"
# Each call reuses the same multiplexed connection automatically
ssh "$HOST" "cd /var/www/app && git pull"
ssh "$HOST" "cd /var/www/app && composer install --no-dev"
ssh "$HOST" "cd /var/www/app && php artisan migrate --force"
ssh "$HOST" "sudo systemctl reload php-fpm"
scp deploy-manifest.json "$HOST:/var/www/app/deploy-manifest.json"
4. Controlling the master connection explicitly: check, close, renew
For long running automation it is worth controlling the state of the master connection explicitly instead of relying purely on ControlPersist. ssh -O check host reports whether an active master connection currently exists, without opening a new session itself, and ssh -O exit host deliberately closes an existing master connection, for example at the end of a deployment run, so a socket is not left open longer than necessary.
A robust deployment script therefore opens the master connection explicitly at the start with ssh -MNf host, runs its actual commands, and closes the connection again in a trap handler on exit, regardless of whether the script completes successfully or aborts with an error.
#!/usr/bin/env bash
set -euo pipefail
HOST="deploy-prod"
# -M: act as master, -N: no remote command, -f: go to background
ssh -MNf "$HOST"
trap 'ssh -O exit "$HOST" 2>/dev/null || true' EXIT
ssh "$HOST" "cd /var/www/app && git pull"
ssh "$HOST" "sudo systemctl reload php-fpm"
# Verify the master connection is still up before relying on it further
if ssh -O check "$HOST" 2>&1 | grep -q "Master running"; then
echo "Multiplexed connection still active"
fi
5. Choosing ControlPath correctly: the path length limit and the %C hash
Unix domain sockets have a hard path length limit, typically just over a hundred characters on Linux. A ControlPath pattern like ~/.ssh/sockets/%h-%p-%r, which embeds hostname, port and username in plain text, can exceed that limit with long hostnames or deeply nested home directories, and ssh then silently falls back to a normal, non multiplexed connection without warning.
The %C token solves this by hashing host, port and username into a single, short value, so the resulting socket path always stays the same length regardless of hostname length. The socket directory should also exist and carry tight permissions before ssh uses it for the first time.
#!/usr/bin/env bash
set -euo pipefail
# Ensure the socket directory exists with tight permissions before ssh uses it
mkdir -p -m 700 ~/.ssh/sockets
# %C is a hash of host, port and user -- always short, avoids the
# ~104 byte Unix domain socket path length limit that %h-%p-%r can hit
grep -q 'ControlPath ~/.ssh/sockets/%C' ~/.ssh/config || \
echo 'ControlPath ~/.ssh/sockets/%C' >> ~/.ssh/config
6. Security implications of a shared socket
The Unix domain socket through which ControlMaster shares the connection is, in effect, direct access to an already authenticated SSH session. Any local process with read and write permission on that socket can open a new session through the existing connection without presenting a key or password of its own. On a multi user system with sloppy directory permissions that is a real risk: another local user with access to the socket can impersonate the owner of the master connection on the target server.
The socket directory should therefore consistently be created with mode 700, so only the owning user has access, and should never sit in a path readable by others, such as a shared /tmp without restrictive permissions. Anyone using ControlMaster on a CI runner or a shared build server should additionally make sure every job runs in its own, isolated home directory, so sockets from different jobs can never overlap.
7. Error handling: orphaned sockets and dropped connections
If a script aborts abnormally, for example via kill -9 or a hard system crash, the socket file entry can survive even though the actual ssh master process is long gone. A subsequent ssh call first tries to use that orphaned socket, fails, and depending on the ssh version must detect on its own that a new master connection is required.
A robust Bash wrapper script therefore explicitly checks with ssh -O check before critical commands whether the master connection actually works, and manually cleans up the stale socket path with rm -f on a failed check before attempting a new connection. That prevents a silent fallback to single connections from quietly eating away the expected speed benefits.
8. Multiplexing also benefits scp, rsync and Git over SSH
ControlMaster's speed benefit is not limited to plain ssh calls. Any tool that uses ssh as transport internally, for example scp, rsync with the -e ssh option, or a git remote over an ssh:// URL, automatically benefits from the same master connection, as soon as the matching Host pattern in ~/.ssh/config applies, without those tools needing to know anything about multiplexing themselves.
Especially with a backup routine that syncs many small files individually through repeated rsync calls, or a deployment script that alternates between git pull over SSH and individual ssh commands on the same server, the saved handshakes add up across many calls into a noticeable time savings for the entire script run.
#!/usr/bin/env bash
set -euo pipefail
HOST="deploy-prod"
# rsync automatically reuses the multiplexed SSH connection to $HOST,
# as long as the same Host pattern in ~/.ssh/config applies here too
rsync -az -e ssh ./build/ "$HOST:/var/www/app/current/"
# git over ssh:// benefits the same way, no extra flags needed
git -C /var/www/app pull "ssh://$HOST/var/www/app.git"
9. When ControlMaster pays off and what the alternatives are
SSH multiplexing pays off above all when a script talks to the same target server multiple times in a row, for example in multi step deployments, backup routines with many small file transfers, or monitoring scripts that poll status regularly. For one off, infrequent connections, the extra configuration brings little benefit.
| Approach | Handshake per command | Configuration effort | Typical use |
|---|---|---|---|
| No multiplexing | Yes, on every call | None | One off, infrequent SSH connections |
| ControlMaster + ControlPersist | No, after the first call | Low, once in ssh_config | Deployment scripts with many commands per run |
| ssh-agent | Yes, but without re-entering a password | Low | Interactive sessions, public key without passphrase prompts |
| Ansible with SSH pipelining | No, uses multiplexing internally | Moderate, Ansible configuration | Configuration management across many hosts |
Mironsoft
Shell automation, DevOps tooling and deployment infrastructure
Shell scripts that hold up in production?
We review existing Bash scripts, spot fragile patterns and replace them with robust Bash patterns: complete error handling, logging and safe parallelization for your deployment stack.
Code Review
ShellCheck analysis and manual review for critical Bash pattern violations.
Refactoring
Retrofitting error handling, logging and safe file operations.
CI Integration
Wiring ShellCheck and BATS into pipelines and building regression tests.
10. Summary
SSH Multiplexing in Bash: The Essentials at a Glance
Core idea
ControlMaster shares a single established SSH connection over a Unix socket with every further call to the same host.
Configuration
ControlMaster auto, ControlPath ~/.ssh/sockets/%C and ControlPersist 10m in ~/.ssh/config cover most cases.
Security
The socket directory must have mode 700, otherwise any local user with access can reuse the existing session.
Error handling
ssh -O check verifies the master connection, ssh -O exit closes it deliberately and prevents orphaned sockets.