Provisioning Traefik with Docker Compose and TLS Termination via Internal CA
2025-05-024 min read
Overview
Running a reverse proxy with proper TLS in a home lab means you can stop clicking through browser certificate warnings and start treating internal services like production. In this post, I walk through deploying Traefik via Docker Compose with TLS termination backed by my self-hosted internal CA -- covering the configuration decisions, the troubleshooting rabbit holes, and the final working setup.
Objectives
- Deploy Traefik using Docker Compose
- Enable HTTPS via static and dynamic configuration
- Load a custom certificate signed by an internal CA
- Validate secure access to the Traefik dashboard
Environment
- Operating System: Ubuntu Server
- Traefik Version: 2.11
- Docker & Docker Compose
- Internal PKI: Self-hosted CA issuing trusted certificates
- Domain: Custom internal domain (e.g.,
*.example.lan)
Directory Structure
hljs bash
~/traefik/
├── traefik.yml # Static configuration
├── docker-compose.yml # Docker service definition
├── config/
│ └── dynamic.yml # Dynamic configuration
└── certs/
├── example.lan.crt # Server certificate
├── example.lan.key # Private key
└── ca.crt # Root CA certificate (optional for clients)
Step-by-Step Configuration
1. Static Configuration (traefik.yml)
hljs yml
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
api:
dashboard: true
log:
level: DEBUG
providers:
file:
filename: /home/user/traefik/config/dynamic.yml
watch: true
2. Dynamic Configuration (config/dynamic.yml)
hljs yml
tls:
certificates:
- certFile: /home/user/traefik/certs/example.lan.crt
keyFile: /home/user/traefik/certs/example.lan.key
http:
routers:
traefik-dashboard:
rule: "Host(`traefik.example.lan`)"
entryPoints:
- websecure
service: api@internal
tls: true
3. Docker Compose File (docker-compose.yml)
hljs yml
version: "3.8"
services:
traefik:
image: traefik:v2.11
container_name: traefik
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./traefik.yml:/home/user/traefik/traefik.yml
- ./config/dynamic.yml:/home/user/traefik/config/dynamic.yml
- ./certs:/home/user/traefik/certs
Note: All file paths are absolute within the container for consistency.
Troubleshooting Process
Problem: Curl to HTTPS Endpoint Failed (Connection Refused)
- Symptoms:
curl -vk https://traefik.example.lanreturned connection refused.- Port 443 showed as open via
ss -tuln, but no container binding occurred.
- Resolutions Attempted:
- Verified ports 80/443 availability (
netstat,lsof,ss) - Ensured
docker-compose downfully removed container states - Restarted Docker service to release potentially held ports
- Verified ports 80/443 availability (
Problem: No Traefik Logs Visible
- Symptoms:
docker logs traefikshowed no output - Fix: Added
log.level: DEBUGtotraefik.ymland confirmed the config was mounted properly
Problem: Dashboard Loaded with Default Self-Signed Certificate
- Symptoms: Dashboard displayed a browser warning for
TRAEFIK DEFAULT CERT - Fix:
- Verified dynamic config was correctly referenced and mounted
- Confirmed cert and key filenames were correct
- Restarted Traefik after correcting mount paths to match container expectations
Final Fix: Proper Mounting and Configuration Paths
- All paths in the YAML files were made fully absolute and consistently mounted into the container
- Docker Compose volumes were validated against container paths
- After restarting the container stack, the browser showed the correct certificate issued by the internal CA
Final Validation
- Verified TLS certificate via browser: matched
CN=traefik.example.lan, signed by internal root CA - Accessed dashboard via
https://traefik.example.lan:443 - No browser warnings once the root CA was installed in the local trust store
Lessons Learned
- Path consistency is everything. The single biggest gotcha was mismatched file paths between the host, Docker Compose volumes, and Traefik's YAML configs. Make every path absolute and triple-check that host mounts align with what the container expects.
- Always enable debug logging first. Without
log.level: DEBUG, Traefik fails silently. Turn it on before you start troubleshooting. - The "TRAEFIK DEFAULT CERT" warning means your dynamic config isn't loading. If you see it, check your file provider path and volume mounts before anything else.
Next Steps
- Integrate with Authentik for OIDC-based SSO
- Add automatic TLS renewal via internal CA workflows
- Use Traefik middlewares for authentication, rate-limiting, or header injection