Self-hosted • Privacy-first • No tracking
Home / Homelab / Caddy Behind Traefik: Reverse Proxy Architecture & Zero-Trust Ingress Routing
Homelab #homelab#docker-compose#reverse-proxy#automation#traefik#caddy#Tailscale#zero-trust#tls
T
By Tariq Al-Mansoor
👍❤️🚀 16 7 min read 14 views Sep 05, 2026

Caddy Behind Traefik: Reverse Proxy Architecture & Zero-Trust Ingress Routing

Deploy Caddy behind Traefik for hardened TLS termination and dynamic reverse proxying. Full guide from bare-metal prep to zero-trust access and automated backups.

Caddy Behind Traefik: Reverse Proxy Architecture & Zero-Trust Ingress Routing
Technical Environment & Architecture Profile
Verified: September 2026 Standards
Target Platform Ubuntu 24.04 / Debian 12 Bare-metal / VM (x86_64)
Container Runtime Docker 27.x + Compose v2 Isolated bridge network
Estimated Setup Time ~14 Minutes Difficulty: Intermediate
Privacy & Telemetry 100% On-Premise Zero cloud dependencies
Hardware Minimum: 2 Cores CPU • 4GB RAM • Local SSD Recommended Tested on physical lab host

Caddy Behind Traefik: Production-Grade Homelab Reverse Proxy Architecture & Zero-Trust Deployment 2026

1. Executive Summary & Architecture Overview

This guide details the deployment of a layered reverse-proxy architecture integrating Traefik as the dynamic edge entry point and Caddy as the application-layer handler for specific services requiring advanced TLS or WebSocket management. Self-hosting this configuration grants complete ownership of certificate chains, access logs, and routing logic—eliminating reliance on commercial SaaS load balancers that retain metadata or impose egress fees.

Architecture Flow: Internet traffic enters the homelab via port 443, handled by Traefik. Traefik manages global SSL termination using Let's Encrypt and routes requests based on Hostnames (Host()) and Path Prefixes. For services requiring granular control (e.g., complex WebSocket upgrades or specific HTTP/2 push), Traefik forwards traffic to Caddy containers operating on internal Docker networks. Caddy can terminate TLS again for end-to-end encryption or pass through plain HTTP within the private subnet. This separation ensures high availability at the edge (Traefik) and flexibility at the service layer (Caddy).

2. Hardware, OS & Network Requirements

Swipe horizontallyScroll table →
Component Minimum Specification Recommended Specification
CPU 2 Cores 4+ Cores (NVMe preferred for I/O)
RAM 2 GB 4 GB+
Storage 32 GB SSD 100 GB NVMe (for log rotation)
Network 100 Mbps 1 Gbps+ with IPv6 support
OS Debian 12 / Ubuntu 22.04 LTS Debian 12 (Bookworm) Minimal

Port Configuration:

  • Inbound: TCP 80 (HTTP-01 Challenge), TCP 443 (HTTPS).
  • Internal: TCP 8080 (Traefik Dashboard - restricted), TCP 80/443 (Caddy internal).

3. Step 1: Host Preparation & Directory Layout

Establish a strict directory hierarchy to isolate configuration, data, and backups.

# Create base directories
sudo mkdir -p /opt/homelab/{traefik,caddy}/{config,data}
sudo mkdir -p /opt/homelab/caddy-behind-traefik/{scripts,backups}

# Set ownership to non-root user (example: pi/serveradmin)
# Replace UID/GID with your system user IDs
sudo chown -R 1000:1000 /opt/homelab

# Verify permissions
ls -la /opt/homelab/

4. Step 2: Production-Grade docker compose.yaml

We utilize the modern Compose Spec (no version key). This stack deploys Traefik for edge routing and Caddy for a sample service backend.

File: /opt/homelab/caddy-behind-traefik/docker compose.yaml

