Eigene PKI im Homelab nutzen: So geht’s

Heute geht es mal um die Verschlüsselung in meinem Homelab. In der heutigen digitalisierten Welt sollte Sicherheit für jeden wichtig sein. Besonders in einem Homelab, wo verschiedene Dienste und IoT-Geräte interagieren, ist es essenziell, sensible Daten vor neugierigen Blicken zu schützen. In diesem Artikel möchte ich meine Erfahrungen und Strategien teilen, wie ich in meinem Homelab eine sichere und vertrauenswürdige Umgebung geschaffen habe, indem ich SSL-Zertifikate und eine eigene PKI (Public Key Infrastructure) implementiert habe.

In diesem Artikel geht es weniger darum wie Verschlüsselung technisch funktioniert. Es geht um das Ausstellen von Zertifikaten was für manche Anwendungen eine Voraussetzung ist, damit diese die Daten überhaupt verschlüsseln. Es geht um die Vertrauensbasis in deinem Netzwerk.

Ein Start mit XCA

Die Idee, eine Root-CA (Certificate Authority) zu erstellen, fand ich schon immer sinnvoll. Früher hatte ich darauf keinen so großen Wert gelegt. Mit der wachsenden Anzahl an IoT Geräten (alles elektronische was so kommuniziert) zuhause, wollte ich aber irgendwann sicherstellen, dass der gesamte Netzwerkverkehr (insbesondere zwischen sensiblen Diensten und IoT-Geräten) verschlüsselt ist.

Anfangs habe ich ausschließlich mit dem sehr guten Tool XCA meine Root-CA verwaltet und eine Sub-CA erstellt, die von der Root signiert wird. Dadurch konnte ich die Verwaltung der Zertifikate zentralisieren und sicherstellen, dass nur vertrauenswürdige Zertifikate in meinem Netzwerk verwendet werden.

Das Tool XCA mit einer Liste meiner Zertifikate

XCA nutze ich immer noch um meine Root-Zertfikate sicher abzulegen. Ich hatte auch überlegt ob ich hier ein Hashicorp Vault nutze.

LabCA (ACME)

Die manuelle Verwaltung meiner Zertifikate ging mir auf die Nerven; daher habe ich LabCA in mein Setup integriert. Mit Hilfe von step-ca zur Automatisierung der CA-Verwaltung bietet LabCA eine benutzerfreundliche Weboberfläche, die die Verwaltung erheblich vereinfacht. Ein Feature von LabCA ist die Möglichkeit, den Zertifikatsabruf über ACME (Automatic Certificate Management Environment) zu automatisieren. Damit werden die Zertifikate für unter anderem Proxmox, Webserver und PostgreSQL-Server automatisch aktualisiert.

Ein Blick in den LabCA Admin, wenn ein Benutzer eingeloggt ist. Man sieht eine Übersicht der letzten Aktivitäten und der Anzahl an Zertifikaten.

Ein weiterer Pluspunkt von LabCA ist die Überwachung der Ablaufzeiten der Zertifikate. So werde ich über E-Mail informiert, wenn eines meiner Zertifikate kurz vor dem Ablauf steht. Dies gibt mir die Sicherheit, dass ich jedes Fehlverhalten mitbekomme und meine Dienste ohne Unterbrechung für die Nutzer (meine Familie und mich) zur Verfügung stehen.

Certbot und Nginx

In einigen meiner Setups habe ich den Nginx Server zur SSL-Terminierung eingesetzt. In meinem neuen Setup greife ich jedoch auf Certbot zurück, um die Zertifikate von meiner eigenen PKI zu beziehen. Diese Zertifikate sind standardmäßig 30 Tage gültig, und Certbot kümmert sich um die rechtzeitige Erneuerung. Durch das Montieren des Verzeichnisses /etc/letsencrypt im Read-Only-Modus in meine Docker-Container kann ich die neu bezogenen Zertifikate nahtlos integrieren.
Wichtig ist, dass man das gesamte /etc/letsencrypt einbindet damit die Symlinks die Certbot erstellt auch funktionieren.

Hier ist als vollständige Beispiel die Konfiguration meines NocoDB Servers:

services:
  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      
      # Hier ist das SSL Zertifkat vom Certbot einbefunden.      
      - /etc/letsencrypt:/etc/letsencrypt:ro
    depends_on:
      - nocodb

  nocodb:
    container_name: nocodb
    image: nocodb/nocodb:0.258.10
    env_file: .env
    restart: always
    ports:
      - 80:8080
    volumes:
      - type: volume
        source: data
        target: /usr/app/data

volumes:
  data:

Die Nginx Konfiguration ist hier sehr einfach gehalten:

events {}

http {
  server {
    listen 443 ssl;
    server_name nocodb.muench.lan;

    # Hier ist das SSL Zertifikat vom Certbot eingebunden
    ssl_certificate /etc/letsencrypt/live/nocodb.muench.lan/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/nocodb.muench.lan/privkey.pem;

    client_max_body_size 200M;

    location / {
      proxy_pass http://nocodb:8080;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
    }
  }
}

