Ein SSH Tunnel leitet eine beliebige TCP-Verbindung durch eine verschlüsselte SSH-Verbindung. Damit erreichst du eine Datenbank, ein Admin-Panel oder ein internes Dashboard auf deinem Server, ohne dafür einen einzigen Port ins Internet zu öffnen. Du brauchst nichts zu installieren: Wer sich per SSH auf einem Server anmelden kann, kann in den allermeisten Fällen auch einen SSH Tunnel aufbauen.
Es gibt drei Richtungen, und genau die verwirren fast jeden beim ersten Mal: -L holt einen Dienst vom Server zu dir, -R bringt einen Dienst von dir auf den Server, und -D macht aus SSH einen SOCKS-Proxy. Dieser Artikel erklärt alle drei so, dass du die Syntax nicht mehr nachschlagen musst, und zwar nicht nur mit Theorie. Wir haben für diesen Text einen Wegwerf-SSH-Server auf unserem eigenen Host gestartet und gemessen, was ein Tunnel kostet, was er schluckt und an welchen zwei Stellen er dir eine Einstellung stillschweigend nicht erfüllt.

Wenn dir SSH selbst noch neu ist, lies zuerst unseren Grundlagenartikel Was ist SSH?. Dort steht, wie die Verschlüsselung funktioniert und warum Schlüssel statt Passwörter keine Komfortfrage sind. Hier geht es um den nächsten Schritt: was du mit der fertigen Verbindung alles transportieren kannst.
Was ist ein SSH Tunnel? Das Prinzip in einem Satz
Ein SSH Tunnel ist eine Abmachung zwischen deinem SSH-Client und dem SSH-Server: „Alles, was an diesem Port ankommt, packst du in die verschlüsselte Verbindung und lieferst es auf der anderen Seite an jener Adresse ab.“
Das klingt abstrakt, ist im Alltag aber ganz konkret. Auf unserem Server lauscht zum Beispiel eine PostgreSQL-Datenbank auf 127.0.0.1:5432. Das ist absichtlich so: Die Datenbank ist von außen nicht erreichbar, nur Programme auf dem Server selbst kommen dran. Wenn ich sie trotzdem mit einem grafischen Programm von meinem Laptop aus ansehen will, habe ich zwei Möglichkeiten:
- Ich öffne Port 5432 in der Firewall und lasse PostgreSQL auf allen Schnittstellen lauschen. Damit steht die Datenbank im Internet, und jeder Bot der Welt darf Passwörter raten.
- Ich baue einen SSH Tunnel. Die Datenbank bleibt, wo sie ist. Mein Laptop spricht mit einem lokalen Port, und SSH trägt die Pakete durch die Verbindung, die ohnehin schon offen ist.
Option 1 ist der Fehler, den wir in unserem Artikel über IT-Sicherheitslücken an echten Fällen beschreiben. Option 2 ist der Grund, warum es SSH Tunnel gibt.
Drei Begriffe tauchen in jedem Tunnel-Befehl auf. Wenn du die trennst, ist die Syntax kein Rätsel mehr:
- Der Eingang: der Port, an dem der Tunnel Verbindungen annimmt. Bei
-Lliegt er auf deinem Rechner, bei-Rauf dem Server. - Das Ziel: die Adresse, an die der Tunnel am anderen Ende ausliefert. Entscheidend: Diese Adresse wird aus Sicht der Gegenseite aufgelöst.
127.0.0.1im Ziel bei-Lheißt „der Server selbst“, nicht dein Laptop. - Der Transport: die SSH-Verbindung zu
user@server, durch die alles läuft.
Der häufigste Denkfehler beim SSH Tunnel ist genau der zweite Punkt. localhost bedeutet in einem Tunnel-Befehl immer „localhost auf der Seite, wo das Ziel aufgelöst wird“. Wer das einmal verinnerlicht, schreibt die Befehle danach im Schlaf.

