HomeLab ohne Portfreigabe veröffentlichen: frp-Tunnel zum VPS mit Caddy
Teil 8 der Reihe Server im Netz: Dienste aus dem Heimnetz mit frp über einen VPS veröffentlichen, ohne Portfreigabe am Router. Mit sicherer frps-Konfiguration, Caddy für HTTPS, gemessener Latenz, Durchsatz und Ausfallverhalten.
Geprüft am 05.11.2026 · für frp 0.72.0, Caddy 2.11.7, nginx 1.31.8
WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Viele Heimanschlüsse lassen sich von außen gar nicht erreichen: DS-Lite, CGNAT beim Mobilfunk oder ein Router, an dem man keine Ports freigeben darf. Und wo es geht, steht das Heimnetz danach offen im Internet. Ein Tunnel dreht die Richtung um. Der Rechner im HomeLab baut selbst eine Verbindung zum VPS auf, und Besucher kommen über diesen Weg herein, ohne dass am Router etwas freigegeben ist. Diese Anleitung richtet dafür frp ein, einen verbreiteten Reverse-Tunnel, und hängt ihn hinter Caddy aus Teil 4, damit der Heimdienst unter einer eigenen Subdomain mit HTTPS erscheint. Dazu kommen Messwerte: was der Umweg an Zeit und Durchsatz kostet und was Besucher sehen, wenn eine Seite ausfällt.
Dies ist Teil 8 der Reihe „Server im Netz“. Als Beispiel dient ein netcup-VPS mit Ubuntu und Docker, auf dem Caddy die Domain sedv-lab.de mit HTTPS ausliefert. Im Heimnetz läuft ein Linux-Rechner mit Docker.
Voraussetzungen
- Ein VPS mit Caddy im Host-Netz wie in Teil 4 und einer Domain, deren Wildcard-Eintrag auf den Server zeigt (Teil 3)
- Ein Rechner im Heimnetz mit Docker und Docker Compose, etwa ein Mini-PC, ein NAS oder eine VM
- Ausgehende Verbindungen vom Heimnetz zum VPS auf TCP-Port 7000
- Der Dienst, den Sie veröffentlichen wollen, im Beispiel ein nginx und der Testdienst
traefik/whoami
So funktioniert der Tunnel
frp besteht aus zwei Programmen. frps läuft auf dem VPS und wartet auf Port 7000 auf Clients. frpc läuft im HomeLab, meldet sich mit einem Token an und hält die Verbindung offen. Kommt beim VPS eine Anfrage für heim.sedv-lab.de an, schickt frps sie durch diese Verbindung nach Hause, frpc reicht sie an den Dienst im Heimnetz weiter, und die Antwort nimmt denselben Weg zurück. Der Router sieht nur eine ausgehende Verbindung. Caddy steht davor und kümmert sich wie bei der Website um Zertifikat, HTTPS und Sicherheits-Header. frps selbst bekommt nur unverschlüsseltes HTTP von Caddy über die Loopback-Schnittstelle.
Das Projekt hat auf GitHub über 110.000 Sterne und wird aktiv gepflegt. Die Docker-Images fatedier/frps und fatedier/frpc kommen vom Entwickler selbst.
Schritt 1: frps auf dem VPS einrichten
Zuerst das Token. Es liegt in einer eigenen Datei, damit es nicht in der Konfiguration steht. Die Container von frp laufen als root und lesen auch Dateien, die nur für root lesbar sind:
mkdir -p /opt/frps && cd /opt/frps
openssl rand -hex 32 > frp-token
chmod 600 frp-token
Die frps.toml:
bindAddr = "0.0.0.0"
bindPort = 7000
proxyBindAddr = "127.0.0.1"
vhostHTTPPort = 8081
transport.tls.force = true
auth.method = "token"
auth.tokenSource.type = "file"
auth.tokenSource.file.path = "/etc/frp/frp-token"
allowPorts = [ { single = 2222 } ]
maxPortsPerClient = 3
webServer.addr = "127.0.0.1"
webServer.port = 7500
webServer.user = "admin"
webServer.password = "LANGES-ZUFALLSPASSWORT"
log.to = "console"
log.level = "info"
Die wichtigsten Zeilen: proxyBindAddr legt die Ports, die Clients anfordern, und den HTTP-Port 8081, an den Caddy weiterreicht, auf 127.0.0.1. transport.tls.force lässt nur verschlüsselte Clients zu. allowPorts begrenzt, welche TCP- und UDP-Ports ein Client auf dem VPS öffnen darf. Ohne diese Zeile könnte jeder mit dem Token beliebige Ports belegen. Das Dashboard auf Port 7500 bleibt lokal. Weil in der Datei das Passwort des Dashboards steht, setzen Sie sie mit chmod 600 frps.toml ebenfalls nur für root lesbar. frps las sie im Test so ohne Probleme.
Die compose.yaml:
services:
frps:
image: fatedier/frps:v0.72.0
container_name: frps
restart: unless-stopped
network_mode: host
command: ["-c", "/etc/frp/frps.toml"]
volumes:
- ./frps.toml:/etc/frp/frps.toml:ro
- ./frp-token:/etc/frp/frp-token:ro
Das Image startet nur /usr/bin/frps und bringt keinen Standardbefehl mit, die Zeile command mit dem Pfad zur Konfiguration ist also nötig. Das Host-Netz passt zu Caddy aus Teil 4, das ebenfalls im Host-Netz läuft und frps so unter 127.0.0.1 erreicht.
docker compose up -d
docker logs frps 2>&1 | grep listen
ss -tlnp | grep frps
Verifizieren: Das Log meldet frps tcp listen on 0.0.0.0:7000, http service listen on 127.0.0.1:8081 und dashboard listen on 127.0.0.1:7500. Von einem anderen Rechner aus bekamen wir auf 8081 und 7500 nur Couldn't connect to server.
Schritt 2: Caddy für die Subdomain
Im Caddyfile kommt ein Block für den neuen Namen dazu, mit den Sicherheits-Headern aus Teil 4:
heim.sedv-lab.de {
import sicher
log {
output file /log/heim.json
format json
}
reverse_proxy 127.0.0.1:8081
}
docker exec caddy caddy reload --config /etc/caddy/Caddyfile
curl -s https://heim.sedv-lab.de/ | grep -o "<h1>[^<]*"
Caddy holte das Zertifikat für den neuen Namen in rund 20 Sekunden. Solange noch kein Client verbunden ist, antwortet frps mit einer eigenen Fehlerseite.