Um sicherzustellen, dass alles reibungslos funktioniert, habe ich die pre_hook und post_hook von Certbot genutzt, um Skripte auszuführen, die relevante Docker-Container neu starten. Diese Automatisierung erspart mir viel manuelle Arbeit und sorgt dafür, dass ich mich auf wichtigere Aufgaben in meinem Homelab konzentrieren kann.

Vorteile des Caddy Servers

Besonders bei neuen Webserver-Setups kommt auch immer wieder der Caddy Server ins Spiel. Caddy unterstützt ACME out-of-the-box, was die Einrichtung und Verwaltung von SSL-Zertifikaten extrem vereinfacht. Ohne die Notwendigkeit von Certbot kann ich Zeit sparen und mich gleichzeitig um die Sicherheit meiner Anwendungen kümmern.

Hier ist ein Beispiel die Konfguration meines pgadmin Servers:

{
    email christian@muench.dev
    acme_ca https://pki.muench.lan/directory
    acme_ca_root /usr/local/share/ca-certificates/muench-lan-root-ca.crt
}

pgadmin4.muench.lan {
    tls {
        ca_root /usr/local/share/ca-certificates/muench-lan-root-ca.crt
    }

    reverse_proxy pgadmin:80
}

Da hier kein Certbot im Spiel ist und der Caddyserver dies übernimmt, muss nur das Root-CA Zertifikat eingebunden werden. Da ich das schon im LXC Container liegen habe, wurde dies einfach in den Container gemounted.

Verschlüsselung zwischen Hosts und Containern

Obwohl ich theoretisch auch meinen Traefik Server so konfigurieren könnte, dass dieser die interne PKI nutzt, habe ich beschlossen, dies zunächst zu vermeiden. Ich will nicht den gesamten internen Traffic über Traefik routen, sondern stattdessen eine direkte Verschlüsselung zwischen den einzelnen Servern und LXC-Containern sicherstellen. Jedes System besitzt daher eigene Zertifikate, was die Sicherheit auf verschiedene Weise erhöht.

Generell muss man überall darauf achten, dass man die Connections innerhalb von Anwendungen auch mit SSL/TLS etc. konfiguriert.
Sehr oft ist das allerdings nicht gerade benutzerfreundlich gemacht. So ignorieren einige Anwendungen auch gerne das Root-CA.
Wenn es geht sollte man aber aber versuche die Verbindungen zu Datenbanken oder anderen Geräten über verschlüsselte Wege einzurichten.

Die Server konfigurieren

Damit jeder Server in meinen Netzwerk der neuen Root-CA vertraut, muss das Root Zertifikat in den jeweiligen Trust-Stores hinzugefügt werden.
Ich setze für meine Fedora Workstation Linux ein und die Proxmox Server nutzen Debian. Der Rest bei mir sind LXC Container (im Proxmox Server) die Ubuntu nutzen.

Das Root-CA Zertifikat habe ich auf einen Server gelegt der für alle Server erreichbar ist.
Das Root-CA kann man als Benutzer bequem (ohne Login) herunterladen.

Fedora

# Download Zertifikat (URL im Beispiel ist angepasst)
curl -O  https://example.com/acme/muench_lan_2025_root.crt

sudo cp muench_lan_2025_root.crt /etc/pki/ca-trust/source/anchors
sudo update-ca-trust

Debian

(bei mir z.B. Proxmox Server)

mkdir -p /usr/local/share/ca-certificates/extra
cd /usr/local/share/ca-certificates/extra

# Download Zertifikat (URL im Beispiel ist angepasst)
curl -O  https://example.com/acme/muench_lan_2025_root.crt

update-ca-certificates

Ubuntu

Meine LXC Container basieren alle auf Ubuntu 24.04.
Manuell kann man die Konfiguration so vornehmen:

sudo -s
mkdir -p /usr/local/share/ca-certificates/extra
cd /usr/local/share/ca-certificates/extra

# Download Zertifikat (url im Beispiel angepasst)
curl -O  https://example.com/acme/muench_lan_2025_root.crt

sudo update-ca-certificates

Für die Server habe ich die Installation der Root-CA per Ansible automatisiert.

Das sieht ungefähr so aus:

---
- name: Install muench-dev root ca playbook
  hosts: all
  become: true
  gather_facts: true
  vars:
    is_ubuntu: ""
  tasks:    
    - name: Install pki.muench.lan CA
      when: is_ubuntu
      block:
      - name: Download root-ca.pem
        ansible.builtin.get_url:
          url: https://example.com/acme/muench_lan_2025_root.crt
          dest: /usr/local/share/ca-certificates/muench-lan-root-ca.crt
          validate_certs: false
          mode: "0644"
          
        register: pki_muench_lan_ca_download

      - name: Update CA trust
        ansible.builtin.command: "update-ca-certificates"
        when: pki_muench_lan_ca_download.changed

Anwendungen konfigurieren

Proxmox selbst