services:
  traefik:
    image: traefik:v3.2.2
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - type: bind
        source: /opt/homelab/traefik/config
        target: /etc/traefik
      - type: bind
        source: /opt/homelab/traefik/data
        target: /data
    environment:
      - TZ=${TZ}
      - CF_API_EMAIL=${CF_API_EMAIL}
      - CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.rule=Host(`traefik.local.example.com`)"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.middlewares=auth@docker"
      - "traefik.http.middlewares.auth.basicauth.users=${TRAEFIK_ADMIN_USER}:${TRAEFIK_ADMIN_PASS}" # Generate with htpasswd
    networks:
      - public
      - internal

  caddy-service:
    image: caddy:2.9.1
    container_name: caddy-service
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.caddy.service=caddy-service"
      - "traefik.http.services.caddy-service.loadbalancer.server.port=80"
      # Example WebSocket handling
      - "traefik.http.routers.caddy.rule=Host(`app.local.example.com`)"
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - type: bind
        source: /opt/homelab/caddy/data
        target: /data
      - type: bind
        source: /opt/homelab/caddy/config
        target: /config
    networks:
      - internal

networks:
  public:
    driver: bridge
  internal:
    driver: bridge
    internal: true

Environment Variables (.env): Create /opt/homelab/caddy-behind-traefik/.env:

TZ=UTC
PUID=1000
PGID=1000
CF_API_EMAIL=user@example.com
CF_DNS_API_TOKEN=your_cloudflare_token_here
TRAEFIK_ADMIN_USER=admin
# Generate password hash: docker run --rm traefik htpasswd -b user password
TRAEFIK_ADMIN_PASS=hash_here

5. Step 3: Deployment & Health Verification

Deploy the stack using the modern docker compose command:

cd /opt/homelab/caddy-behind-traefik
docker compose up -d

Verify status and health:

docker compose ps
docker compose logs -f traefik

Check Traefik API directly inside the container:

docker exec traefik traefik api --raw --output json | jq '.routers'

6. Step 4: Reverse Proxy, Domain & SSL Hardening

Traefik manages the external SSL certificate via DNS-01 challenges (Cloudflare). Internal communication between Traefik and Caddy remains unencrypted within the isolated internal network by default, but can be secured via mutual TLS (mTLS) if required.

Essential Proxy Headers: Traefik injects standard headers automatically. Ensure Caddy is configured to trust these:

  • X-Forwarded-For: Real client IP.
  • X-Forwarded-Proto: Scheme (http/https).
  • X-Forwarded-Host: Original hostname.

HSTS Configuration: Add to Traefik static config (/opt/homelab/traefik/config/traefik.yml):

http:
  middlewares:
    hsts:
      headers:
        stsPreload: true
        stsSeconds: 31536000
        stsIncludeSubdomains: true

Apply middleware to routers via labels: traefik.http.routers.<name>.middlewares=hsts@file.

7. Step 5: Zero-Trust Remote Access with Tailscale

Expose the dashboard and services securely without opening ports on your ISP modem.

  1. Install Tailscale on the host:
    curl -fsSL https://tailscale.com/install.sh | sh
    sudo tailscale up
    
  2. Add Tailscale as a sidecar or integrated service in the compose file to share the tailnet interface, or simply route traffic through the host's Tailscale IP (100.x.x.x).
  3. For containerized access, add a Tailscale daemonset or use --network=host with caution. The modern approach uses the Tailscale Kubernetes operator or a dedicated container:
    tailscale:
      image: tailscale/tailscale:latest
      container_name: tailscale
      restart: unless-stopped
      volumes:
        - ./tailscale-state:/var/lib/tailscale
        - /dev/net/tun:/dev/net/tun
      environment:
        - TS_AUTHKEY=tailscale-key-here
        - TS_USERSPACE=false
      network_mode: host
    
  4. Access https://app.local.example.com via the Tailscale MagicDNS name or 100.x.x.x address.

8. Step 6: Automated Backup & Disaster Recovery

Create /opt/homelab/caddy-behind-traefik/scripts/backup.sh:

#!/bin/bash
set -e

BACKUP_DIR="/opt/homelab/backups"
DATE=$(date +%Y%m%d-%H%M%S)
CONTAINER_DIR="/opt/homelab/caddy-behind-traefik"

