Der EIngang eines Strassentunnels mit englischsprachigen Hinweisschildern

Gastbeitrag: Transparenter Reverse-Proxy mit rathole

Homelab-Exposure Deluxe: End-to-End TLS via Nginx Stream & Rathole

In Ermangelung einer eigenen Macroblogging-Seite (I blame ‚Impressumspflicht‘ ) hier ein Gastbeitrag im Bytespeicher-Blog ohne Vereinsbezug, aber mit Technikhilfe für den ambitionierten Homelab-Bastler.

Ich stand vor einem klassischen Problem: Mein Homelab-Server im eigenen Keller bietet Diensten an, von denen ich einige auch ausserhalb meines Heimnetzes nutzen möchte, aber mein heimischer Internetanschluss sitzt hinter einem NAT und hat eine dynamische IP. Erschwerend kommt hinzu, dass ich oft aus Netzen agiere, deren restriktive Firewalls kein DynDNS, keine Residential-IPs und erst recht keine Non-Standard-Ports zulassen.

Die gängige Lösung wäre ein Reverse Proxy auf einem VPS (mit fester Domain und statischer IP), der den Traffic per VPN (z. B. Wireguard) in den heimischen Keller tunnelt. Aber mein Threat Model ist etwas spezieller: Da der VPS meine primäre Angriffsfläche im Netz darstellt, betrachte ich ihn als potenziell kompromittierbar. Ich möchte nicht, dass der VPS meinen HTTPS-Traffic entschlüsseln kann oder uneingeschränkten Zugriff auf mein internes Netzwerk erhält, sollte er jemals fallen.

Hier ist mein Setup für eine völlig transparente Anbindung via Nginx Stream-Module und Rathole.


Mein Konzept: SNI-Preread & Rathole

Mein Ziel ist "Transparenz". Der VPS soll lediglich auf Layer 4 (TCP) entscheiden, wohin die Reise geht, ohne das TLS-Paket jemals auszupacken.

  1. Nginx Stream: Ich nutze das ssl_preread-Modul auf dem VPS, um den Hostnamen (SNI) aus dem verschlüsselten Paket zu lesen.
  2. Rathole: Dieser in Rust geschriebene Tunnel schiebt die Pakete durch das NAT direkt zu meinem heimischen Server.
  3. Local Termination: Mein SSL-Private-Key verlässt niemals mein Haus. Der VPS sieht nur verschlüsselten Datensalat.

Schritt 1: Der Tunnel mit Rathole

Rathole realisiert einen schmalen Tunnel zwischen VPS und Homeserver. Ich definiere auf dem VPS (Server) den Port, an dem auf eingehende Verbindungen vom Client gewartet wird, und die Ports für die jeweiligen Dienste. Dabei kann ich beliebige Eigangsports auf dem VPS auf beliebige Ausgangsports auf dem Heimserver mappen.

Die Server-Konfiguration (server.toml) auf dem VPS:
Hier lege ich fest, welche Dienste über welche Ports erreichbar gemacht werden:

# server.toml
[server]
bind_addr = "0.0.0.0:2333" # Port für den Tunnel zum Rathole-Client

[server.services.nextcloud]
token = "bar" # eindeutiges security token pro Dienst
bind_addr = "0.0.0.0:9443" # Traffic am port 9443 auf dem VPS soll durch den Tunnel

[server.services.paperless]
token = "foo"
bind_addr = "0.0.0.0:8443"

[server.services.paperless_80]  # später mehr dazu
token = "foo80"
bind_addr = "0.0.0.0:880"

[server.services.nextcloud_80]
token = "bar80"
bind_addr = "0.0.0.0:980"

Und die Gegenseite (client.toml) auf dem Homeserver:
Hier wird die Verbindung zum VPS aufgebaut und die Services mit lokalen Endpunkten verbunden.

# client.toml
[client]
remote_addr = "example.com:2333" # Die Adresse des VPS

[client.services.paperless]
token = "foo" # Das gleiche Token wie beim Server für diesen Dienst
local_addr = "127.0.0.1:8060" # Der lokale Port, der die Anfrage beantworten soll

[client.services.nextcloud]
token = "bar"
local_addr = "127.0.0.1:443"

[client.services.paperless_80]  # Dazu später mehr
token = "foo80"
local_addr = "127.0.0.1:8050"

