
SSL/TLS für PostgreSQL mit Docker einrichten

Ziel
Wenn du PostgreSQL produktiv betreiben möchtest, ist die Einrichtung von Transport Layer Security (TLS) ein Muss, um Man-in-the-Middle-Angriffe zu verhindern. In dieser Schritt-für-Schritt-Anleitung zeige ich dir, wie du dich mit deiner eigenen lokalen Zertifizierungsstelle sicher mit deiner Datenbank verbindest. Wenn du eine ausführliche Erklärung aller Einstellungen benötigst, schau dir die offizielle Dokumentation zur TLS-Einrichtung in PostgreSQL an.
In diesem Beispiel erlauben wir NUR verschlüsselte Verbindungen und setzen sslmode
auf verify-full
, was die strengste Verbindungseinstellung in PostgreSQL ist.
Voraussetzungen
Du benötigst Docker auf deinem Rechner. Wenn du einen Mac oder Windows verwendest, installiere Docker Desktop und stelle sicher, dass es im Hintergrund läuft. Überprüfe, ob Docker läuft, indem du docker ps
im Terminal eingibst. Du solltest eine Liste laufender Container sehen oder zumindest keine Fehlermeldung erhalten, wenn Docker läuft.
PostgreSQL mit TLS via OpenSSL und Docker einrichten
Schritt 1: Zertifizierungsstelle (CA) generieren
Hinweis: Alle im Folgenden generierten Zertifikatsdateien (CA, Server und Client) sollten als Geheimnisse behandelt werden. Füge sie nicht zu deinem Git-Repository hinzu, logge sie nicht und baue sie nicht in dein Docker-Image ein (nur zur Laufzeit als Umgebungsvariablen verwenden). Füge sie ggf. deiner
.gitignore
hinzu.
Zuerst erstellen wir einen Root-CA-Schlüssel und ein Zertifikat, das unsere Server- und Client-Zertifikate signiert. Du kannst den Namen ändern, wenn du möchtest. Unsere CA ist 365 Tage gültig und muss danach erneuert werden.
openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 365 -out rootCA.crt -subj "/CN=MyLocalCA"
Schritt 2: Server-Zertifikate erstellen
Erzeuge einen Schlüssel und eine CSR (Certificate Signing Request) und signiere sie mit der CA:
Wichtig: Hier musst du den Domainnamen angeben, auf dem deine PostgreSQL-Instanz läuft. Im Beispiel verwende ich
localhost
, aber passe den Hostnamen an, falls du einen anderen verwendest.
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr -subj "/CN=localhost"
openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out server.crt -days 365 -sha256
Schritt 3: Client-Zertifikate erstellen
Dieses Zertifikat wird vom Client zur Authentifizierung verwendet. Nur Zertifikate, die von derselben CA wie das Server-Zertifikat signiert wurden, funktionieren.
Die Erstellung erfolgt analog zum Server. Als Common Name habe ich den Postgres-User gewählt:
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr -subj "/CN=postgres"
openssl x509 -req -in client.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out client.crt -days 365 -sha256
Schritt 4: Init-Skript zur Aktivierung von TLS in PostgreSQL
Lege eine neue Datei im Projekt-Root an und nenne sie ssl-config.sh
. Das Skript ist für die Verwendung in einem Docker-Container auf Basis eines PostgreSQL-Images gedacht.
Es kopiert die Server-Zertifikate in den Container und nimmt alle nötigen Einstellungen vor, um TLS mit sslmode verify-full
zu erzwingen.
Wenn du unsichere Verbindungen erlauben willst, kannst du den Abschnitt "Force SSL" im Skript weglassen oder sslmode anpassen.
#!/bin/bash
# Zertifikatsdateien hinzufügen
echo "$SERVER_CRT" > /var/lib/postgresql/server.crt
echo "$SERVER_KEY" > /var/lib/postgresql/server.key
echo "$ROOT_CA_CRT" > /var/lib/postgresql/rootCA.crt
# Berechtigungen setzen
chmod 600 /var/lib/postgresql/server.* /var/lib/postgresql/rootCA.crt
chown postgres:postgres /var/lib/postgresql/server.* /var/lib/postgresql/rootCA.crt
# PostgreSQL für SSL konfigurieren
echo "ssl = on" >> /var/lib/postgresql/data/postgresql.conf
echo "ssl_cert_file = '/var/lib/postgresql/server.crt'" >> /var/lib/postgresql/data/postgresql.conf
echo "ssl_key_file = '/var/lib/postgresql/server.key'" >> /var/lib/postgresql/data/postgresql.conf
echo "ssl_ca_file = '/var/lib/postgresql/rootCA.crt'" >> /var/lib/postgresql/data/postgresql.conf
# SSL erzwingen
echo "hostssl all all all cert clientcert=verify-full" > /var/lib/postgresql/data/pg_hba.conf
Das Skript benötigt 3 Umgebungsvariablen:
SERVER_CRT
SERVER_KEY
ROOT_CA_CRT
Der Inhalt dieser Variablen ist der Klartext der drei Zertifikatsdateien server.crt
, server.key
und rootCA.crt
.
Schritt 5: Docker-Image mit aktiviertem TLS bauen
Wir erstellen ein einfaches Dockerfile
auf Basis von postgres:17.5
und kopieren unser Init-Skript in das Verzeichnis docker-entrypoint-initdb.d
:
FROM postgres:17.5
COPY ssl-config.sh /docker-entrypoint-initdb.d/ssl-config.sh
Schritt 6: Verbindung zur Instanz herstellen
Um zu prüfen, ob alles funktioniert, bauen und starten wir das Image lokal und versuchen, uns zu verbinden. Wir nutzen den psql
-Client. Falls nicht installiert, installiere ihn z.B. mit brew install libpq
auf MacOS.
Hinweis: Dieser lokale Test funktioniert nur, wenn du zuvor den Common Name deines Server-Zertifikats auf
localhost
gesetzt hast. Andernfalls musst du ein neues Server-Zertifikat mitlocalhost
als Common Name erstellen.
Image bauen:
docker build -t postgres-tls .
Image starten:
docker run \
-e POSTGRES_PASSWORD=secret \
-e SERVER_CRT="$(cat server.crt)" \
-e SERVER_KEY="$(cat server.key)" \
-e ROOT_CA_CRT="$(cat rootCA.crt)" \
-p 5432:5432 --name postgres-tls postgres-tls
Achte darauf, die korrekten Pfade zu deinen Zertifikatsdateien anzugeben, falls du sie verschoben hast.
Verbindung testen:
# Damit solltest du in die PostgreSQL-Shell gelangen:
psql "host=localhost dbname=postgres user=postgres sslmode=verify-full sslrootcert=rootCA.crt sslcert=client.crt sslkey=client.key"
# Das sollte fehlschlagen, da wir TLS erzwingen:
psql "host=localhost dbname=postgres user=postgres sslmode=disable"
Wichtig: Führe dies im selben Verzeichnis aus, in dem deine Zertifikate liegen, oder passe die Pfade an.
Deployment
Um die Datenbank über das Internet verfügbar zu machen, deployen wir sie auf Sliplane.
- Erstelle ein GitHub-Repository mit den beiden oben beschriebenen Dateien:
ssl-config.sh
aus Schritt 4 undDockerfile
aus Schritt 5. Ein Beispiel-Repo zum Forken findest du hier: PostgreSQL mit TLS Repository - Melde dich bei Sliplane mit deinem GitHub-Account an
- Erstelle ein neues Projekt und klicke auf "Deploy Service"
- Erstelle einen neuen Server, auf dem deine PostgreSQL-Instanz laufen soll – du kannst mit dem Basis-Server starten und später skalieren
- Wähle "Repository" als Deploy-Quelle
- Wähle dein PostgreSQL-Repository aus der Liste
Falls das Repository nicht angezeigt wird, klicke zuerst auf "Configure Repository Access", um Sliplane Zugriff zu gewähren
- Stelle das Protokoll auf TCP und klicke auf "Deploy". So sollten die Einstellungen aussehen:
Hinweis: Dieser Deploy wird absichtlich fehlschlagen! Wir haben die Umgebungsvariablen noch nicht gesetzt. Wir müssen zuerst deployen, um eine
sliplane.app
-Domain zu erhalten, die wir für das Zertifikat benötigen. Alternativ kannst du die Umgebungsvariablen schon ausfüllen, wenn du eine eigene Domain im Zertifikat verwendest und diese später an den Service anhängst.
- Erstelle die Zertifikate gemäß Schritt 1-3. Verwende dabei die
sliplane.app
-Domain deines Services als Common Name für das Server-Zertifikat. Du findest sie in den Service-Einstellungen unter "Public Domain".
Hinweis: Dein Service muss öffentlich sein, damit er eine Domain erhält (siehe Screenshot oben).
- Nachdem du die Zertifikate für deine öffentliche Postgres-Instanz erstellt hast, füge sie als Umgebungsvariablen hinzu, zusammen mit einem
POSTGRES_PASSWORD
deiner Wahl.
Deine Umgebungsvariablen sollten enthalten:
- POSTGRES_PASSWORD
- SERVER_CRT
- SERVER_KEY
- ROOT_CA_CRT
Nach dem Speichern der Umgebungsvariablen wird ein neuer Deploy ausgelöst, der nun erfolgreich sein sollte.
Das war's! Du hast jetzt Zugriff auf eine PostgreSQL-Instanz über eine sichere Verbindung. Teste sie mit:
psql "host=YOUR_APP.sliplane.app dbname=postgres user=postgres sslmode=verify-full sslrootcert=rootCA.crt sslcert=client.crt sslkey=client.key"
Hinweis: Ersetze die sliplane.app-Domain durch die, die dir zugewiesen wurde und die im Zertifikat verwendet wird.
Wenn dir dieses Tutorial gefallen hat, freue ich mich über Kommentare, Likes und Shares.
Danke!
Lukas