Das Video von Hussein Nasser (englisch) zeigt lokales und entferntes Forwarding an Beispielen und ist eine gute Ergänzung, wenn du die Richtungen lieber einmal gezeichnet siehst.
So haben wir getestet
Damit am echten SSH-Dienst unseres Servers nichts verändert wird, haben wir für jeden Versuch einen zweiten, temporären sshd gestartet: eigener Port (2299), nur an 127.0.0.1 gebunden, eigener Host-Schlüssel, eigener Client-Schlüssel, Passwort-Anmeldung aus. Server und Client sind dieselbe Maschine (12 Kerne, Ubuntu, OpenSSH_9.6p1). Das heißt: Netzwerk-Latenz spielt in unseren Zahlen keine Rolle. Was wir messen, ist der reine Aufwand von SSH selbst. Das ist bei Durchsatzfragen der ehrlichere Vergleich, weil echte Netzwerke jeden Effekt mit ihrer eigenen Latenz überdecken würden.
Die Testdienste waren ein kleiner Python-Webserver, ein minimaler eigener Socket-Server und ein echter Node.js-Dienst, der auf dem Server ohnehin läuft. Nach den Tests haben wir alle Prozesse beendet und geprüft, dass keiner der Testports mehr lauscht.
So sieht die Ausgangslage des produktiven sshd aus (sshd -T), damit du weißt, mit welchen Standardwerten du vermutlich selbst arbeitest:
allowtcpforwarding yes
gatewayports no
permitopen any
permittunnel no
clientaliveinterval 0
Das sind die Ubuntu-Standards. allowtcpforwarding yes heißt: Jeder, der sich anmelden darf, darf auch tunneln. Zu dieser Tatsache kommen wir im Sicherheitsabschnitt zurück.
Lokales Port-Forwarding mit -L
Lokales Forwarding ist der Fall, den du am häufigsten brauchst. Du holst einen Dienst, der nur auf dem Server (oder nur im Netz hinter dem Server) erreichbar ist, auf einen Port deines Rechners.
ssh -L 8080:127.0.0.1:5432 user@server
Gelesen von links nach rechts: „Öffne auf meinem Rechner Port 8080. Was dort ankommt, schicke durch die Verbindung zu server, und der Server soll es an 127.0.0.1:5432 weitergeben.“ Danach verbindest du dein Datenbankprogramm mit localhost:8080, und es spricht in Wahrheit mit der PostgreSQL auf dem Server.
Im Test hat genau das sofort funktioniert. ss -tlnp zeigte danach den neuen Eingang, und zwar zweimal:
LISTEN 127.0.0.1:18080 users:(("ssh",pid=2932593,fd=5))
LISTEN [::1]:18080 users:(("ssh",pid=2932593,fd=4))
Der SSH-Client lauscht also nur auf deiner Loopback-Schnittstelle, für IPv4 und IPv6. Andere Geräte in deinem WLAN kommen nicht an den Tunnel. Das ist der sichere Standard. Die erste Anfrage durch den Tunnel kam mit HTTP 200 in 1,7 Millisekunden zurück.
Das Ziel muss nicht der Server selbst sein
Die Zieladresse kann jeder Rechner sein, den der Server erreicht. Das macht -L zum Werkzeug für Netze, in die du von außen gar nicht hineinkommst:
ssh -L 8443:10.0.0.5:443 user@bastion
Hier ist bastion ein Sprungserver mit öffentlicher Adresse, und 10.0.0.5 ist ein internes Admin-Panel, das nur im privaten Netz hängt. Dein Browser öffnet https://localhost:8443, und der Bastion-Host reicht die Verbindung nach innen weiter. Wichtig: Die Strecke vom Bastion-Host zu 10.0.0.5 ist nicht von SSH verschlüsselt. Verschlüsselt ist nur der Weg von dir bis zum Bastion-Host. Bei HTTPS-Zielen ist das egal, bei Klartext-Protokollen solltest du wissen, dass das letzte Stück offen ist.
Nur den Tunnel, ohne Shell
Wenn du nur den Tunnel willst und keine Kommandozeile, sind zwei Schalter nützlich:
ssh -N -f -L 8080:127.0.0.1:5432 user@server
-Nführt keinen Befehl auf dem Server aus. Die Verbindung existiert nur für den Tunnel.-fschickt SSH nach der Anmeldung in den Hintergrund. Dein Terminal ist wieder frei.
Mit -f läuft der Tunnel weiter, bis du den Prozess beendest. Finden kannst du ihn mit pgrep -af "ssh -N", beenden mit kill <PID>. Eleganter geht es mit einer Steuerverbindung, die wir weiter unten zeigen.
Mehrere Tunnel in einer Verbindung
Du kannst -L beliebig oft angeben. Eine SSH-Verbindung trägt alle Tunnel gleichzeitig:
ssh -N -L 8080:127.0.0.1:5432 -L 9090:127.0.0.1:9090 -L 3001:127.0.0.1:3000 user@server
Datenbank, Monitoring und ein internes Dashboard, alles über eine einzige Anmeldung. Für die Firewall des Servers ist das unsichtbar: Sie sieht nur die SSH-Verbindung auf Port 22.
Falle 1: Ein belegter Port, und SSH macht trotzdem weiter
Hier kommt der erste Fund aus unseren Tests, und er ist tückisch. Wir haben einen zweiten Tunnel auf einen Port gelegt, den der erste Tunnel schon belegt hatte. Ergebnis ohne Zusatzoption:
bind [127.0.0.1]:18080: Address already in use
channel_setup_fwd_listener_tcpip: cannot listen to port: 18080
Could not request local forwarding.
Das sieht nach einem Fehler aus. Ist es aber nur halb: Der SSH-Prozess lief weiter. Unser timeout 4 musste ihn nach vier Sekunden abschießen (Rückgabewert 124). Die Verbindung steht, die Anmeldung war erfolgreich, nur der Tunnel, für den du das Ganze gestartet hast, existiert nicht. Mit -f im Hintergrund siehst du diese Warnung nicht einmal, wenn die Ausgabe irgendwo im Log verschwindet. Und wenn der belegte Port zufällig einem anderen Dienst gehört, verbindest du dich mit dem und wunderst dich über die Antworten.
Die Lösung ist eine einzige Option:
ssh -o ExitOnForwardFailure=yes -N -f -L 8080:127.0.0.1:5432 user@server
Mit ExitOnForwardFailure=yes hat SSH im selben Versuch sofort mit Rückgabewert 255 abgebrochen. Ein Skript merkt das, ein systemd-Dienst startet neu, und du siehst den Fehler. In Skripten und Diensten gehört diese Option immer dazu. Wir würden sie auch interaktiv in die ~/.ssh/config schreiben.
Entferntes Port-Forwarding mit -R (Reverse SSH Tunnel)
-R ist die Gegenrichtung. Der Eingang liegt auf dem Server, das Ziel bei dir. Damit machst du einen Dienst, der auf deinem Rechner läuft, auf dem Server erreichbar.
ssh -R 9000:127.0.0.1:3000 user@server
Gelesen: „Öffne auf dem Server Port 9000. Was dort ankommt, schicke durch die Verbindung zurück zu mir, und mein Rechner gibt es an 127.0.0.1:3000 weiter.“