[client.services.nextcloud_80]
token = "bar80"
local_addr = "127.0.0.1:80"

Damit die Tunnel nach einem Neustart automatisch stehen, nutze ich entsprechende Systemd-Services für den Rathole-Server und den Client.

[Unit]
Description=Rathole Server Service
After=network.target

[Service]
Type=simple
Restart=on-failure
RestartSec=5s
LimitNOFILE=1048576

# with root
ExecStart=/usr/bin/rathole -s /etc/rathole/server.toml
# without root
# ExecStart=%h/.local/bin/rathole -s %h/.local/etc/rathole/rathole.toml

[Install]
WantedBy=multi-user.target

Schritt 2: Nginx als Layer-4 Dispatcher

In der nginx.conf auf meinem VPS arbeite ich im stream-Block. Anstatt den Traffic zu entschlüsseln, nutze ich map, um basierend auf dem Hostnamen ($ssl_preread_server_name) das richtige Backend zu wählen.

stream {
    map $ssl_preread_server_name $backend_name {
        example.com           local_nginx;       # Eigene Landingpage auf dem VPS
        paperless.example.com rathole_paperless; # Ab ins Heimnetz
        nextcloud.example.com rathole_nextcloud;
    }

    upstream rathole_nextcloud {
        server 127.0.0.1:9443; # Rathole Port
    }

    server {
        listen 443;
        proxy_pass $backend_name;
        ssl_preread on; # Das ist die Magie!
    }
}

Schritt 3: Die HTTP-Weiterleitung für Certbot

Ein wichtiger Aspekt ist die Zertifikats-Erneuerung via Certbot auf meinem heimischen Server. Damit die HTTP-01-Challenge funktioniert, muss Port 80 ebenfalls erreichbar sein. Da ich auf Port 80 keine SNI-Informationen habe, löse ich das im klassischen http-Block meiner nginx.conf auf dem VPS:

http {
    upstream backend_nextcloud {
        server 127.0.0.1:980; # Rathole Port für HTTP-Weiterleitung
    }

    server {
        listen 80;
        server_name nextcloud.example.com;

        location / {
            proxy_pass http://backend_nextcloud;
            proxy_set_header Host $host;
        } 
    }
}

Ich leite Port 80 parallel über zusätzliche Rathole-Dienste (z. B. nextcloud_80 auf Port 980, s.o.) nach Hause weiter. Selbst wenn mein heimischer Webserver am Ende alles auf HTTPS umleitet, ist dieser Pfad essenziell, damit Certbot sie temporär überschreiben und seine Zertifikate von außen validieren kann.


Warum ich diesen Weg gehe (Vorteile & Sicherheit)

Ich habe mich bewusst gegen eine klassische Wireguard-Lösung entschieden. Ein großer Vorteil dieses Rathole-Setups ist die Abschottung: Bei einer VPN-Lösung müsste ich auf meinem heimischen Server komplexe Firewall-Regeln (iptables/nftables) pflegen, um sicherzustellen, dass der VPS wirklich nur die Ports erreicht, die er soll. Da Rathole explizit nur die in der client.toml definierten lokalen Adressen anspricht, fällt dieser administrative Aufwand für die Abschottung komplett weg.

Zudem erreiche ich mein Ziel der Zero-Knowledge-Infrastruktur:

  • Keine Entschlüsselung: Mein VPS sieht niemals meine privaten Schlüssel oder den Inhalt der Datenpakete.

  • Minimale Angriffsfläche: Der Tunnel ist punktgenau auf die Dienste beschränkt.

  • Bypass von Firewalls: Da nach außen hin alles über Port 443 läuft und die Verbindung von innen nach außen aufgebaut wird, ist mein Setup für restriktive Netze praktisch unsichtbar.

  • Da der Tunnel aktiv vom Homeserver aus zur statischen IP des VPS aufgebaut wird, benötige ich keinen zusätzlichen DynDNS Dienst.

  • Ich kann weiterhin auch Dienste auf dem VPS anbieten und es fügt sich nahtlos in die Konfiguration ein

Es ist vielleicht nicht das gängigste Szenario, aber für mein Sicherheitsbedürfnis ist es die ideale Lösung, um mein Homelab ins ‚böse‘ Internet zu bringen.

Happy Hacking!


Photo by Daniel Jerez on unsplash

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert