Eine offene Verbindung für viele Kommandos statt eines Handshakes pro Aufruf
Wer in einem Deployment-Skript zehn oder zwanzig Mal hintereinander per SSH auf denselben Server zugreift, zahlt jedes Mal den vollen Preis für TCP-Handshake, Schlüsselaustausch und Authentifizierung. ControlMaster teilt eine einmal aufgebaute SSH-Verbindung über einen lokalen Unix-Socket mit allen weiteren Aufrufen und macht Bash-Automatisierung dadurch spürbar schneller.
Inhaltsverzeichnis
- 1. Das Problem: jede SSH-Verbindung kostet einen vollen Handshake
- 2. ControlMaster, ControlPath und ControlPersist in der SSH-Konfiguration
- 3. Wiederholte SSH-Aufrufe in einem Deployment-Skript beschleunigen
- 4. Master-Verbindung gezielt steuern: prüfen, beenden, erneuern
- 5. ControlPath richtig wählen: Pfadlängenlimit und %C-Hash
- 6. Sicherheitsaspekte geteilter Sockets
- 7. Fehlerbehandlung: verwaiste Sockets und abgebrochene Verbindungen
- 8. Multiplexing auch für scp, rsync und Git-over-SSH nutzen
- 9. Wann sich ControlMaster lohnt und welche Alternativen es gibt
- 10. Zusammenfassung
- 11. FAQ
1. Das Problem: jede SSH-Verbindung kostet einen vollen Handshake
Ein einzelner ssh-Aufruf durchläuft einen TCP-Handshake, einen Diffie-Hellman-Schlüsselaustausch zur Aushandlung der Sitzungsverschlüsselung und schließlich die eigentliche Authentifizierung, sei es per Public-Key oder Passwort. Auf einem lokalen Netzwerk fällt das kaum auf, aber bei einer Verbindung über das Internet mit spürbarer Latenz oder bei einem Server mit langsamer Public-Key-Prüfung summiert sich dieser Overhead schnell auf mehrere hundert Millisekunden pro Verbindung.
In einem Deployment-Skript, das nacheinander den Anwendungscode hochlädt, Migrationen ausführt, Caches leert und den Dienst neu startet, bedeutet das bei fünf getrennten ssh-Aufrufen fünf volle Handshakes hintereinander, obwohl sich Netzwerkpfad, Schlüssel und Zielserver zwischen den Aufrufen gar nicht ändern. Genau dieses wiederholte Aushandeln lässt sich mit SSH-Multiplexing vollständig vermeiden.
2. ControlMaster, ControlPath und ControlPersist in der SSH-Konfiguration
Die drei relevanten Optionen leben typischerweise in ~/.ssh/config, lassen sich aber auch pro Aufruf mit -o setzen. ControlMaster auto weist ssh an, für die erste Verbindung zu einem Host eine Master-Verbindung aufzubauen und für jede weitere Verbindung zu prüfen, ob bereits eine Master-Verbindung existiert. ControlPath legt fest, wo der Unix-Domain-Socket für diese Master-Verbindung im Dateisystem abgelegt wird, und ControlPersist bestimmt, wie lange die Master-Verbindung nach dem letzten genutzten Kanal noch offen bleibt, bevor sie automatisch schließt.
Ohne ControlPersist würde die Master-Verbindung schließen, sobald die erste interaktive Sitzung beendet wird, selbst wenn ein Skript direkt danach eine weitere Verbindung öffnen will. Mit einem Wert wie ControlPersist 10m bleibt der Socket zehn Minuten lang im Hintergrund offen, ganz ohne dass eine aktive Sitzung angezeigt läuft, und bedient in dieser Zeit jede neue Verbindung sofort.
# ~/.ssh/config
Host deploy-*
ControlMaster auto
ControlPath ~/.ssh/sockets/%C
ControlPersist 10m
ServerAliveInterval 30
ServerAliveCountMax 3
3. Wiederholte SSH-Aufrufe in einem Deployment-Skript beschleunigen
Sobald ControlMaster in der Konfiguration steht, ändert sich am eigentlichen Bash-Skript nichts: Jeder ssh- und scp-Aufruf verwendet automatisch die vorhandene Master-Verbindung, sofern ControlPath auf denselben Host, Port und Benutzer passt. Der erste Aufruf im Skript baut die Verbindung noch komplett neu auf, jeder weitere Aufruf im selben Skriptlauf profitiert vom bereits offenen Socket und spart den kompletten Handshake.
Bei mehreren aufeinanderfolgenden Kommandos macht sich dieser Unterschied deutlich bemerkbar: Statt bei jedem der fünf Deployment-Schritte erneut Schlüssel auszuhandeln und die Authentifizierung zu durchlaufen, reicht ssh die neue Sitzung einfach über den bestehenden, bereits authentifizierten Kanal durch, was die Gesamtlaufzeit eines mehrstufigen Deployments oft um mehr als die Hälfte reduziert.
#!/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. Master-Verbindung gezielt steuern: prüfen, beenden, erneuern
Für lange laufende Automatisierung lohnt es sich, den Zustand der Master-Verbindung explizit zu steuern, statt sich allein auf ControlPersist zu verlassen. Mit ssh -O check host lässt sich feststellen, ob gerade eine aktive Master-Verbindung existiert, ohne dabei selbst eine neue Sitzung zu öffnen, und mit ssh -O exit host lässt sich eine bestehende Master-Verbindung gezielt schließen, etwa am Ende eines Deployment-Laufs, um keinen Socket unnötig lange offen zu lassen.
Ein robustes Deployment-Skript öffnet die Master-Verbindung deshalb explizit am Anfang mit ssh -MNf host, führt seine eigentlichen Kommandos aus und schließt die Verbindung im trap-Handler beim Beenden wieder, unabhängig davon, ob das Skript erfolgreich durchläuft oder mit einem Fehler abbricht.
#!/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. ControlPath richtig wählen: Pfadlängenlimit und %C-Hash
Unix-Domain-Sockets haben eine harte Pfadlängenbegrenzung, unter Linux typischerweise etwas über hundert Zeichen. Ein ControlPath nach dem Muster ~/.ssh/sockets/%h-%p-%r, das Hostname, Port und Benutzername im Klartext einbaut, kann bei langen Hostnamen oder tief verschachtelten Home-Verzeichnissen dieses Limit überschreiten, und ssh fällt dann kommentarlos auf eine normale, nicht gemultiplexte Verbindung zurück.
Der Ersatztoken %C löst dieses Problem, indem er Host, Port und Benutzername zu einem einzigen, kurzen Hash-Wert zusammenfasst, sodass der resultierende Socket-Pfad unabhängig von der Länge des Hostnamens immer gleich lang bleibt. Das Verzeichnis für die Sockets sollte außerdem existieren und mit engen Rechten versehen sein, bevor ssh es zum ersten Mal verwendet.
#!/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. Sicherheitsaspekte geteilter Sockets
Der Unix-Domain-Socket, über den ControlMaster die Verbindung teilt, ist im Prinzip ein direkter Zugang zur bereits authentifizierten SSH-Sitzung. Jeder lokale Prozess, der Lese- und Schreibrechte auf diesen Socket hat, kann eine neue Sitzung über die bestehende Verbindung öffnen, ohne selbst einen Schlüssel oder ein Passwort vorzuweisen. Auf einem Mehrbenutzersystem mit unsauberen Verzeichnisrechten ist das ein reales Risiko: Ein anderer lokaler Benutzer mit Zugriff auf den Socket kann sich als der Besitzer der Master-Verbindung auf dem Zielserver ausgeben.
Das Sockets-Verzeichnis sollte deshalb konsequent mit Modus 700 angelegt werden, sodass ausschließlich der eigene Benutzer Zugriff hat, und niemals in einem für andere lesbaren Pfad wie einem geteilten /tmp ohne restriktive Rechte liegen. Wer ControlMaster auf einem CI-Runner oder einem geteilten Build-Server einsetzt, sollte zusätzlich sicherstellen, dass jeder Job in einem eigenen, isolierten Home-Verzeichnis läuft, damit Sockets aus verschiedenen Jobs sich niemals überschneiden können.
7. Fehlerbehandlung: verwaiste Sockets und abgebrochene Verbindungen
Bricht ein Skript ungewöhnlich ab, etwa durch kill -9 oder einen harten Systemabsturz, kann der Socket-Datei-Eintrag bestehen bleiben, obwohl der eigentliche ssh-Master-Prozess längst nicht mehr läuft. Ein nachfolgender ssh-Aufruf versucht dann zunächst, diesen verwaisten Socket zu nutzen, scheitert, und muss abhängig von der ssh-Version selbst erkennen, dass eine neue Master-Verbindung nötig ist.
Ein robustes Bash-Wrapper-Skript prüft deshalb vor kritischen Kommandos explizit mit ssh -O check, ob die Master-Verbindung tatsächlich funktioniert, und räumt bei einem fehlgeschlagenen Check den alten Socket-Pfad manuell mit rm -f auf, bevor eine neue Verbindung versucht wird. So verhindert das Skript, dass ein stiller Fallback auf Einzelverbindungen unbemerkt die erwarteten Geschwindigkeitsvorteile kostet.
8. Multiplexing auch für scp, rsync und Git-over-SSH nutzen
Der Geschwindigkeitsvorteil von ControlMaster beschränkt sich nicht auf reine ssh-Aufrufe. Jedes Werkzeug, das intern ssh als Transport verwendet, etwa scp, rsync mit der Option -e ssh oder ein git-Remote über eine ssh://-URL, profitiert automatisch von derselben Master-Verbindung, sobald die passende Host-Regel in ~/.ssh/config greift, ganz ohne dass diese Werkzeuge selbst etwas von Multiplexing wissen müssen.
Besonders bei einer Backup-Routine, die viele kleine Dateien einzeln mit wiederholten rsync-Aufrufen synchronisiert, oder bei einem Deployment-Skript, das abwechselnd git pull über SSH und einzelne ssh-Kommandos auf demselben Server ausführt, summiert sich der eingesparte Handshake über viele Aufrufe hinweg zu einer spürbaren Zeitersparnis für den gesamten Skriptlauf.
#!/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. Wann sich ControlMaster lohnt und welche Alternativen es gibt
SSH-Multiplexing zahlt sich vor allem dann aus, wenn ein Skript mehrfach hintereinander denselben Zielserver anspricht, etwa in mehrstufigen Deployments, Backup-Routinen mit vielen kleinen Dateiübertragungen oder Monitoring-Skripten, die regelmäßig Status abfragen. Für einmalige, seltene Verbindungen bringt die zusätzliche Konfiguration dagegen kaum Nutzen.
| Ansatz | Handshake pro Kommando | Konfigurationsaufwand | Typischer Einsatz |
|---|---|---|---|
| Kein Multiplexing | Ja, bei jedem Aufruf | Keiner | Einmalige, seltene SSH-Verbindungen |
| ControlMaster + ControlPersist | Nein, nach dem ersten Aufruf | Gering, einmalig in ssh_config | Deployment-Skripte mit vielen Befehlen pro Lauf |
| ssh-agent | Ja, aber ohne erneute Passworteingabe | Gering | Interaktive Sitzungen, Public-Key ohne Passphrase-Eingabe |
| Ansible mit SSH-Pipelining | Nein, nutzt intern Multiplexing | Mittel, Ansible-Konfiguration | Konfigurationsmanagement über viele Hosts |
Mironsoft
Shell-Automatisierung, DevOps-Tooling und Deployment-Infrastruktur
Shell-Skripte, die in der Produktion zuverlässig laufen?
Wir analysieren bestehende Bash-Skripte, erkennen fragile Muster und ersetzen sie durch robuste Bash-Patterns: mit vollständiger Fehlerbehandlung, Logging und sicherer Parallelisierung für euren Deployment-Stack.
Code-Review
ShellCheck-Analyse und manuelle Prüfung auf kritische Bash-Pattern-Verstöße.
Refactoring
Fehlerbehandlung, Logging und sichere Dateioperationen nachrüsten.
CI-Integration
ShellCheck und BATS in Pipelines integrieren und Regressionstests aufbauen.
10. Zusammenfassung
SSH-Multiplexing in Bash: Das Wichtigste auf einen Blick
Grundprinzip
ControlMaster teilt eine einmal aufgebaute SSH-Verbindung über einen Unix-Socket mit allen weiteren Aufrufen zum selben Host.
Konfiguration
ControlMaster auto, ControlPath ~/.ssh/sockets/%C und ControlPersist 10m in ~/.ssh/config reichen für die meisten Fälle.
Sicherheit
Das Sockets-Verzeichnis muss Modus 700 haben, sonst kann jeder lokale Nutzer mit Zugriff die bestehende Sitzung mitbenutzen.
Fehlerbehandlung
ssh -O check prüft die Master-Verbindung, ssh -O exit schließt sie gezielt und verhindert verwaiste Sockets.