Wofür braucht man das? Drei typische Fälle:
- Ein Rechner hinter NAT oder Mobilfunk-Router, der keine eingehenden Verbindungen annehmen kann. Er baut von sich aus eine Verbindung zum Server auf, und über
-Rkommst du von dort zurück auf ihn. Das ist der klassische Reverse SSH Tunnel für Fernwartung. - Webhooks auf die lokale Entwicklungsmaschine. Ein Zahlungsanbieter will deinen Webhook unter einer öffentlichen Adresse aufrufen, dein Code läuft aber auf dem Laptop. Der Server nimmt die Anfrage an, und der Tunnel reicht sie zu dir durch.
- Etwas vorzeigen, das noch nicht deployed ist.
Im Test kam eine Anfrage an den neuen Port auf der Serverseite sofort mit HTTP 200 vom Dienst auf der Clientseite zurück.
Falle 2: GatewayPorts verwirft deine Adresse ohne Meldung
Jetzt der zweite und wichtigere Fund. Wer einen Webhook von außen empfangen will, braucht den -R-Eingang auf der öffentlichen Schnittstelle, nicht auf Loopback. Also schreibt man die Adresse ausdrücklich dazu:
ssh -R 0.0.0.0:19000:127.0.0.1:18500 user@server
Wir haben das gegen unseren Testserver mit der Ubuntu-Standardeinstellung GatewayPorts no gemacht, zusätzlich mit ExitOnForwardFailure=yes. SSH hat nicht abgebrochen, keine Warnung ausgegeben, der Tunnel lief. Ein Blick auf die Serverseite:
LISTEN 127.0.0.1:19000
LISTEN [::1]:19000
Der Server hat das 0.0.0.0 einfach ignoriert und den Port nur an Loopback gebunden. Von außen ist der Tunnel nicht erreichbar. Und weil der Tunnel aus Sicht des Clients erfolgreich eingerichtet wurde, schlägt auch ExitOnForwardFailure nicht an. Du suchst den Fehler dann in deiner Firewall, beim Webhook-Anbieter oder in deinem Code, aber nicht dort, wo er liegt.
Damit -R öffentlich lauscht, muss der Server das erlauben, in /etc/ssh/sshd_config:
GatewayPorts clientspecified
Mit clientspecified darf der Client die Bindungsadresse selbst wählen. yes würde jeden -R-Tunnel öffentlich machen, auch die, bei denen du das nicht wolltest. Wir empfehlen clientspecified und zusätzlich eine Einschränkung nur für den Benutzer, der das braucht:
Match User tunnel
GatewayPorts clientspecified
AllowTcpForwarding remote
PermitListen 0.0.0.0:9000
X11Forwarding no
PermitTTY no
Danach sudo sshd -t (Syntaxprüfung) und sudo systemctl reload ssh. Und vergiss nicht, dass der Port dann auch in der Firewall offen sein muss. Wie das sauber geht, steht in unserer Anleitung UFW Firewall einrichten.
Der Spiegel auf der Clientseite
Interessant ist die Asymmetrie. Bei -L haben wir dasselbe probiert, -L 0.0.0.0:18081:..., und dort hat der Client die Adresse befolgt: Der Port lauschte auf 0.0.0.0, also für jedes Gerät, das deinen Rechner erreicht. Das ist dokumentiert (eine ausdrückliche Bindungsadresse hat bei -L Vorrang), aber es heißt praktisch: Auf deinem Laptop im Café-WLAN reicht ein 0.0.0.0 im Befehl, und jeder am Nachbartisch kann deine Server-Datenbank über deinen Laptop erreichen. Bei -L gilt daher: keine Bindungsadresse angeben, außer du willst den Tunnel ausdrücklich mit anderen Geräten teilen.
Kurz zusammengefasst: Bei -L entscheidet dein Client, und er gehorcht dir. Bei -R entscheidet der Server, und er widerspricht dir, ohne es zu sagen.
Dynamisches Forwarding mit -D (SOCKS-Proxy)
-L und -R verbinden immer genau einen Port mit genau einem Ziel. -D ist anders: Es öffnet einen SOCKS-Proxy, und die Anwendung sagt bei jeder Verbindung selbst, wohin sie will.
ssh -D 1080 -N user@server
Danach stellst du in deinem Browser (oder in curl, in einem Datenbankprogramm, in einer Paketverwaltung) einen SOCKS5-Proxy auf 127.0.0.1:1080 ein. Jede Verbindung dieser Anwendung tritt dann beim Server ins Netz aus. Für Webseiten sieht es so aus, als säßest du im Rechenzentrum.