In Proxmox habe ich zudem meinen ACME-Server als Account hinterlegt, sodass Proxmox ebenfalls selbstständig die Zertifikate verwaltet. Das letztliche Ziel ist klar: Ich möchte die Automatisierung maximieren und die Eingriffe von meiner Seite minimieren.
Leider kann man aus der Proxmox UI nicht direkt den eigenen ACME Server hinterlegen. Über die Kommandozeile funktioniert dies aber.

# PVE Node
pvenode acme account register muench_lan "christian@muench.dev" --directory https://pki.muench.lan/directory

# Für dem PBS
proxmox-backup-manager acme account register default "christian@muench.dev" --directory https://pki.muench.lan/directory

Danach kann man in Proxmox sehr einfach das Zertifikat holen lassen.

Proxmox UI - Zertifikat konfigurieren

Postgres Server

In meinem Docker-Setup habe ich die von Certbot geholten Zertifikate/Schlüssel über die Parameter ssl_cert_file und ssl_key_file eingebunden.

services:
  postgres:
    container_name: postgres
    image: postgres:16
    ports:
      - 5432:5432
    command:
      - postgres

      # .... weitere Konfiguration

      - -c      
      - ssl_cert_file=/certs/live/postgres.muench.lan/fullchain.pem

      - -c      
      - ssl_key_file=/certs/live/postgres.muench.lan/privkey.pem
    restart: unless-stopped
    env_file:
      - postgres.env
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./certs:/certs

Leider passen die Rechte im Container nicht der Postgres User (im Container) darf die Zertifikate nicht lesen.

Aus diesem Grund habe ich in meiner Certbot Renewal Config /etc/letsencrypt/renewal/postgres.muench.lan.conf Datei folgendes eingeführt:

[renewalparams]
post_hook="/usr/local/bin/move-certificate.sh"

Das Script /usr/local/bin/move-certificate.sh verschiebt dann die Zertifikate in das Verzeichnis, was im Postgres Container gemounted wurde und setzt die Rechte für den Postgres User damit dieser zugreifen darf.
Danach wird die Datenbank (alle 30 Tage einmal) durchgestartet.

Ein Problem was ich entdeckt hatte war, dass Postgres keine Zertifikate aus Symlinks auflöst. Deswegen musste ich beim Kopieren der Dateien die Symlinks auflösen lassen.

#!/bin/bash

CERT_SRC=/etc/letsencrypt;
CERT_DEST=/srv/docker/postgres/certs;

# Resolve symlink -> Postgres cannot work with linked certs
cp -rL $CERT_SRC/* $CERT_DEST/;

# Postgres runs as user 1000 with group 100
chown -R 999 $CERT_DEST;

chmod -R 700 $CERT_DEST;


cd /srv/docker/postgres;
docker compose restart postgres;

Node-RED und n8n (Node-JS Anwendungen)

Damit Node-RED meine Root-CA nutzt, habe ich das zuvor über Ansible installierte Root-CA .crt File einfach in den Docker Container gemounted und der NodeJS Anwendung über die Env Variable NODE_EXTRA_CA_CERTS gesagt, dass es die Datei nutzen soll.

services:
  node-red:
    container_name: node-red
    # ... other configs
    
    # Node sagen, dass es die Root-CA aus der Datei nutzt
    environment:
      - NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/muench-lan-root-ca.crt
    volumes:
      - ./data:/data:rw
      
      # Zertifikat mounten
      - /usr/local/share/ca-certificates/muench-lan-root-ca.crt:/usr/local/share/ca-certificates/muench-lan-root-ca.crt:ro

Bei n8n sieht das fast genauso aus da es auch eine Node Anwendung ist.
Die Variable NODE_EXTRA_CA_CERTS kann hier auch gesetzt werden.

Testen muss sein

Im Browser lässt sich sehr einfach prüfen ob das Zertifikat genutzt wird.
Wenn alles passt, sollte man die ganze Kette aus Root-CA -> Sub-CA -> TLS Zertifikat sehen können.
Zerifikatskette im Browser

Über OpenSSL lässt sich auch schnell prüfen ob ein Zertifikat passt.
Für meinen Postgres Server kann ich das so machen:

echo "" | openssl s_client -starttls postgres -connect postgres.muench.lan:5432 -showcerts

Dabei sollte sowas wie Verify return code: 0 (ok) angezeigt werden.

Wichtig ist, dass man die Verbindungen prüft da einige Anwendungen nicht einen Fehler anzeigen sondern einen Fallback auf die nichtverschlüsselte Verbindung machen.

Fazit

Zusammenfassend lässt sich sagen, dass im Homelab die Implementierung von SSL und einer eigenen PKI nicht nur die Sicherheit erhöht, sondern auch den Alltag erheblich erleichtert. Durch die Automatisierung der Zertifikatsverwaltung mit Tools wie LabCA und Certbot, sowie der flexiblen Nutzung moderner Serverlösungen, habe ich eine Infrastruktur geschaffen, die sowohl sicher als auch benutzerfreundlich ist.

Was man bei mir sieht ist, dass so ein Homelab wirklich lebst. Es ist immer in der Umgestaltung. Aber das ist es ja was es ausmacht. Man kann viel Testen und dann vielleicht auch im beruflichen Leben nutzen.