Once you run more than a couple of services at home, a reverse proxy stops being optional.
My setup is simple: everything sits behind Caddy.
Only ports 80 / 443 are exposed. Services stay internal and move freely.
Caddy handles the boring but critical parts:
- a single public entry point
- automatic TLS
- clean separation between domains and backend services
Below is the out-of-box Docker Compose configuration I use.
services:
caddy:
image: caddy:latest
container_name: caddy
restart: unless-stopped
cap_add:
- NET_ADMIN
ports:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./site:/srv
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config:
With this in place, adding or removing services is just editing the Caddyfile.
No port juggling, no mental overhead.
Once a reverse proxy is in front, the homelab becomes something you can grow without friction.
Some of my Caddyfile configurations (masked):
# =============================
# Git server
# =============================
git.example.org {
reverse_proxy 192.168.10.22:8001
}
# =============================
# well-known redirects + proxy
# =============================
cloud.example.org {
redir /.well-known/carddav /remote.php/dav/ 301
redir /.well-known/caldav /remote.php/dav/ 301
reverse_proxy 192.168.10.23:11000
}
# =============================
# Upstream HTTPS (internal cert)
# =============================
vpn.example.org {
reverse_proxy https://192.168.10.6:943 {
transport http {
tls_insecure_skip_verify
}
}
}
# =============================
# Subpath rewrite (Guacamole)
# =============================
remote.example.org {
rewrite * /guacamole{uri}
reverse_proxy 192.168.10.24:8080
}
# =============================
# Model APIs: auth gate + long timeouts
# =============================
(mymodel_auth) {
@unauth {
not header Authorization "Bearer <REDACTED>"
}
respond @unauth "Unauthorized" 401
}
m0.example.org {
import mymodel_auth
reverse_proxy 192.168.10.9:8080 {
transport http {
read_timeout 1h
write_timeout 1h
}
}
}
# m1.example.org / m2.example.org / ollama.example.org
# follow the same pattern with different ports
Once you run more than a couple of services at home, a reverse proxy stops being optional.
My setup is simple: everything sits behind Caddy.
Only ports 80 / 443 are exposed. Services stay internal and move freely.
Caddy handles the boring but critical parts:
Below is the out-of-box Docker Compose configuration I use.
With this in place, adding or removing services is just editing the Caddyfile.
No port juggling, no mental overhead.
Once a reverse proxy is in front, the homelab becomes something you can grow without friction.
Some of my Caddyfile configurations (masked):
Read Next
Homelab: Self-Hosting GitLab
Homelab: Shadowsocks + VLESS (TCP+TLS)
Homelab: WireGuard VPN (Docker)
Homelab: OpenVPN AS (Docker)
Launch your OpenVPN Access Server instantly with this ready-to-use Docker Compose file. Skip the complex setup - just copy, paste, and get a fully functional VPN server with local network access.