Im Test:
curl --socks5-hostname 127.0.0.1:11080 http://127.0.0.1:18500/
kam mit HTTP 200 zurück, und zwar vom Dienst auf der Serverseite. Das 127.0.0.1 in der URL wurde also auf dem Server aufgelöst, nicht lokal. Das ist die Variante --socks5-hostname (oder socks5h://): Auch die DNS-Auflösung läuft durch den Tunnel. Mit --socks5 ohne -hostname löst curl den Namen lokal auf, und deine DNS-Anfragen gehen am Tunnel vorbei. Bei Browsern heißt die entsprechende Einstellung meist „DNS über den SOCKS-Proxy“ oder „Proxy-DNS“. Wer -D nutzt, um nicht über das lokale Netz aufzufallen, muss diese Einstellung setzen.
Typische Einsatzzwecke:
- Mehrere interne Weboberflächen in einem Netz erreichen, ohne für jede einen eigenen
-L-Tunnel zu bauen. - In einem fremden WLAN den Browserverkehr verschlüsselt über den eigenen Server leiten.
- Prüfen, wie eine Seite aus dem Rechenzentrum aussieht (Geo-Blocking, Caching, Firewall-Regeln).
Ein SOCKS-Proxy ist kein VPN. Er trägt nur, was eine Anwendung ihm ausdrücklich gibt. Dein System, andere Programme und UDP-Verkehr laufen weiter direkt. Wenn du ganze Geräte oder Netze verbinden willst, ist ein echtes VPN das bessere Werkzeug. In unserem Vergleich Tailscale vs WireGuard haben wir beide Wege gemessen.
Das Video von Automation Avenue (englisch) zeigt -R und -D Schritt für Schritt in einer Laborumgebung.
Was kostet ein SSH Tunnel? Unsere Messung
Die häufigste Sorge: Macht der Tunnel alles langsam? Wir haben das an zwei Größen gemessen, Durchsatz und Antwortzeit.
Durchsatz
Eine 200-MB-Datei, dreimal direkt vom lokalen Webserver und dreimal durch einen -L-Tunnel geladen:
| Lauf | Direkt | Durch den Tunnel |
|---|---|---|
| 1 | 1,98 GB/s | 0,76 GB/s |
| 2 | 1,67 GB/s | 0,85 GB/s |
| 3 | 1,87 GB/s | 0,85 GB/s |
Durch den Tunnel bleiben also etwa 40 bis 50 Prozent des Loopback-Durchsatzes übrig. Das klingt dramatisch, ist es aber nicht: Rund 800 MB/s entsprechen gut 6 Gigabit pro Sekunde. Das ist die Geschwindigkeit, mit der eine SSH-Verbindung auf einem einzelnen Prozessorkern verschlüsseln und entschlüsseln kann. Deine echte Internetleitung liegt fast immer weit darunter. Bei einem Server-Uplink von 1 Gbit/s ist der Tunnel nicht der Engpass.
Wichtig zu wissen: Eine SSH-Verbindung verschlüsselt auf einem Kern. Mehrere Tunnel in derselben Verbindung teilen sich diesen Kern. Wenn du große Datenmengen parallel bewegst, sind mehrere getrennte Verbindungen schneller als eine mit vielen -L.
Antwortzeit, und der Moment, in dem wir uns fast falsch gemessen hätten
Bei der Antwortzeit haben wir zuerst einen Wert gesehen, der uns kurz erschreckt hat. 50 kleine Anfragen an den Python-Webserver:
| Median | 95. Perzentil | |
|---|---|---|
| Direkt | 0,54 ms | 0,73 ms |
| Durch den Tunnel | 41,99 ms | 42,13 ms |
Fast 42 Millisekunden pro Anfrage, auf derselben Maschine, absolut konstant. Hätten wir hier aufgehört, stünde in diesem Artikel: „Ein SSH Tunnel kostet 40 ms pro Anfrage.“ Das wäre falsch gewesen.
Wir haben dieselbe Messung mit zwei anderen Diensten wiederholt:
| Dienst | Direkt | Durch den Tunnel |
|---|---|---|
| Minimaler Socket-Server (Antwort in einem Stück) | 0,6–0,9 ms | 0,7–1,1 ms |
| Echter Node.js-Dienst auf dem Server | 0,8–1,3 ms | 1,0–1,8 ms |
Bei beiden kostet der Tunnel einige Zehntel Millisekunden, nicht 40. Der Unterschied: Der Python-Testserver (http.server, HTTP/1.0) schickt Kopfzeilen und Inhalt in getrennten Schreibvorgängen. Wir haben die Ursache nicht bis ins letzte Paket verfolgt. Das Muster passt aber genau auf ein bekanntes TCP-Zusammenspiel: Der Nagle-Algorithmus hält ein kleines Paket zurück, bis das vorherige bestätigt ist, und die Gegenseite wartet mit der Bestätigung (Delayed ACK). Unter Linux beträgt diese Mindestwartezeit 40 Millisekunden. Die SSH-Verbindung ist ein zusätzlicher TCP-Abschnitt, auf dem dieses Zusammenspiel auftreten kann.
Die praktische Lehre: Wenn ein Dienst durch einen Tunnel plötzlich auffällig träge wirkt, und zwar mit einer verdächtig runden, konstanten Verzögerung um 40 ms, liegt das fast nie an der Verschlüsselung. Miss mit einem zweiten Dienst gegen, bevor du den Tunnel verantwortlich machst.
Den Tunnel dauerhaft betreiben
Ein Tunnel auf der Kommandozeile stirbt, wenn die Verbindung abreißt: WLAN-Wechsel, Router-Neustart, Server-Reboot. Für alles, was dauerhaft laufen soll, brauchst du zwei Dinge: Lebenszeichen, damit SSH eine tote Verbindung überhaupt bemerkt, und einen Aufpasser, der neu startet.

Lebenszeichen
Ohne Einstellung schickt SSH keine Lebenszeichen. Unser produktiver Server steht auf clientaliveinterval 0, und auch der Client fragt standardmäßig nicht nach. Eine Verbindung, deren Gegenstelle still verschwunden ist, kann so sehr lange als „verbunden“ gelten, und dein Tunnel nimmt Verbindungen an, die nirgendwo ankommen. Auf der Clientseite reichen zwei Optionen:
ServerAliveInterval 15
ServerAliveCountMax 3
Damit fragt der Client alle 15 Sekunden nach, und nach drei unbeantworteten Anfragen (also etwa 45 Sekunden) gibt er auf und beendet sich. Erst dieses Beenden macht einen Neustart möglich.
Neustart mit systemd
Früher war dafür autossh das Standardwerkzeug. Es funktioniert noch, ist aber auf modernen Systemen überflüssig, weil systemd genau das kann. Auf unserem Server ist autossh nicht einmal installiert, und wir haben es nie vermisst. Eine Unit-Datei /etc/systemd/system/db-tunnel.service:
[Unit]
Description=SSH Tunnel zur Datenbank
After=network-online.target
Wants=network-online.target
[Service]
User=tunnel
ExecStart=/usr/bin/ssh -N \
-o ExitOnForwardFailure=yes \
-o ServerAliveInterval=15 \
-o ServerAliveCountMax=3 \
-o BatchMode=yes \
-i /home/tunnel/.ssh/id_ed25519 \
-L 127.0.0.1:15432:127.0.0.1:5432 \
tunnel@db.example.com
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Dann:
sudo systemctl daemon-reload
sudo systemctl enable --now db-tunnel
systemctl status db-tunnel
Drei Optionen sind hier keine Dekoration. ExitOnForwardFailure macht aus einem belegten Port einen Neustart statt eines stillen Halbzustands (Falle 1). ServerAlive* macht aus einer toten Leitung einen Neustart. BatchMode=yes verhindert, dass SSH nach einem Passwort fragt und dann ewig auf eine Eingabe wartet, die nie kommt. Kein -f: systemd will den Prozess im Vordergrund behalten. Wie Unit-Dateien im Detail funktionieren, steht in unserer Anleitung systemd-Service erstellen.
Beim allerersten Start muss der Host-Schlüssel des Zielservers bereits in known_hosts des Tunnel-Benutzers stehen. Verbinde dich also einmal von Hand als dieser Benutzer und bestätige den Fingerabdruck. StrictHostKeyChecking=no als Abkürzung ist keine gute Idee: Damit würde der Tunnel auch zu einem untergeschobenen Server aufgebaut.
Tunnel bequem in der ~/.ssh/config
Lange Tunnel-Befehle tippt niemand gern zweimal. In der ~/.ssh/config bekommt jeder Tunnel einen Namen:
Host db-tunnel
HostName server.example.com
User fabian
IdentityFile ~/.ssh/id_ed25519
LocalForward 15432 127.0.0.1:5432
LocalForward 19090 127.0.0.1:9090
ExitOnForwardFailure yes
ServerAliveInterval 15
ServerAliveCountMax 3
SessionType none
Danach reicht ssh db-tunnel. LocalForward entspricht -L, RemoteForward entspricht -R, DynamicForward entspricht -D. SessionType none ist die Config-Schreibweise für -N (ab OpenSSH 8.7, ältere Versionen nehmen -N auf der Kommandozeile).
Tunnel über einen Sprungserver
Liegt das Ziel hinter einem Bastion-Host, kombinierst du den Tunnel mit ProxyJump:
ssh -J user@bastion -L 15432:127.0.0.1:5432 user@intern
Dein Client baut erst die Verbindung zum Bastion-Host auf und von dort eine zweite, ebenfalls Ende-zu-Ende verschlüsselte Verbindung zu intern. Der Tunnel endet auf intern, und dein privater Schlüssel verlässt deinen Rechner nie. Das ist dem Agent-Forwarding mit -A klar vorzuziehen, aus den Gründen, die wir in Was ist SSH? beschreiben.
Tunnel nachträglich hinzufügen und sauber beenden
Zwei kaum bekannte Werkzeuge machen den Alltag leichter. In einer laufenden interaktiven Sitzung öffnet die Tastenfolge Enter, ~, C eine kleine Kommandozeile, in der du mit -L 8080:127.0.0.1:80 einen Tunnel nachträglich hinzufügst, ohne neu zu verbinden. Seit OpenSSH 9.2 ist diese Kommandozeile standardmäßig abgeschaltet. Du schaltest sie mit EnableEscapeCommandline yes in der ~/.ssh/config wieder ein.
Für Skripte gibt es Steuerverbindungen:
ssh -f -N -M -S ~/.ssh/tunnel.sock -L 15432:127.0.0.1:5432 user@server
# ... arbeiten ...
ssh -S ~/.ssh/tunnel.sock -O exit user@server
-M -S legt einen Steuer-Socket an, und -O exit beendet genau diesen Tunnel, ohne dass du nach Prozessnummern suchen musst. Mit -O check fragst du, ob er noch lebt.
Sicherheit: Was ein SSH Tunnel für Angreifer bedeutet
Bis hier ging es darum, was du mit einem Tunnel machen kannst. Jetzt die Kehrseite: Das kann jeder, der sich auf deinem Server anmelden darf.
Mit dem Ubuntu-Standard AllowTcpForwarding yes und PermitOpen any darf jeder SSH-Benutzer Tunnel zu beliebigen Zielen bauen, die der Server erreicht. Ein Benutzer, dem du nur Dateiübertragung geben wolltest, kann damit deine lokal gebundene Datenbank, Redis-Instanzen oder interne Admin-Oberflächen erreichen. Und über -D kann er deinen Server als Proxy für beliebigen Verkehr ins Internet nutzen, der dann mit deiner Server-IP auftritt.
Das ist kein Grund zur Panik, aber einer zum Nachdenken. Drei Stellschrauben:
1. Tunnel nur dort erlauben, wo sie gebraucht werden. Global ausschalten und gezielt freigeben:
AllowTcpForwarding no
Match User admin
AllowTcpForwarding yes
2. Schlüssel einschränken. In ~/.ssh/authorized_keys kannst du vor jeden Schlüssel Optionen schreiben. Ein Schlüssel nur für einen bestimmten Datenbanktunnel:
restrict,port-forwarding,permitopen="127.0.0.1:5432" ssh-ed25519 AAAA... backup-tunnel
restrict schaltet alles ab (Terminal, Agent, X11, Forwarding), port-forwarding erlaubt Tunnel wieder, permitopen beschränkt sie auf genau ein Ziel. Wird dieser Schlüssel gestohlen, kann der Dieb genau diesen einen Tunnel bauen, sonst nichts. Wie du Schlüssel überhaupt sauber erzeugst, zeigt unsere Anleitung SSH-Key erstellen.
3. Anmeldungen hart machen. Ein Tunnel ist nur so sicher wie die Anmeldung, auf der er sitzt. Passwort-Anmeldung aus, Schlüssel an, und gegen das Grundrauschen der Bots hilft Fail2ban. In unserem SSH-Grundlagenartikel haben wir gezählt: 26.753 fehlgeschlagene Anmeldeversuche in 22 Tagen auf einem einzigen Server.
Noch ein Hinweis zu Firmennetzen: Ein SSH Tunnel kann Firewall-Regeln umgehen. Das ist der Sinn der Sache, wenn es deine eigene Infrastruktur ist. In fremden Netzen kann es gegen Nutzungsregeln verstoßen. Prüf vorher, ob du das darfst.
SSH Tunnel unter Windows und macOS
Die Befehle in diesem Artikel funktionieren unverändert auf macOS und auf Windows 10 und 11. Beide bringen OpenSSH mit, unter Windows im Terminal oder in PowerShell als ssh.exe. Die Konfigurationsdatei liegt unter Windows in C:\Users\<Name>\.ssh\config.
Wer lieber klickt, findet in PuTTY unter „Connection → SSH → Tunnels“ dieselben drei Varianten: „Local“ für -L, „Remote“ für -R, „Dynamic“ für -D. Viele Datenbankprogramme (DBeaver, DataGrip, TablePlus, pgAdmin) haben außerdem eine eingebaute Option „SSH-Tunnel“. Die baut intern genau einen -L-Tunnel auf, du trägst nur Server, Benutzer und Schlüssel ein.
SSH Tunnel, VPN oder Cloudflare Tunnel?
Ein SSH Tunnel ist nicht für jeden Zweck das beste Werkzeug. Unsere Faustregeln aus dem eigenen Betrieb:
| Ziel | Werkzeug |
|---|---|
| Einzelnen Dienst auf einem Server kurz erreichen (Datenbank, Dashboard) | SSH Tunnel mit -L |
| Dauerhafte Verbindung zwischen zwei Servern für einen Dienst | SSH Tunnel per systemd oder WireGuard |
| Ganze Geräte oder Netze verbinden, auch UDP | VPN (WireGuard, Tailscale) |
| Lokalen Dienst öffentlich im Internet zeigen | Reverse-Tunnel mit -R auf eigenem Server, oder ein Dienst wie Cloudflare Tunnel |
| Browserverkehr im fremden WLAN schützen | -D (SOCKS) |
Der große Vorteil des SSH Tunnels: Er braucht nichts Neues. Kein Dienst, kein Konto, kein zusätzlicher offener Port. Der Nachteil: Er transportiert nur TCP, er hängt an einer einzelnen Verbindung und er verschlüsselt auf einem Kern. Für alles, was viele Geräte oder viel Bandbreite betrifft, ist ein VPN robuster.
Ein Wort zu -w und PermitTunnel: OpenSSH kann auch echte Netzwerkschnittstellen (tun-Geräte) durchtunneln, also ein kleines VPN auf Schicht 3. Das ist auf unserem Server mit permittunnel no abgeschaltet, braucht Root auf beiden Seiten und ist in der Praxis WireGuard klar unterlegen. Wir erwähnen es nur der Vollständigkeit halber.
Häufige Fehler und was sie bedeuten
bind [127.0.0.1]:8080: Address already in use: Der lokale Eingangsport ist belegt, von einem früheren Tunnel oder einem anderen Programm. ss -tlnp | grep 8080 zeigt, wer ihn hat. Und: Ohne ExitOnForwardFailure=yes läuft SSH trotzdem weiter (Falle 1).
channel 2: open failed: connect failed: Connection refused: Der Tunnel steht, aber am Ziel lauscht nichts. Prüf auf der Gegenseite, ob der Dienst läuft und auf der Adresse lauscht, die du im Ziel angegeben hast. Klassiker: Der Dienst lauscht auf 127.0.0.1, du hast im Ziel die öffentliche IP eingetragen, oder umgekehrt.
channel 2: open failed: administratively prohibited: open failed: Der Server verbietet den Tunnel. Ursache ist AllowTcpForwarding no, ein permitopen, das dieses Ziel nicht enthält, oder ein restrict am Schlüssel.
-R funktioniert nur vom Server selbst aus, nicht von außen: GatewayPorts steht auf no (Falle 2). Kein Fehler, keine Warnung, nur ein Loopback-Port.
Der Tunnel funktioniert, aber nach einer Weile kommt nichts mehr an: Die Verbindung ist tot, ohne dass SSH es bemerkt hat. ServerAliveInterval setzen.
FAQ zum SSH Tunnel
Ist ein SSH Tunnel sicher?
Die Strecke zwischen deinem Client und dem SSH-Server ist mit derselben Verschlüsselung geschützt wie jede SSH-Sitzung. Nicht verschlüsselt ist das Stück vom SSH-Server zum Ziel, wenn das Ziel ein anderer Rechner ist. Die größeren Risiken liegen woanders: ein Tunnel-Eingang, der versehentlich auf 0.0.0.0 lauscht, und Benutzer, die Tunnel bauen dürfen, obwohl sie es nicht sollen. Beides kannst du mit den Einstellungen aus dem Sicherheitsabschnitt abstellen.
Ist ein SSH Tunnel ein VPN?
Nein. Ein SSH Tunnel leitet einzelne TCP-Ports oder (mit -D) den Verkehr einzelner Anwendungen weiter. Ein VPN verbindet ganze Netzwerkschnittstellen, inklusive UDP und aller Programme. Für einen Dienst ist der Tunnel einfacher, für ganze Geräte ist ein VPN besser.
Was ist der Unterschied zwischen -L und -R?
Bei -L liegt der Eingang auf deinem Rechner, und das Ziel wird vom Server aus erreicht. Du holst einen Dienst vom Server zu dir. Bei -R liegt der Eingang auf dem Server, und das Ziel wird von deinem Rechner aus erreicht. Du stellst einen Dienst von dir auf dem Server bereit.
Wie halte ich einen SSH Tunnel dauerhaft offen?
Mit ServerAliveInterval und ServerAliveCountMax, damit tote Verbindungen erkannt werden, plus einem Aufpasser, der neu startet. Am einfachsten ist ein systemd-Dienst mit Restart=always, wie oben gezeigt. autossh geht auch, ist aber nicht mehr nötig.
Wie beende ich einen SSH Tunnel?
Interaktiv mit Strg+C oder exit. Hintergrund-Tunnel (-f) beendest du mit kill auf die Prozessnummer (pgrep -af "ssh -N"), oder sauberer über eine Steuerverbindung mit ssh -S <socket> -O exit. systemd-Tunnel stoppst du mit systemctl stop.
Macht ein SSH Tunnel meine Verbindung langsamer?
Kaum. In unserer Messung kostete der Tunnel auf derselben Maschine einige Zehntel Millisekunden pro Anfrage, und der Durchsatz lag bei rund 800 MB/s, also deutlich über jeder üblichen Internetleitung. Eine auffällig konstante Verzögerung um 40 ms ist meist ein TCP-Effekt im Zusammenspiel mit dem Dienst, nicht die Verschlüsselung.
Brauche ich Root-Rechte für einen SSH Tunnel?
Für -L und -D auf Ports ab 1024 nicht. Ports unter 1024 (etwa 80 oder 443) als Eingang brauchen auf der jeweiligen Seite Root-Rechte. Für -R auf öffentlichen Adressen muss jemand mit Root-Rechten GatewayPorts in der sshd_config ändern.
Kann ich mehrere Tunnel gleichzeitig nutzen?
Ja, in einer einzigen Verbindung. -L, -R und -D lassen sich beliebig oft und beliebig gemischt angeben, und in der ~/.ssh/config schreibst du einfach mehrere LocalForward-Zeilen untereinander.
Fazit: Der SSH Tunnel ist das unterschätzteste Werkzeug auf deinem Server
Ein SSH Tunnel löst ein Problem, das sonst gern mit einem offenen Port gelöst wird, und damit falsch. Datenbanken, Dashboards und Admin-Oberflächen können auf 127.0.0.1 bleiben, und du erreichst sie trotzdem in Sekunden. Kein neuer Dienst, kein neues Konto, nur die Verbindung, die du ohnehin hast.
Aus unseren Tests nehmen wir zwei Regeln mit, die in kaum einer Anleitung stehen. Erstens: ExitOnForwardFailure=yes gehört in jeden Tunnel, der nicht direkt vor deinen Augen läuft, sonst steht die Verbindung und der Tunnel fehlt. Zweitens: Ein -R auf 0.0.0.0 wird vom Server bei GatewayPorts no stillschweigend auf Loopback umgebogen. Kein Fehler, nur ein Tunnel, der von außen nicht erreichbar ist. Wer das einmal weiß, spart sich eine Stunde Fehlersuche an der falschen Stelle.
Wenn du gerade einen Server einrichtest, ist der nächste sinnvolle Schritt unsere Anleitung Linux-Server einrichten. Dort zeigen wir, wie Firewall, SSH-Härtung und Dienste so zusammenspielen, dass Tunnel der normale Weg zu internen Diensten werden und offene Ports die Ausnahme.