Verifizieren: curl zeigt The page you requested was not found. Das ist die Seite von frps, Caddy und das Zertifikat funktionieren also schon.
Meldet ein Client mit dem Token einen anderen Namen an, reicht Caddy ihn nicht an frps weiter: Auf Port 80 antwortet der Auffangblock aus Teil 4 mit 404, auf 443 gibt es für unbekannte Namen kein Zertifikat. Kurz nach der Ausstellung des Zertifikats fragten übrigens schon die ersten Scanner nach Dateien wie .env und .git auf heim.sedv-lab.de, das frps-Log zeigte sie mit no route found. Neue Namen fallen also sofort auf, denn sie stehen in den öffentlichen Zertifikatslogs.
Schritt 3: frpc im HomeLab starten
Auf dem Rechner im Heimnetz liegen compose.yaml, frpc.toml und eine Kopie der Token-Datei vom VPS in einem Ordner. Die Token-Datei kopieren Sie zum Beispiel mit scp und setzen sie ebenfalls auf 600.
services:
frpc:
image: fatedier/frpc:v0.72.0
restart: unless-stopped
command: ["-c", "/etc/frp/frpc.toml"]
volumes:
- ./frpc.toml:/etc/frp/frpc.toml:ro
- ./frp-token:/etc/frp/frp-token:ro
whoami:
image: traefik/whoami:v1.12.0
restart: unless-stopped
web:
image: nginx:1.31.8-alpine
restart: unless-stopped
volumes:
- ./html:/usr/share/nginx/html:ro
Die frpc.toml:
serverAddr = "sedv-lab.de"
serverPort = 7000
loginFailExit = false
auth.method = "token"
auth.tokenSource.type = "file"
auth.tokenSource.file.path = "/etc/frp/frp-token"
[[proxies]]
name = "heim-web"
type = "http"
localIP = "web"
localPort = 80
customDomains = ["heim.sedv-lab.de"]
requestHeaders.set.x-forwarded-proto = "https"
healthCheck.type = "http"
healthCheck.path = "/"
healthCheck.intervalSeconds = 10
healthCheck.maxFailed = 3
[[proxies]]
name = "heim-whoami"
type = "http"
localIP = "whoami"
localPort = 80
customDomains = ["heim.sedv-lab.de"]
locations = ["/whoami"]
requestHeaders.set.x-forwarded-proto = "https"
TLS zum Server ist bei frpc seit Version 0.50 voreingestellt und muss nicht eigens stehen. loginFailExit = false sorgt dafür, dass frpc es weiter versucht, wenn der VPS beim Start gerade nicht erreichbar ist. Die beiden Proxys teilen sich einen Namen: /whoami geht an den Testdienst, alles andere an nginx. Die Dienste erreicht frpc über ihre Namen im Docker-Netz, Ports nach außen brauchen sie keine.
docker compose up -d
docker compose logs frpc | grep -E "login|start proxy"
Verifizieren: Das Log zeigt login to server success und für beide Proxys start proxy success. https://heim.sedv-lab.de/ liefert jetzt die Seite aus dem Heimnetz, im Test wenige Sekunden nach dem Start. Im Dashboard des VPS stehen beide Proxys als „Online“. Das Dashboard öffnen Sie über einen SSH-Tunnel mit ssh -L 7500:127.0.0.1:7500 root@IHR-VPS und dann http://127.0.0.1:7500 im Browser.

