SSH-Tunneling und Port-Forwarding in der Praxis
$
/etc
Linux · SSH · Netzwerk · DevOps
SSH-Tunneling und Port-Forwarding
in der Praxis

SSH-Tunneling verwandelt eine einzige verschlüsselte Verbindung in einen sicheren Kanal für beliebige TCP-Dienste, die sonst nur lokal auf einem Server erreichbar sind. Dieser Artikel erklärt lokale, entfernte und dynamische Portweiterleitung an konkreten Beispielen, zeigt den Tunnel zu einer nur serverintern erreichbaren Datenbank, den Einsatz von autossh für dauerhaft stabile Verbindungen und die Sicherheitsrisiken offen gelassener Tunnel.

13 Min. Lesezeit ssh -L · ssh -R · ssh -D · autossh OpenSSH · SOCKS5 · Linux

1. Was SSH-Tunneling technisch bedeutet

SSH wird meist auf die Remote-Shell reduziert, doch das zugrunde liegende Protokoll ist ein generischer, verschlüsselter Transportkanal für beliebige TCP-Verbindungen. Über eine einzige authentifizierte Verbindung auf Port 22 kann der SSH-Client mehrere logische Kanäle multiplexen, darunter auch Kanäle vom Typ direct-tcpip und forwarded-tcpip. Genau diese Kanaltypen sind die technische Grundlage von Port-Forwarding: Statt Daten für eine Shell-Sitzung zu übertragen, transportiert der Kanal rohe TCP-Nutzdaten einer beliebigen Anwendung, unsichtbar für alles zwischen Client und Server.

Daraus ergeben sich drei fundamental unterschiedliche Weiterleitungsrichtungen. Lokale Portweiterleitung (-L) holt einen entfernten Dienst zu dir. Entfernte Portweiterleitung (-R) macht einen lokalen Dienst auf dem entfernten Server sichtbar. Dynamische Portweiterleitung (-D) baut aus derselben Verbindung einen vollwertigen SOCKS5-Proxy. Alle drei nutzen dieselbe verschlüsselte Verbindung und dieselbe Authentifizierung, unterscheiden sich aber grundlegend darin, wer auf wen zugreifen darf.

2. Lokale Portweiterleitung mit ssh -L

Das lokale Forwarding ist die häufigste Variante: Ein Port auf dem eigenen Rechner wird transparent zu einem Zielport hinter dem SSH-Server durchgereicht. Die Syntax ssh -L lokaler_port:zielhost:zielport user@sshserver öffnet auf dem lokalen Rechner einen Listener. Jede Verbindung dorthin wird über den SSH-Server zum zielhost:zielport weitergeleitet, wobei zielhost aus Sicht des SSH-Servers aufgelöst wird, nicht aus Sicht des Client-Rechners. Das ist der Punkt, an dem viele Einsteiger stolpern: localhost als Zielhost bezieht sich auf den Server, nicht auf den eigenen Laptop.

Typischer Einsatz ist der Zugriff auf ein internes Admin-Interface, das nur auf dem Serverhost selbst lauscht, etwa ein Monitoring-Dashboard auf Port 9090. Mit -N unterdrückt man die Remote-Shell komplett und nutzt die Verbindung ausschließlich als Tunnel, mit -f schickt man den Prozess in den Hintergrund. Diese Kombination ist die Grundlage fast aller produktiven Tunnel-Setups.


# Local forwarding: reach an internal dashboard bound to the server itself
$ ssh -L 9090:127.0.0.1:9090 -N -f deploy@app-server.example.com

# Verify: local listener now exists on the client
$ ss -tlnp | grep 9090
LISTEN 0 128 127.0.0.1:9090 0.0.0.0:*  users:(("ssh",pid=41210,fd=6))

# Access the internal dashboard through the tunnel
$ curl -s http://127.0.0.1:9090/metrics | head -3

# Close the backgrounded tunnel again
$ pkill -f "9090:127.0.0.1:9090"

3. Entfernte Portweiterleitung mit ssh -R

Die entfernte Portweiterleitung kehrt die Richtung um: Ein Dienst, der lokal auf deinem Rechner läuft, wird über den SSH-Server nach außen sichtbar gemacht. Die Syntax ssh -R remote_port:localhost:lokaler_port user@sshserver öffnet auf dem entfernten Server einen Listener, der Verbindungen zurück zum lokalen Zielport tunnelt. Standardmäßig bindet der Server diesen Listener nur an 127.0.0.1, sodass er ausschließlich vom Server selbst erreichbar ist, nicht von außen. Erst die Server-Option GatewayPorts ändert dieses Verhalten.

