SSH-Multiplexing mit ControlMaster in Automatisierung nutzen
AI generated
$_
#!/
Bash · SSH · Deployment · Automatisierung
SSH-Multiplexing mit ControlMaster
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.

15 Min. Lesezeit ControlMaster · ControlPath · ControlPersist SSH · Deployment · Bash

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.

11. FAQ: SSH-Multiplexing in Bash: Das Wichtigste auf einen Blick

1Was macht ControlMaster in der SSH-Konfiguration?
ControlMaster auto weist ssh an, für die erste Verbindung zu einem Host eine Master-Verbindung aufzubauen und alle weiteren Verbindungen über diese eine Verbindung zu teilen, statt jedes Mal neu zu verhandeln.
2Wofür steht ControlPath genau?
ControlPath legt den Dateisystempfad des Unix-Domain-Sockets fest, über den die Master-Verbindung geteilt wird. %C erzeugt daraus einen kurzen Hash aus Host, Port und Benutzer.
3Was passiert ohne ControlPersist?
Ohne ControlPersist schließt die Master-Verbindung, sobald die erste Sitzung beendet wird. Mit ControlPersist 10m bleibt sie zehn Minuten im Hintergrund offen und bedient neue Verbindungen sofort.
4Muss ich mein Deployment-Skript für ControlMaster anpassen?
Nein. Sobald die Konfiguration in ~/.ssh/config steht, nutzt jeder ssh- und scp-Aufruf die Master-Verbindung automatisch, sofern Host, Port und Benutzer übereinstimmen.
5Wie prüfe ich, ob eine Master-Verbindung aktiv ist?
Mit ssh -O check host. Der Befehl meldet, ob eine laufende Master-Verbindung existiert, ohne selbst eine neue Sitzung zu öffnen.
6Wie beende ich eine Master-Verbindung gezielt?
Mit ssh -O exit host. Das schließt die geteilte Verbindung sauber, was sich für den Abschluss eines Deployment-Laufs empfiehlt.
7Warum sollte ich %C statt %h-%p-%r im ControlPath verwenden?
Unix-Domain-Sockets haben eine harte Pfadlängenbegrenzung. %C erzeugt einen kurzen Hash und bleibt damit unabhängig von der Länge des Hostnamens innerhalb des Limits.
8Welches Sicherheitsrisiko bringt ein geteilter Socket mit sich?
Jeder lokale Prozess mit Zugriff auf den Socket kann eine neue Sitzung über die bestehende, bereits authentifizierte Verbindung öffnen. Das Sockets-Verzeichnis muss deshalb mit Modus 700 nur dem eigenen Benutzer gehören.
9Was tue ich bei einem verwaisten Socket nach einem Absturz?
Vor kritischen Kommandos mit ssh -O check prüfen, ob die Master-Verbindung funktioniert, und bei einem fehlgeschlagenen Check den alten Socket-Pfad manuell mit rm -f entfernen, bevor eine neue Verbindung versucht wird.
10Lohnt sich ControlMaster für ein Skript, das nur einmal verbindet?
Kaum. Der Geschwindigkeitsvorteil entsteht erst ab dem zweiten Aufruf zum selben Host. Für einmalige Verbindungen bringt die zusätzliche Konfiguration keinen messbaren Nutzen.