Schritt 4: Die Falle mit X-Forwarded-Proto
Der Testdienst whoami zeigt, welche Header beim Dienst ankommen. Ohne die Zeile requestHeaders.set sah das im Test so aus:

Die echte Adresse des Besuchers steht vorn in X-Forwarded-For, frps hängt 127.0.0.1 an. Das passt. X-Forwarded-Proto meldet aber http, obwohl der Besucher HTTPS genutzt hat: Caddy hat korrekt https gesetzt, frps bekommt die Anfrage aber über einfaches HTTP und überschreibt den Wert. Viele Anwendungen bauen daraus ihre Links und Weiterleitungen. Die Folge sind Links mit http://, Warnungen zu gemischten Inhalten oder Weiterleitungsschleifen bei Anwendungen, die selbst auf HTTPS umleiten. Mit requestHeaders.set.x-forwarded-proto = "https" je Proxy kam im zweiten Lauf X-Forwarded-Proto: https an.
Verifizieren: curl -s https://heim.sedv-lab.de/whoami | grep X-Forwarded zeigt X-Forwarded-Proto: https.
Schritt 5: Was der Tunnel kostet und wie er Ausfälle verkraftet
Gemessen haben wir vom Rechner im Heimnetz aus, also mit einer Anfrage, die zum VPS und durch den Tunnel zurück nach Hause läuft. Zum Vergleich diente die Website direkt auf dem VPS. Für den Durchsatz haben wir eine 100 MB große Datei durch den Tunnel geladen und dieselbe Menge als rohes TCP ohne Tunnel zum VPS geschickt.

