Skip to content

Traefik & Network Routing

Traefik is the single, unified reverse proxy and load balancer for all CharlieHub services. It handles both HTTP(S) and TCP routing from a centralized control plane via the Domain Manager API.

Overview

Property Value
Location hub2 (OVH Dedicated Server)
Container charliehub-traefik
Instances 1 (unified ACME-only)
Ports 80, 443 (HTTPS), 8883 (MQTT TCP), 8091 (dashboard)
URL https://traefik.charliehub.net
Config /opt/charliehub/traefik/config/
Architecture Single-writer (drift-proof)

Architecture

Traffic Flow Overview

                                    INTERNET
                                        │
                                        ▼
                            ┌───────────────────────┐
                            │   DNS: *.charliehub.net │
                            │   → 51.68.235.106      │
                            └───────────┬───────────┘
                                        │
                                        ▼
┌───────────────────────────────────────────────────────────────────────────────┐
│                         hub2 (OVH Dedicated)                                  │
│                            51.68.235.106                                      │
│                                                                               │
│  ┌─────────────────────────────────────────────────────────────────────────┐  │
│  │                    Traefik (Unified ACME)                              │  │
│  │          Single instance → All routing (HTTP + TCP)                     │  │
│  │                 Port 80, 443, 8883                                      │  │
│  │                                                                         │  │
│  │   ┌─────────────────────┐    ┌─────────────────────────────────┐      │  │
│  │   │ TLS Termination     │    │ TCP Services                    │      │  │
│  │   │ + HTTP Routing      │    │ (MQTT on :8883)                │      │  │
│  │   │                     │    │ (via tcp entrypoint)           │      │  │
│  │   └────────┬────────────┘    └─────────────┬───────────────────┘      │  │
│  └────────────┼────────────────────────────────┼──────────────────────────┘  │
│               │                                │                             │
│               ▼                                ▼                             │
│  ┌──────────────────────────┐    ┌─────────────────────────────────┐        │
│  │ Docs, Grafana,           │    │ parking-mosquitto               │        │
│  │ Prometheus, UniFi, etc   │    │ (MQTT broker)                   │        │
│  │ (HTTP services)          │    │                                 │        │
│  └──────────────────────────┘    └─────────────────────────────────┘        │
│                                                                               │
└───────────────────────────────────────────────────────────────────────────────┘

How Traefik + Domain Manager Work Together

Traefik handles all inbound HTTPS traffic from the internet. All routing (HTTP + TCP) is managed from a single PostgreSQL source via Domain Manager:

PostgreSQL domains table
         ↓