Praktisch relevant ist das etwa, um einen lokal laufenden Webhook-Empfänger während der Entwicklung kurzfristig über einen erreichbaren Server testen zu lassen, ohne einen öffentlichen Tunneldienst eines Drittanbieters zu nutzen. Ein weiteres Szenario: Support-Zugriff auf ein Gerät hinter einer restriktiven Firewall, das selbst eine ausgehende Verbindung zu einem erreichbaren Jump-Host aufbaut und darüber eine Rückverbindung anbietet, ganz ohne eingehende Firewall-Regel am Zielgerät.

Konkret sieht der Aufbau so aus: ssh -R 8443:localhost:3000 -N -f deploy@jump-host.example.com öffnet auf dem Jump-Host einen an 127.0.0.1:8443 gebundenen Listener, sichtbar mit ss -tlnp | grep 8443 direkt auf dem Server. Beim Support-Szenario dreht sich die Rolle um: Das Gerät selbst führt ssh -R 2222:localhost:22 -N -f support@jump-host.example.com aus und macht seine eigene SSH-Shell über den Jump-Host per ssh -p 2222 localhost erreichbar, ganz ohne eingehende Verbindung von außen zum Gerät selbst.

4. Dynamische Portweiterleitung und SOCKS-Proxy mit ssh -D

Bei mehr als ein oder zwei Zielen wird das Anlegen einzelner lokaler Weiterleitungen schnell unübersichtlich. Die dynamische Portweiterleitung löst dieses Problem, indem sie aus der SSH-Verbindung einen vollwertigen SOCKS5-Proxy macht. Der Befehl ssh -D 1080 -N -f user@sshserver öffnet lokal Port 1080 als SOCKS5-Endpunkt. Jede Anwendung, die SOCKS5 unterstützt, kann darüber beliebige Ziele erreichen, die vom SSH-Server aus routbar sind, ohne dass für jedes Ziel ein eigener -L-Eintrag nötig wäre.

In der Praxis konfiguriert man Browser, curl oder ganze Kommandozeilen-Toolchains über proxychains so, dass ihr Traffic durch den SOCKS-Proxy läuft. Damit lässt sich zum Beispiel ein komplettes internes Subnetz erreichen, das nur vom Jump-Host aus geroutet wird, etwa um mehrere interne Webanwendungen im selben VPC-Segment zu testen, ohne für jede einzeln einen Tunnel aufzubauen. Wichtig: DNS-Auflösung läuft standardmäßig lokal, nicht über den Proxy, sofern man nicht explizit --socks5-hostname oder die entsprechende Browser-Einstellung für Remote-DNS aktiviert.


# Dynamic forwarding: turn the SSH connection into a SOCKS5 proxy
$ ssh -D 1080 -N -f deploy@jump-host.example.com

# Route a single curl request through the proxy, resolving DNS remotely
$ curl --socks5-hostname 127.0.0.1:1080 http://internal-app.local/health

# Route an arbitrary CLI tool through the same proxy via proxychains
$ echo "socks5 127.0.0.1 1080" >> /etc/proxychains.conf
$ proxychains mysql -h db-internal.local -u reporting -p

# Verify the proxy listener is up
$ ss -tlnp | grep 1080

5. Praxisfall: Tunnel zu einer nur serverintern erreichbaren Datenbank

Ein sehr häufiges Szenario in Magento- und PHP-Deployments: MySQL oder MariaDB läuft auf dem Applikationsserver, ist aber bewusst nur an 127.0.0.1 gebunden, um jeden direkten Zugriff aus dem Netz von vornherein auszuschließen. Für Reporting, Migrations-Debugging oder den Anschluss eines GUI-Clients wie DBeaver oder Sequel Ace braucht man dennoch Zugriff von der eigenen Maschine aus. Genau hierfür ist lokale Portweiterleitung das richtige Werkzeug, ohne die Datenbank jemals über das öffentliche Netz erreichbar zu machen.

Der Tunnel ssh -L 3307:127.0.0.1:3306 -N -f deploy@app-server.example.com bindet lokal Port 3307, weil 3306 auf dem eigenen Rechner oft bereits von einer lokalen MySQL-Instanz belegt ist. Der GUI-Client verbindet sich dann schlicht gegen 127.0.0.1:3307, als wäre die Datenbank lokal installiert. Der SSH-Server sieht dabei ausschließlich verschlüsselten SSH-Traffic auf Port 22, niemals unverschlüsselten MySQL-Traffic auf der Leitung nach außen, was diesen Weg auch für sensible Produktivdaten deutlich sicherer macht als ein direkt exponierter Datenbank-Port.