Der Tunnel brauchte im Median 72 Millisekunden bis zum ersten Byte, die Website direkt auf dem VPS 58. Die 14 Millisekunden Unterschied entsprechen ungefähr einer zusätzlichen Strecke zwischen VPS und Heimnetz, der Ping lag bei 16 Millisekunden. Beim Durchsatz ist der Upload der Heimleitung die Grenze, denn jede Antwort verlässt das Heimnetz auf diesem Weg. Rohes TCP schaffte 5,5 MB pro Sekunde, der Tunnel mit HTTPS davor 5,2, also rund 94 Prozent. Wer große Dateien aus dem HomeLab anbietet, merkt also die eigene Leitung, nicht frp.
Ausfälle haben wir auf beiden Seiten provoziert. Nach einem Neustart von frpc war die Seite nach einer Sekunde wieder da. Ein Neustart von frps auf dem VPS kostete drei bis vier Sekunden: Caddy meldete einmal 502, danach kam die Fehlerseite von frps, bis frpc sich zwei Sekunden nach dem Start neu angemeldet hatte. Nach einer längeren Pause des Servers dauert es länger, weil frpc die Abstände zwischen seinen Versuchen vergrößert: Als frps im Test wegen einer kaputten Konfiguration eine Weile immer wieder abstürzte, meldete sich frpc 12 Sekunden nach dem erfolgreichen Start an. Im Test wurde zudem der Rechner im Heimnetz unerwartet heruntergefahren. Nach dem Hochfahren starteten die Container von selbst, frpc verband sich, und die Seite war ohne Zutun wieder erreichbar.
Fällt nur der Dienst aus, hilft der healthCheck. Wir haben nginx gestoppt: Anfragen hingen zunächst ohne Antwort. Nach rund 40 Sekunden, also drei Fehlschlägen mit je 10 Sekunden Abstand und 3 Sekunden Zeitlimit, meldete frpc health check status change to failed und nahm den Proxy vom Server. Ab dann bekamen Besucher sofort die Fehlerseite statt einer hängenden Verbindung. Der zweite Proxy /whoami lief dabei ungestört weiter. Sechs Sekunden nach dem Neustart von nginx war die Seite zurück. Wer schneller umschalten will, verkürzt intervalSeconds, maxFailed oder timeoutSeconds.
Verifizieren: docker compose stop web, nach einer Minute zeigt curl -s https://heim.sedv-lab.de/ die Fehlerseite von frps, nach docker compose start web wieder die Seite.
Schritt 6: Grenzen prüfen
Drei Proben zeigen, dass die Einstellungen aus Schritt 1 greifen. Ein Client mit falschem Token bekam token in login doesn't match token from configuration. Ein Client mit transport.tls.enable = false scheiterte mit session shutdown, im Log von frps stand non-TLS connection received on a TlsOnly server. Ein TCP-Proxy auf Port 2223 wurde mit port not allowed abgelehnt, der erlaubte Port 2222 lauschte laut ss nur auf 127.0.0.1 und war von außen nicht erreichbar.
Verifizieren: docker logs frps 2>&1 | grep -E "doesn't match|TlsOnly|not allowed" zeigt abgewiesene Versuche.
Typische Fehler
| Meldung oder Beobachtung | Ursache und Lösung |
|---|---|
Browser zeigt The page you requested was not found. | frps kennt den Namen nicht: frpc ist nicht verbunden, customDomains stimmt nicht oder der healthCheck hat den Proxy entfernt. Log von frpc prüfen. |
token in login doesn't match token from configuration | Token-Dateien unterscheiden sich. Datei erneut vom VPS kopieren, auf Zeilenumbrüche achten. |
connect to server error: session shutdown | TLS beim Client abgeschaltet, frps verlangt es. transport.tls.enable = false entfernen. |
start error: port not allowed | Port steht nicht in allowPorts. Bewusst ergänzen oder anderen Port wählen. |
frps startet ständig neu, im Log toml: line 16, column 1: toml: key to is already defined | Doppelter Schlüssel in der frps.toml, im Test nach einer missglückten Bearbeitung. Datei prüfen, frpc verbindet sich danach von selbst wieder. |
Anwendung erzeugt http://-Links oder leitet endlos um | frps setzt X-Forwarded-Proto: http. requestHeaders.set.x-forwarded-proto = "https" im Proxy setzen, Schritt 4. |
| Anfragen hängen, wenn der Dienst aus ist | Ohne healthCheck bleibt der Proxy registriert. Prüfung einrichten wie in Schritt 3. |
Häufige Fragen
Warum nicht einfach Cloudflare Tunnel?
Cloudflare Tunnel braucht keinen eigenen Server, dafür läuft der ganze Verkehr samt TLS über Cloudflare. Mit frp und einem eigenen VPS bleiben Zertifikat und Daten bei Ihnen, ohne die Vorgaben eines Fremdanbieters zu Größe und Inhalt. Wer Anmeldung vor dem Dienst und eine Oberfläche möchte, findet in Pangolin eine Alternative auf WireGuard-Basis.
Geht das auch für SSH oder andere TCP-Dienste?
Ja, mit type = "tcp" und remotePort. Mit proxyBindAddr = "127.0.0.1" landet der Port aber nur lokal auf dem VPS. Sie erreichen ihn dann über SSH auf den VPS, was oft genau gewünscht ist. Soll er öffentlich sein, müssen Sie das bewusst anders einstellen.
Ist der Weg zwischen HomeLab und VPS verschlüsselt?
Ja. frpc spricht standardmäßig TLS mit frps, und transport.tls.force verbietet unverschlüsselte Clients. Das Zertifikat des Servers prüft frpc ohne transport.tls.trustedCaFile allerdings nicht, gegen einen Angreifer in der Leitung schützt erst ein eigenes Zertifikat mit dieser Einstellung. Nur das letzte Stück zwischen Caddy und frps auf dem VPS ist unverschlüsseltes HTTP, und das bleibt auf der Loopback-Schnittstelle.
Wie schütze ich einen Dienst, der keine eigene Anmeldung hat?
Im Caddy-Block mit basic_auth oder einem Anmeldedienst davor. Alles, was über heim.sedv-lab.de erreichbar ist, steht genauso öffentlich im Netz wie bei einer Portfreigabe.
Fazit
frp bringt Dienste aus dem Heimnetz ohne Portfreigabe ins Internet und kostete in der Messung kaum etwas: 14 Millisekunden mehr Antwortzeit und rund 94 Prozent der Leitung beim Durchsatz. Neustarts auf beiden Seiten heilten von selbst in wenigen Sekunden, ein Neustart des Heimrechners nach dem Hochfahren. Sicher wird der Aufbau durch vier Einstellungen in der frps.toml: Proxys nur lokal, TLS als Pflicht, Token aus einer Datei und eine Liste erlaubter Ports. Caddy davor liefert HTTPS und Header. Denken Sie an X-Forwarded-Proto, sonst glaubt die Anwendung im HomeLab, sie laufe ohne HTTPS.
Weiterführende Anleitungen und Quellen
- Teil 4: Caddy als Eingangstür
- Teil 3: Domain fürs Lab verdrahten
- Teil 6: Anubis vor der Website
- Docker und Docker Compose auf Linux installieren
- Docker Compose absichern
- Cloudflare Tunnel auf Synology NAS mit Docker
- Pangolin mit Docker installieren
- frp auf GitHub
- frp v0.72.0 Release
- frp: vollständige Beispielkonfiguration frps
- frp: vollständige Beispielkonfiguration frpc
- Caddy: reverse_proxy


