Mit PostgreSQL oder MySQL über einen SSH-Tunnel verbinden

Führen Sie ssh -N -L 5433:127.0.0.1:5432 user@server aus und verbinden Sie Ihren Client dann mit 127.0.0.1 auf Port 5433: psql -h 127.0.0.1 -p 5433 -U app appdb. Für MySQL nehmen Sie -L 3307:127.0.0.1:3306 und mysql -h 127.0.0.1 -P 3307 -u app -p appdb. Die Datenbank kann für das Internet geschlossen bleiben und nur auf ihrer eigenen Loopback-Adresse lauschen.

Sie möchten nicht für jeden Tunnel ein Terminal offen halten? Wardterm speichert den Forward und öffnet ihn, wenn Sie ihn brauchen. Zeigen

Aktualisiert am . Ausgeführt mit OpenSSH 10.3 und OpenSSH 10.0 als Clients, über OpenSSH 10.0 auf Debian 13, zu PostgreSQL 17 und MySQL 8.0, die nur auf 127.0.0.1 lauschen, einschließlich verify-full mit host und hostaddr sowie des reinen Tunnel-Benutzers.

PostgreSQL

Lassen Sie in einem Terminal den Tunnel laufen:

ssh -N -o ExitOnForwardFailure=yes -L 127.0.0.1:5433:127.0.0.1:5432 user@server

In einem anderen:

psql -h 127.0.0.1 -p 5433 -U app appdb

Port 5433 ist ein beliebiger freier Port auf Ihrem Rechner. Ein anderer Port als 5432 vermeidet einen Konflikt mit einem lokalen PostgreSQL. Ein Connection-String funktioniert genauso, in jedem Tool und jeder Bibliothek:

postgresql://app@127.0.0.1:5433/appdb

In einem grafischen Datenbank-Tool tragen Sie Host 127.0.0.1 und Port 5433 mit dem Datenbank-Login ein und lassen die SSH-Option des Tools aus, denn diese Aufgabe übernimmt schon der Tunnel.

Auf dem Server sieht es so aus, als käme die Verbindung vom Server selbst. In unserem Test lieferte inet_client_addr() den Wert 127.0.0.1. Daher braucht pg_hba.conf eine Zeile dafür, die die meisten Standarddateien bereits enthalten:

host  appdb  app  127.0.0.1/32  scram-sha-256

MySQL

ssh -N -o ExitOnForwardFailure=yes -L 127.0.0.1:3307:127.0.0.1:3306 user@server
mysql -h 127.0.0.1 -P 3307 -u app -p appdb

Schreiben Sie 127.0.0.1 und nicht localhost. Der MySQL-Client versteht localhost als Aufforderung, den lokalen Unix-Socket zu nutzen, ignoriert den Port und scheitert:

ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

Auch --protocol=TCP behebt das, falls Sie unbedingt localhost tippen wollen.

Das Konto muss von der Adresse aus erlaubt sein, die der Server sieht. Ein als 'app'@'localhost' angelegter Benutzer kann über TCP scheitern. Beim offiziellen MySQL-Image, in dem skip_name_resolve aktiv ist, kam Access denied for user 'app'@'127.0.0.1'. Legen Sie das Konto für diese Adresse an:

CREATE USER 'app'@'127.0.0.1' IDENTIFIED BY '...';
GRANT ALL ON appdb.* TO 'app'@'127.0.0.1';

Welche Adresse wohin gehört

-L 127.0.0.1:5433:127.0.0.1:5432
   |         |    |         |
   |         |    |         +- port of the database
   |         |    +- database address, as seen by the SSH server
   |         +- port on your machine
   +- address to listen on, on your machine
  • Lassen Sie die erste Adresse bei 127.0.0.1, damit niemand sonst in Ihrem Netzwerk Ihren Tunnel nutzen kann.
  • Die Datenbank kann ausschließlich auf Loopback lauschen: listen_addresses = 'localhost' in PostgreSQL, bind-address = 127.0.0.1 in MySQL. Beides funktionierte in unserem Test, und der Tunnel braucht keine weitere Öffnung.
  • Bevorzugen Sie auch als Ziel 127.0.0.1 statt localhost. Der SSH-Server löst den Namen auf und probiert womöglich zuerst IPv6.

Remote- und dynamische Forwards folgen demselben Muster, siehe SSH-Tunnel: lokal, remote und dynamisch.

Liegt die Datenbank auf einem anderen Rechner, zum Beispiel als Managed Service, nennen Sie sie als Ziel. Die Datenbank sieht dann die private Adresse des SSH-Servers, und genau die gehört in ihre Zugriffsliste:

ssh -N -L 127.0.0.1:5433:db.internal.example.com:5432 user@server