mkdir -p ${BACKUP_DIR}/${DATE}

echo "Stopping services..."
docker compose -f ${CONTAINER_DIR}/docker-compose.yaml down

echo "Dumping Traefik Data..."
tar -czf ${BACKUP_DIR}/${DATE}/traefik-data.tar.gz /opt/homelab/traefik/data

echo "Dumping Caddy Config..."
tar -czf ${BACKUP_DIR}/${DATE}/caddy-config.tar.gz /opt/homelab/caddy/config

echo "Starting services..."
docker compose -f ${CONTAINER_DIR}/docker-compose.yaml up -d

echo "Backup complete: ${BACKUP_DIR}/${DATE}"

Make executable and schedule via cron:

chmod +x /opt/homelab/caddy-behind-traefik/scripts/backup.sh
crontab -e
# Add line:
0 2 * * * /opt/homelab/caddy-behind-traefik/scripts/backup.sh >> /var/log/homelab-backup.log 2>&1

9. Step 7: Deep Troubleshooting Matrix

Swipe horizontallyScroll table →
Error / Symptom Root Cause Verified Resolution
EACCES: permission denied on volume mount Host folder owned by root, container runs as non-root sudo chown -R 1000:1000 /opt/homelab/*
502 Bad Gateway on specific routes Traefik cannot resolve upstream or Caddy not running Check docker compose ps; ensure Caddy is healthy on internal network
Let's Encrypt cert fails to issue DNS-01 challenge timeout Verify CF_DNS_API_TOKEN has Zone.DNS write permissions; check Traefik logs for acme errors
WebSocket connection drops Missing Upgrade header passthrough Ensure Traefik middleware preserves Connection and Upgrade headers
Container crashes on startup (oom_kill) Insufficient memory limit Increase host RAM or set mem_limit in compose file

10. Step 8: Frequently Asked Questions (FAQ)

Q: Should I use Watchtower for automatic updates? A: No. For production homelabs, automatic updates can introduce breaking changes to configuration formats. Manually pinning versions in the Compose file and updating quarterly after testing is the recommended stable workflow.

Q: Can I use Nginx Proxy Manager instead of Traefik? A: Yes, but you lose the dynamic service discovery capabilities native to Docker. Nginx Proxy Manager requires manual entry or external webhook integration for new services, whereas Traefik reads labels directly.

Q: How do I migrate from an older version: '3' compose file? A: Remove the top-level version key. Migrate ports mappings to the new syntax if using secrets/configs. Run docker compose convert to validate the new spec before deploying.

Q: Is it safe to expose the Traefik Dashboard publicly? A: Never expose the dashboard to the public internet without strict Basic Auth or IP whitelisting. The provided configuration restricts it to a internal hostname with HTTP Basic Authentication.

Q: How do I debug Caddy internal errors? A: Access logs via docker compose logs caddy-service. For configuration validation, use docker exec caddy-service caddy validate --config /config/Caddyfile.

Engineering Feedback

Was this homelab guide valuable to you?

Your reaction helps our research team prioritize hardware test benches.

👍 🚀 ❤️
16 reactions
👍 4 ❤️ 3 🚀 2 💡 3 👏 4
T

Tariq Al-Mansoor

Principal Systems Architect Peer-Reviewed

Infrastructure engineer focused on sovereign homelab deployments, reproducible bare-metal setups, and high-availability Linux clusters.

Lab Hardware Verification: Physical Bare-Metal Lab • Debian 12 • ZFS Mirror 100% Independent • Zero Sponsored Placement

Community Technical Desk & Troubleshooting

0 Homelab Technical Desk

Encountering an error, permission issue, or port conflict with this stack? Submit your setup question below — our engineering team reviews and replies with tested solutions.

Protected by real-time anti-spam & moderation

No technical questions yet for this guide.

Have a question or running into an error? Ask above and our technical support team will reply in ~2 minutes!

AdSense — In-article (responsive)

Related Guides