Domain Manager API (/api/domains/*)
         ↓
traefik_generator.py (v2.3+)
         ↓
/traefik/config/generated/routes.yml (atomic writes)
         ↓
Traefik routers (HTTP + TCP)

Example: UniFi API accessing UCG controllers

  1. User requests https://unifi.charliehub.net/api/clients?controller=uk
  2. Traefik terminates TLS, checks Authelia, routes to UniFi API container
  3. UniFi API needs to reach UK UCG at 10.44.1.29
  4. Traffic routes via WireGuard interface wg-uk to 10.44.x.x subnet
  5. Response flows back through the same path

Routing Management

Key Principle: Single Source of Truth

All routes are defined in PostgreSQL and managed via the Domain Manager API. Never edit /traefik/config/generated/routes.yml directly — it is generated from the database.

How to Add a Route

Use the Domain Manager API:

curl -X POST http://localhost:8001/api/domains \
  -H "X-API-Key: charliehub" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "myservice.charliehub.net",
    "protocol": "http",
    "service_type": "api",
    "backend_host": "myservice",
    "backend_port": 8080,
    "environment": "production",
    "auth_required": true,
    "status": "active"
  }'

The API validates the configuration, stores it in PostgreSQL, and automatically triggers route generation.

Generated Routes

Routes are generated to /traefik/config/generated/routes.yml:

http:
  routers:
    myservice-charliehub-net:
      rule: Host(`myservice.charliehub.net`)
      service: myservice-charliehub-net
      entryPoints: [websecure]
      tls:
        certResolver: letsencrypt
      middlewares:
        - authelia@file
  services:
    myservice-charliehub-net:
      loadBalancer:
        servers:
          - url: http://myservice:8080

TCP Routing

TCP routes (like MQTT) are managed the same way:

curl -X POST http://localhost:8001/api/domains \
  -H "X-API-Key: charliehub" \
  -d '{
    "domain": "mqtt.verdegris.eu",
    "protocol": "tcp",
    "service_type": "tcp-proxy",
    "tcp_entrypoint": "mqtt",
    "backend_host": "parking-mosquitto",
    "backend_port": 1883,
    "status": "active"
  }'

See CharlieHub TCP Routing for detailed TCP routing procedures.

Traefik Configuration

Static Configuration (Immutable)

# /opt/charliehub/traefik/config/traefik.yml
entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"
  mqtt:
    address: ":8883"

certificatesResolvers:
  letsencrypt:
    acme:
      email: admin@charliehub.net
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

providers:
  file:
    directory: /config
    watch: true

Key Points: - Docker provider is disabled (routes managed via file provider only) - File provider watches /config recursively (includes /config/generated/) - ACME is the only certificate resolver (Let's Encrypt) - TCP entrypoint mqtt defined here; used by Domain Manager routes

Dynamic Configuration (Generated)

Routes are generated to /traefik/config/generated/routes.yml and include: - HTTP routers with Authelia middleware - TCP routers with SNI-based routing - Service definitions with load balancer backends - Security headers and CORS middleware

Authentication Management

Overview

Traefik enforces Authelia authentication by default for all managed services. Authentication is controlled via the Domain Manager API using the auth_required field.

auth_required Behavior Middleware Use Case
true (default) Requires Authelia login authelia@file + security headers Internal/sensitive services
false No authentication Security headers only Public services, public APIs

Updating Authentication

# Disable authentication
curl -X PUT http://localhost:8001/api/domains/123 \
  -H "X-API-Key: charliehub" \
  -d '{"auth_required": false}'

# Re-enable authentication
curl -X PUT http://localhost:8001/api/domains/123 \
  -H "X-API-Key: charliehub" \
  -d '{"auth_required": true}'

Routes regenerate automatically.

Accessing Services from Homelab

The Problem

When a homelab VM/CT accesses docs.charliehub.net:

  1. DNS resolves to 51.68.235.106 (hub2's public IP)
  2. Traffic goes out via the UCG's WAN connection
  3. Arrives at hub2 from the site's public WAN IP (e.g., 78.116.21.175)
  4. Authelia sees the WAN IP, not the VM's internal IP

This breaks IP-based authentication bypass.

The Solution: Route via WireGuard

Configure local DNS on your UCG to route CharlieHub services via WireGuard:

Domain Points To Notes
*.charliehub.net 10.44.x.x hub2's WireGuard IP (via wg-uk)

On UniFi UCG: Settings → Networks → (select network) → DHCP → DNS Server → Add custom DNS entries

With this setup: 1. VM resolves docs.charliehub.net10.44.x.x 2. Traffic routes via WireGuard tunnel to hub2 3. Arrives at hub2 from 10.x.x.x (VM's actual IP) 4. Authelia bypass rules work correctly

Common Operations

View Active HTTP Routers

curl -s http://localhost:8091/api/http/routers | jq '.[].name'

View Active TCP Routers

curl -s http://localhost:8091/api/tcp/routers | jq '.[].name'

Check Certificate Status

curl -s http://localhost:8091/api/http/routers | jq '.[] | {name, tls}'

View Generated Routes

# Preview what was generated
cat /opt/charliehub/traefik/config/generated/routes.yml | head -50

View Logs

docker logs charliehub-traefik --tail 100 -f

Troubleshooting

404 Not Found

  • Check router is registered: curl http://localhost:8091/api/http/routers
  • Verify domain exists in database: docker exec -i charliehub-postgres psql -U charliehub -d charliehub_domains -c "SELECT domain, status FROM domains WHERE domain='myservice.charliehub.net'"
  • Check status is active (disabled routes don't generate)

502 Bad Gateway

  • Backend container not running or unhealthy
  • Wrong port in backend_port
  • Network connectivity issue: docker exec -i charliehub-postgres psql ... -c "SELECT backend_host, backend_port FROM domains ..."

Certificate Errors

# Check ACME status
docker exec -i charliehub-postgres psql -U charliehub -d charliehub_domains \
  -c "SELECT domain, status FROM domains WHERE status='active' ORDER BY domain"

# Check Traefik logs for ACME errors
docker logs charliehub-traefik 2>&1 | grep -i acme

Routes Not Generating

# Manually trigger generation
docker exec charliehub_domain_manager_v3 python3 /app/services/traefik_generator.py

# Check for errors in output

Authelia Redirect Loop

  • Service missing from Authelia bypass rules
  • Cookie domain mismatch
  • Check Authelia logs: docker logs charliehub_authelia

Last updated: 2026-02-12 | Architecture: Unified ACME, drift-proof