TLS bleibt wichtig

SSH verschlüsselt von Ihrem Rechner bis zum SSH-Server. Liegt die Datenbank auf einem anderen Rechner, ist die letzte Teilstrecke nicht mehr von dieser Verschlüsselung abgedeckt, nutzen Sie daher zusätzlich TLS auf der Datenbankverbindung.

Die Zertifikatsprüfung braucht besondere Sorgfalt, denn Ihr Client glaubt, er verbinde sich mit 127.0.0.1, und das steht nicht im Zertifikat:

psql "host=127.0.0.1 port=5433 user=app dbname=appdb sslmode=verify-full sslrootcert=ca.crt"
... server certificate for "db.example.com" does not match host name "127.0.0.1"

Setzen Sie in PostgreSQL host auf den Namen im Zertifikat und hostaddr auf den Tunnel. Das bestand in unserem Test mit vollständiger Prüfung:

psql "host=db.example.com hostaddr=127.0.0.1 port=5433 user=app dbname=appdb sslmode=verify-full sslrootcert=ca.crt"

MySQL hat keine entsprechende Einstellung. --ssl-mode=VERIFY_IDENTITY prüft den Namen, den Sie getippt haben, Sie bräuchten also ein Zertifikat, das 127.0.0.1 enthält, oder einen Namen in Ihrer hosts-Datei, der darauf zeigt. Einfaches REQUIRED verschlüsselt, ohne zu prüfen, wer antwortet.

Ein Benutzer, der nur diesen Tunnel öffnen kann

Eine Person oder App, die nur die Datenbank braucht, sollte keine Shell auf dem Server bekommen. Legen Sie einen Benutzer ohne Shell an:

sudo useradd -m -s /usr/sbin/nologin dbtunnel

Installieren Sie dessen öffentlichen Schlüssel in /home/dbtunnel/.ssh/authorized_keys. Fügen Sie dann eine Drop-in-Datei hinzu, zum Beispiel /etc/ssh/sshd_config.d/10-dbtunnel.conf. Match endet am Ende dieser Datei und wirkt sich daher nicht auf die Einstellungen danach aus:

Match User dbtunnel
  AllowTcpForwarding local
  PermitOpen 127.0.0.1:5432
  X11Forwarding no
  AllowAgentForwarding no
  PermitTTY no
  ForceCommand /usr/sbin/nologin

Führen Sie sudo sshd -t aus, laden Sie neu und verbinden Sie sich:

ssh -N -L 127.0.0.1:5433:127.0.0.1:5432 dbtunnel@server

Das haben wir mit dieser Konfiguration beobachtet:

  • Der obige Tunnel funktioniert, und psql verbindet sich darüber.
  • Port 3306 oder jedes andere Ziel wird abgelehnt.
  • -R scheitert mit remote port forwarding failed for listen port.
  • Eine normale Anmeldung gibt This account is currently not available. aus.
  • Auch -L 5433:localhost:5432 wird abgelehnt. PermitOpen vergleicht Text, ohne Namensauflösung, der Client muss also genau 127.0.0.1 anfordern.

Um stattdessen einen einzelnen Schlüssel zu beschränken, können Sie Optionen in authorized_keys verwenden, in einer Zeile:

restrict,port-forwarding,permitopen="127.0.0.1:5432" ssh-ed25519 AAAA... dbtunnel

Das blockiert ebenfalls eine Shell und andere Ziele. In unserem Test erlaubte es dennoch -R, das Öffnen eines Ports auf dem Server bleibt also möglich. Der Match-Block mit AllowTcpForwarding local schließt das, und wir bevorzugen ihn aus diesem Grund.

Dieser Benutzer kommt nur bis zur Anmeldung der Datenbank. Das Datenbankkonto sollte trotzdem ein eigenes Passwort oder Zertifikat haben und nur die nötigen Rechte.

Fragen

Warum lokal Port 5433 statt 5432?

Jeder freie Port geht. Ein anderer vermeidet den Konflikt mit einem PostgreSQL, das schon auf Ihrem Rechner läuft.

Warum ignoriert mysql den Port, wenn ich localhost schreibe?

Der MySQL-Client versteht localhost als lokalen Unix-Socket. Verwenden Sie 127.0.0.1 oder ergänzen Sie --protocol=TCP.

Ist der Datenbankverkehr im SSH-Tunnel verschlüsselt?

Von Ihrem Rechner bis zum SSH-Server ja. Läuft die Datenbank auf einem anderen Rechner, liegt die letzte Strecke außerhalb des Tunnels. Nutzen Sie dann zusätzlich TLS.

Quellen

Mehr zu Tunnel und Jump-Hosts

Alle Anleitungen