Internal HTTPS reverse proxy with automatic SSL certificates via Cloudflare DNS-01 challenge. Traffic arrives via Cloudflare Tunnel; Caddy terminates TLS and reverse-proxies to backend services.

Container: CT 127 IP: 192.168.144.31 Ports: 80 (HTTP), 443 (HTTPS)

Source Code

Source Code: github.com/opajanvv/homelab-docker/tree/main/lanproxy Local clone: ~/workspace/homelab-docker/lanproxy/

Configs are version-controlled. Edit locally, commit, push, then pull on server + reload Caddy. Never edit Caddyfile directly on the server.

Deployment

# Clone LXC template
pct clone 902 127 --hostname lanproxy --full
pct set 127 --cores 1 --memory 512
pct set 127 -net0 name=eth0,bridge=vmbr0,firewall=1,gw=192.168.144.1,ip=192.168.144.31/23
pct set 127 -mp0 /lxcdata/lanproxy,mp=/data
pct set 127 -mp1 /home/jan/homelab-docker,mp=/opt/homelab-docker
pct set 127 -features nesting=1,keyctl=1
pct set 127 -onboot 1

# Deploy
pct start 127
pct exec 127 -- bash -c 'systemctl enable --now docker'
# Configs available via bind mount (no git clone needed)
pct exec 127 -- bash -c 'cd /opt/homelab-docker/lanproxy && chmod +x install.sh && ./install.sh'

Configuration

Environment Variable:

  • CLOUDFLARE_API_TOKEN - Cloudflare API token for DNS-01 challenge

Data Locations:

  • /data/ - Caddy data (certificates, logs)
  • /opt/homelab-docker/lanproxy/ - Docker Compose config, Caddyfile

Config File: /opt/homelab-docker/lanproxy/Caddyfile

Caddyfile Format:

<service-domain> {
    tls {
        dns cloudflare {env.CLOUDFLARE_API_TOKEN}
    }
    encode zstd gzip
    reverse_proxy <internal-ip>:<port>
}

Active Routes

Domain Backend Service
hermes-ui.janvv.nl 192.168.144.63:8787 Hermes Web UI
hermes.janvv.nl 192.168.144.63:9119 Hermes Dashboard (default)
assistant.janvv.nl 192.168.144.120:8123 Home Assistant
photos.janvv.nl 192.168.144.110:2283 Immich
tasks.janvv.nl 192.168.144.60:1337 Planka
n8n.janvv.nl 192.168.144.61:5678 n8n
kijkdoos.janvv.nl 192.168.144.100:8096 Jellyfin
search.janvv.nl 192.168.144.64:8080 SearXNG
proxmox.janvv.nl 192.168.144.10:8006 Proxmox VE
printer.janvv.nl 192.168.144.66:631 CUPS admin
print.janvv.nl 192.168.144.66:8080 CUPS upload page
status.janvv.nl 192.168.144.25:80 Status monitor
terminal.janvv.nl 192.168.144.10:4711 ttyd (Proxmox host)
laptop1.janvv.nl 192.168.145.10:4711 ttyd (laptop1)
laptop2.janvv.nl 192.168.145.20:4711 ttyd (laptop2)
www.janvv.nl, opa.janvv.nl 192.168.144.72:80 Grav
jokegoudriaan.nl, www.jokegoudriaan.nl 192.168.144.70:80 WordPress Joko

For the full Caddyfile, see the GitHub repo.

How It Works

  1. External client requests https://hermes.janvv.nl
  2. Cloudflare DNS resolves to the tunnel (CNAME → *.cfargotunnel.com)
  3. Cloudflare Tunnel routes the request to Caddy at https://192.168.144.31
  4. Caddy receives request, terminates TLS (cert via Cloudflare DNS-01), proxies to backend
  5. Response flows back through Caddy → tunnel → Cloudflare → client

Backup

What to backup:

  • /opt/homelab-docker/lanproxy/Caddyfile - Route configuration
  • /lxcdata/lanproxy/ - Caddy data (certificates, logs)

Backup command:

cp /opt/homelab-docker/lanproxy/Caddyfile /backup/homelab/
rsync -av /lxcdata/lanproxy/ /backup/homelab/lanproxy/

Maintenance

Update:

pct exec 127 -- bash -c 'cd /opt/homelab-docker/lanproxy && docker compose pull && docker compose up -d'

View logs:

pct exec 127 -- bash -c 'cd /opt/homelab-docker/lanproxy && docker compose logs -f'

Reload config after Caddyfile change:

pct exec 127 -- bash -c 'cd /opt/homelab-docker/lanproxy && docker compose restart'

Adding a New Route

  1. Edit Caddyfile locally in ~/workspace/homelab-docker/lanproxy/Caddyfile:

    new-service.janvv.nl {
       tls {
           dns cloudflare {env.CLOUDFLARE_API_TOKEN}
       }
       encode zstd gzip
       reverse_proxy 192.168.144.XX:PORT
    }
  2. Commit and push:

    cd ~/workspace/homelab-docker && git add lanproxy/Caddyfile && git commit -m "caddy: add new-service" && git push
  3. Pull on server and reload:

    ssh -A jan@server 'cd /home/jan/homelab-docker && git pull && sudo pct exec 127 -- bash -c "cd /opt/homelab-docker/lanproxy && docker compose restart"'
  4. Add DNS CNAME (proxied) via Cloudflare API pointing to the tunnel, and add a tunnel ingress rule routing to Caddy (https://192.168.144.31).

  5. Verify:

    curl -sk -o /dev/null -w "%{http_code}" https://new-service.janvv.nl/

Troubleshooting

Certificate errors: Check Cloudflare API token is valid Route not working: Verify service is accessible from Lanproxy container DNS issues: Check Pi-hole DNS configuration

Related