#!/usr/bin/env bash
# db-tunnel.sh: open a persistent local tunnel to the app server's database
set -euo pipefail

REMOTE_HOST="app-server.example.com"
REMOTE_USER="deploy"
LOCAL_PORT=3307
DB_PORT=3306

# Skip if a tunnel on this port is already running
if ss -tln | grep -q ":${LOCAL_PORT} "; then
  echo "[INFO] Tunnel on port ${LOCAL_PORT} already active"
  exit 0
fi

autossh -M 0 -f -N \
  -o "ServerAliveInterval=30" \
  -o "ServerAliveCountMax=3" \
  -o "ExitOnForwardFailure=yes" \
  -L "${LOCAL_PORT}:127.0.0.1:${DB_PORT}" \
  "${REMOTE_USER}@${REMOTE_HOST}"

echo "[OK] Database reachable at 127.0.0.1:${LOCAL_PORT}"
mysql -h 127.0.0.1 -P "${LOCAL_PORT}" -u reporting -p -e "SELECT 1"

6. autossh für dauerhaft stabile Tunnel

Ein manuell gestarteter Tunnel überlebt weder einen WLAN-Wechsel noch einen kurzen Netzwerkausfall noch einen Server-Neustart. Genau dieses Problem löst autossh: Es startet den eigentlichen ssh-Prozess, überwacht die Verbindung fortlaufend und baut sie bei einem Abbruch automatisch neu auf, ohne manuelles Eingreifen. In Kombination mit den Optionen ServerAliveInterval und ServerAliveCountMax erkennt SSH selbst tote Verbindungen zuverlässig, noch bevor ein TCP-Timeout überhaupt greift, und signalisiert autossh den nötigen Neustart.

Die Option -M 0 deaktiviert den älteren Monitoring-Port-Mechanismus von autossh zugunsten der SSH-eigenen Keepalive-Prüfung, was in modernen OpenSSH-Versionen der robustere Ansatz ist und keinen zusätzlichen offenen Port erfordert. Für dauerhaften Betrieb gehört ein solcher Tunnel in eine systemd-Unit statt in eine manuell gestartete Hintergrundsitzung: Nur so übersteht er auch einen Server-Reboot automatisch und lässt sich mit systemctl status wie jeder andere Dienst überwachen und im Fehlerfall per Journal nachvollziehen.

7. SSH-Config: Tunnel dauerhaft und wiederverwendbar konfigurieren

Lange ssh-Befehlszeilen mit mehreren Optionen sind fehleranfällig und schlecht wiederverwendbar. Die Datei ~/.ssh/config löst das, indem sie Host-Aliase mit vorkonfigurierten Optionen definiert. Ein Host-Block kann LocalForward, RemoteForward und DynamicForward direkt enthalten, sodass ein einfaches ssh db-tunnel genügt, um exakt denselben Tunnel wie in einer langen Kommandozeile aufzubauen. Das reduziert Tippfehler und macht Tunnel-Definitionen versionierbar, wenn die Config-Datei Teil eines dotfiles-Repositories ist.

Die Option ExitOnForwardFailure=yes ist für Automatisierung entscheidend: Ohne sie startet SSH auch dann erfolgreich, wenn die Portweiterleitung selbst fehlschlägt, etwa weil der lokale Port bereits belegt ist, und Skripte gehen fälschlich von einem funktionierenden Tunnel aus. Mit dieser Option bricht die Verbindung sofort ab, wenn das Forwarding nicht zustande kommt, was Fehler in CI-Pipelines und Deploy-Skripten früh sichtbar macht statt sie stillschweigend zu verschlucken.


# ~/.ssh/config: reusable tunnel definitions as host aliases

Host db-tunnel
  HostName app-server.example.com
  User deploy
  LocalForward 3307 127.0.0.1:3306
  ServerAliveInterval 30
  ServerAliveCountMax 3
  ExitOnForwardFailure yes
  # no shell needed, tunnel only
  RequestTTY no
  RemoteCommand none

Host webhook-relay
  HostName jump-host.example.com
  User deploy
  RemoteForward 8443 localhost:3000
  ExitOnForwardFailure yes

Host socks-proxy
  HostName jump-host.example.com
  User deploy
  DynamicForward 1080
  ServerAliveInterval 30

8. Sicherheitsaspekte offen gelassener Tunnel

Ein SSH-Tunnel ist nur so sicher wie der zugrunde liegende Schlüssel und die Konfiguration des Servers. Standardmäßig ist die Server-Option GatewayPorts no aktiv, das heißt entfernte Weiterleitungen binden nur an 127.0.0.1 und sind nicht von außen erreichbar. Wird diese Option versehentlich auf yes gesetzt, macht jede entfernte Weiterleitung den weitergeleiteten Dienst potenziell für das gesamte Netz sichtbar, in dem der SSH-Server steht, oft ohne dass der Nutzer, der den Tunnel öffnet, sich dessen bewusst ist.

Ein zweites, häufig übersehenes Risiko: Wer nur Tunnel-Zugriff gewähren will, sollte einen eigenen SSH-Key mit eingeschränkten Optionen in authorized_keys hinterlegen, etwa command="echo restricted",no-pty,permitopen="127.0.0.1:3306". So kann der Key ausschließlich zum genannten Ziel tunneln, aber keine Shell öffnen und auch keine beliebigen anderen Ports erreichen. Vergessene, dauerhaft offene Tunnel sind zudem ein Monitoring-Thema: Mit ss -tlnp | grep ssh und regelmäßiger Auswertung lassen sich verwaiste Tunnel identifizieren, bevor sie zu unbemerkten Zugriffspfaden in produktive Systeme werden.


{
  "audit_run": "2026-07-12T07:40:00+02:00",
  "host": "app-server.example.com",
  "open_ssh_forward_listeners": [
    {
      "local_bind": "127.0.0.1:8443",
      "type": "remote_forward",
      "owning_pid": 9021,
      "connected_since": "2026-06-02T11:12:04+02:00",
      "flag": "stale_over_30_days"
    },
    {
      "local_bind": "127.0.0.1:3307",
      "type": "local_forward",
      "owning_pid": 41210,
      "connected_since": "2026-07-12T06:58:11+02:00",
      "flag": "active_expected"
    }
  ],
  "recommended_action": "terminate stale_over_30_days entries and rotate the associated key"
}

9. SSH-Tunneling-Varianten im direkten Vergleich

Ob lokale, entfernte oder dynamische Weiterleitung die richtige Wahl ist, hängt von der Zugriffsrichtung und der Anzahl der Ziele ab. Genauso wichtig ist die Wahl zwischen einem fragilen Ad-hoc-Setup und einem robusten, dauerhaften Pattern, gerade bei Datenbank-Tunneln, die im Alltag ständig gebraucht werden.

Szenario Unsicherer / fragiler Ansatz Empfohlenes SSH-Tunneling-Pattern Vorteil
Datenbank-Zugriff aus der Ferne Datenbank-Port an 0.0.0.0 binden und in der Firewall öffnen ssh -L Tunnel zu Datenbank auf 127.0.0.1 Datenbank bleibt von außen komplett unerreichbar
Tunnel dauerhaft am Laufen halten Terminal mit ssh -L manuell offen lassen autossh mit ServerAliveInterval als systemd-Service Automatischer Reconnect nach Netzwerkausfall oder Reboot
Internen Webservice testen Firewall-Regel für öffentlichen Zugriff anlegen ssh -R Reverse-Tunnel nur für den Testzeitraum Kein dauerhaft offener Port am Zielsystem
Mehrere interne Anwendungen erreichen Für jede Anwendung einen eigenen Tunnel oder VPN-Client ssh -D dynamischer SOCKS5-Proxy für alle Ziele Ein einziger Tunnel für beliebig viele Ziele
Tunnel-Zugriff einschränken Vollzugriff über den normalen persönlichen SSH-Key Eigener Key mit permitopen und no-pty in authorized_keys Key kann nicht für Shell-Zugriff missbraucht werden

Mironsoft

Server-Zugriff, Netzwerksicherheit und Deployment-Infrastruktur

Sicheren Zugriff auf interne Dienste ohne offene Ports?

Wir richten SSH-Tunnel-Setups mit autossh und systemd ein, schränken Tunnel-Keys auf das Nötigste ein und sorgen dafür, dass eure Datenbanken und internen Dienste niemals direkt exponiert werden müssen.

Tunnel-Setup

Lokale, entfernte und dynamische Weiterleitung sauber über ~/.ssh/config verwaltet

Persistenz

autossh als systemd-Service, übersteht Reboots und Netzwerkausfälle automatisch

Zugriffshärtung

Eingeschränkte Tunnel-Keys mit permitopen und regelmäßiges Monitoring offener Tunnel

10. Zusammenfassung

SSH-Tunneling und Port-Forwarding lösen drei unterschiedliche Zugriffsprobleme mit derselben verschlüsselten Grundlage. Lokale Weiterleitung (-L) holt einen entfernten Dienst zu dir, etwa eine Datenbank, die bewusst nur an 127.0.0.1 auf dem Server gebunden ist. Entfernte Weiterleitung (-R) macht einen lokalen Dienst über den Server erreichbar, ohne eingehende Firewall-Regeln am eigentlichen Zielgerät. Dynamische Weiterleitung (-D) bündelt beliebig viele Ziele über einen einzigen SOCKS5-Proxy.

Für den produktiven Einsatz reicht ein manuell gestarteter Tunnel nicht aus. autossh mit ServerAliveInterval, verpackt in einer systemd-Unit, hält Tunnel dauerhaft stabil und übersteht Netzwerkausfälle und Reboots automatisch. Genauso wichtig ist die Sicherheitsseite: GatewayPorts no, eingeschränkte Tunnel-Keys mit permitopen und regelmäßiges Monitoring offener Weiterleitungen verhindern, dass vergessene Tunnel zu unbemerkten Zugriffspfaden in produktive Systeme werden.

SSH-Tunneling und Port-Forwarding: Das Wichtigste auf einen Blick

Lokal vs. entfernt

ssh -L holt einen entfernten Dienst zu dir, ssh -R macht einen lokalen Dienst über den Server erreichbar.

Dynamisch mit SOCKS

ssh -D baut einen SOCKS5-Proxy für beliebig viele Ziele, ohne für jedes einen eigenen Tunnel anzulegen.

Datenbank-Tunnel in der Praxis

ssh -L 3307:127.0.0.1:3306 erreicht eine Datenbank, die nur serverintern lauscht, ohne sie je öffentlich zu exponieren.

Persistenz & Sicherheit

autossh als systemd-Service für Stabilität, permitopen-Keys und Monitoring gegen vergessene offene Tunnel.

11. FAQ: SSH-Tunneling und Port-Forwarding

1Unterschied lokale vs. entfernte Weiterleitung?
ssh -L holt einen Dienst hinter dem Server zu dir. ssh -R macht umgekehrt einen lokalen Dienst über den Server erreichbar.
2Wie funktioniert ssh -D?
Öffnet lokal einen SOCKS5-Proxy-Port. Jede SOCKS5-fähige Anwendung erreicht darüber beliebige Ziele hinter dem SSH-Server, ohne separate -L-Einträge.
3Warum stirbt mein Tunnel unbemerkt?
Netzwerkwechsel oder NAT-Timeouts beenden die TCP-Verbindung, ohne dass SSH es sofort merkt. ServerAliveInterval erkennt das aktiv, ein manueller Tunnel baut sich danach aber nicht selbst neu auf.
4Was bringt autossh gegenüber einer eigenen Schleife?
autossh nutzt aktive SSH-Keepalives und startet nur bei echtem Ausfall neu. Eigene Neustart-Schleifen erkennen tote Verbindungen oft erst deutlich verzögert.
5Wie tunnle ich zu einer internen Datenbank?
ssh -L 3307:127.0.0.1:3306 -N -f user@server. Der Client verbindet sich gegen 127.0.0.1:3307, nach außen ist nur verschlüsselter SSH-Traffic sichtbar.
6Wofür steht ssh -N?
Unterdrückt Remote-Befehl beziehungsweise Shell, die Verbindung dient ausschließlich als Tunnel. Nicht zwingend nötig, macht den Zweck aber eindeutig.
7Warum ist GatewayPorts standardmäßig aus?
GatewayPorts no bindet entfernte Weiterleitungen nur an 127.0.0.1 auf dem Server. Ohne diese Einschränkung wäre jeder Reverse-Tunnel im gesamten Servernetz sichtbar.
8Wie finde ich offene Tunnel?
ss -tlnp | grep ssh listet aktive Forward-Listener samt PID auf Client und Server. Regelmäßige Auswertung deckt vergessene Tunnel auf.
9Mehrere Weiterleitungen gleichzeitig möglich?
Ja, mehrere -L-, -R- und -D-Optionen lassen sich in einem Aufruf oder einem Host-Block der ~/.ssh/config kombinieren.
10Ist ein SSH-Tunnel dasselbe wie ein VPN?
Nein. Ein VPN routet meist den gesamten Verkehr auf IP-Ebene um, ein SSH-Tunnel leitet gezielt einzelne Verbindungen oder SOCKS5-Traffic weiter.