What is Traefik, and when does it make sense?
Traefik is a reverse proxy written in Go, released in 2015 by Emile Vauge. It is not the only option. Nginx Proxy Manager is more click-friendly, Caddy is simpler to configure as text. But Traefik works best with Docker. It reads your container labels and reconfigures itself automatically whenever you start a new service.
The alternative is tracking ports manually. Port 8080 for Pi-hole, 8181 for Nextcloud, 9000 for something you set up six months ago and can no longer identify. Traefik gives you pihole.your-domain.com, nextcloud.your-domain.com and HTTPS on both without any extra work.
This guide uses Cloudflare as the DNS provider because it is the most common setup in homelab environments and because Cloudflare DNS challenge gives you wildcard certificates covering all subdomains at once. If you use a different DNS provider, Traefik supports over 60 providers.
- Raspberry Pi (3B+, 4 or 5) running Raspberry Pi OS Lite 64-bit
- Docker and Docker Compose installed (see the Cloudflare Tunnel guide for Docker installation)
- A domain with DNS managed by Cloudflare
- A Cloudflare API token with
Zone:DNS:Editpermission - Ports 80 and 443 forwarded in your router to the Pi's IP (or use Cloudflare Tunnel — see below)
http://localhost:80. HTTPS terminates at Cloudflare rather than Traefik, but you keep your router closed.
Project structure
Create a folder for Traefik and set up the files you need:
mkdir -p ~/traefik/data
cd ~/traefik
# Create an empty acme.json for certificates — MUST have permission 600
touch data/acme.json
chmod 600 data/acme.json
The folder structure when you are done:
traefik/
├── docker-compose.yml
├── data/
│ ├── traefik.yml # static configuration
│ └── acme.json # Let's Encrypt certificates (auto-generated)
Create the Docker network
Traefik needs to reach your other containers. A shared Docker network handles that. Create it once:
docker network create traefik-net
Every service that should get HTTPS via Traefik must join this network.
Cloudflare API token
Traefik needs to create DNS records at Cloudflare to prove domain ownership to Let's Encrypt. That requires an API token, not your global API key.
Go to Cloudflare Dashboard → My Profile → API Tokens → Create Token. Use the Edit zone DNS template, restrict it to the specific domain you are using, and copy the token. It is shown only once.
Create a .env file in ~/traefik/ with the token:
# ~/traefik/.env
CF_DNS_API_TOKEN=your_cloudflare_api_token_here
.env files to a repository. This token gives write access to your DNS records.
traefik.yml — static configuration
Create ~/traefik/data/traefik.yml. This is the static configuration Traefik reads at startup:
# ~/traefik/data/traefik.yml
api:
dashboard: true # enable the web dashboard
insecure: false # dashboard via HTTPS only
log:
level: INFO # DEBUG is very verbose — use only when troubleshooting
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure # force all HTTP to HTTPS
scheme: https
websecure:
address: ":443"
certificatesResolvers:
cloudflare:
acme:
email: [email protected] # your email for Let's Encrypt notices
storage: /etc/traefik/acme.json
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
- "1.0.0.1:53"
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false # containers are not exposed unless they have labels
docker-compose.yml
Create ~/traefik/docker-compose.yml:
# ~/traefik/docker-compose.yml
services:
traefik:
image: traefik:v3.0
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
ports:
- "80:80"
- "443:443"
environment:
- CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./data/traefik.yml:/etc/traefik/traefik.yml:ro
- ./data/acme.json:/etc/traefik/acme.json
networks:
- traefik-net
labels:
- "traefik.enable=true"
# Dashboard route — replace your-domain.com with your actual domain
- "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.your-domain.com`)"
- "traefik.http.routers.traefik-dashboard.entrypoints=websecure"
- "traefik.http.routers.traefik-dashboard.tls.certresolver=cloudflare"
- "traefik.http.routers.traefik-dashboard.service=api@internal"
# Basic auth for dashboard — generate with: echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g
- "traefik.http.routers.traefik-dashboard.middlewares=traefik-auth"
- "traefik.http.middlewares.traefik-auth.basicauth.users=admin:$$apr1$$xxxxxxxx$$xxxxxxxxxxxxxxxxxxxxxxxxx"
networks:
traefik-net:
external: true
Generate the basic auth password
You need a hashed password string for dashboard access. Install apache2-utils and run:
sudo apt install apache2-utils -y
# Generate hashed password — replace admin and your_password
echo $(htpasswd -nb admin your_password) | sed -e s/\\$/\\$\\$/g
Copy the output and paste it into the basicauth.users label in docker-compose.yml. The double $$ characters are required because Docker Compose would otherwise interpret $ as a variable reference.
Start Traefik
cd ~/traefik
docker compose up -d
# Follow the logs
docker compose logs -f
The first time Traefik starts it fetches a certificate from Let's Encrypt via Cloudflare DNS challenge. That typically takes 30–120 seconds. You will see something like Obtained certificate for domains in the log when it succeeds.
caServer: "https://acme-staging-v02.api.letsencrypt.org/directory" under acme: in traefik.yml to use the staging server. Staging certificates are not trusted by browsers, but you will not hit rate limits.
Connect a service — Nextcloud example
Add Traefik labels to your service and attach it to the traefik-net network. Traefik detects it and creates the routing automatically.
If you already have Nextcloud running, add this to your Nextcloud docker-compose.yml:
services:
nextcloud:
image: nextcloud:latest
# ... your existing settings ...
networks:
- traefik-net # REQUIRED
- nextcloud-internal # your internal network for the database
labels:
- "traefik.enable=true"
- "traefik.http.routers.nextcloud.rule=Host(`nextcloud.your-domain.com`)"
- "traefik.http.routers.nextcloud.entrypoints=websecure"
- "traefik.http.routers.nextcloud.tls.certresolver=cloudflare"
- "traefik.http.services.nextcloud.loadbalancer.server.port=80"
networks:
traefik-net:
external: true
nextcloud-internal:
internal: true # no internet access from this network
Restart the Nextcloud stack with docker compose up -d. Traefik picks up the labels and Nextcloud is reachable at https://nextcloud.your-domain.com within 30 seconds — with a valid certificate.
Connect Pi-hole or AdGuard Home
Same principle. Add labels and traefik-net to your Pi-hole or AdGuard Home service:
labels:
- "traefik.enable=true"
- "traefik.http.routers.pihole.rule=Host(`pihole.your-domain.com`)"
- "traefik.http.routers.pihole.entrypoints=websecure"
- "traefik.http.routers.pihole.tls.certresolver=cloudflare"
# Pi-hole web interface runs on port 80 inside the container
- "traefik.http.services.pihole.loadbalancer.server.port=80"
Wildcard certificate (optional)
Instead of one certificate per subdomain you can get a single wildcard covering *.your-domain.com. DNS challenge is required for wildcards — which you are already using with Cloudflare. No changes to traefik.yml. Just add these labels to each router:
- "traefik.http.routers.nextcloud.tls.domains[0].main=your-domain.com"
- "traefik.http.routers.nextcloud.tls.domains[0].sans=*.your-domain.com"
Troubleshooting
Certificate not arriving
Check the Traefik log with docker compose logs traefik | grep -i acme. The most common causes:
- Wrong API token: Token is missing
Zone:DNS:Editpermission or is scoped to the wrong zone. - acme.json has wrong permissions: Run
chmod 600 ~/traefik/data/acme.json. - DNS propagation: Cloudflare DNS is usually fast, but give it 60 seconds.
Service not reachable
Run docker network inspect traefik-net and confirm that both the Traefik container and your service container appear in the network. If either is missing, routing is impossible regardless of what the labels say.
Dashboard shows "404 page not found"
The Traefik dashboard requires an exact match on the host configured in the label rule. Test directly: curl -H "Host: traefik.your-domain.com" http://localhost. If that works, it is a DNS issue — the domain is not pointing at the Pi's IP.
Redirect loop
This usually happens when Traefik runs behind Cloudflare with SSL mode set to "Flexible" in Cloudflare. Cloudflare sends HTTPS to Traefik, Traefik redirects to HTTPS, and it loops. Set Cloudflare SSL to "Full (strict)" or "Full" if you are using Cloudflare Tunnel.
Next steps
Traefik is running. Connect the rest of your services:
- Connect Nextcloud as shown above
- Combine with Cloudflare Tunnel to avoid open ports entirely
- Add Traefik rate limiting and IP filtering middleware for publicly exposed services
- Set up security headers middleware (HSTS, X-Frame-Options etc.) once and apply it to all routes
Frequently asked questions
Can I use Traefik without a domain name?
No. Traefik uses host-based routing and requires a domain. For LAN-only use without a domain, Nginx Proxy Manager is a simpler alternative with a GUI and direct IP support.
What is the difference between Traefik and Nginx Proxy Manager?
Nginx Proxy Manager has a GUI and is faster to get started with. Traefik is configured in code via Docker labels, which makes it better suited to automated setups with no GUI dependency. Traefik renews SSL certificates automatically.
Does Traefik run on a Raspberry Pi Zero?
Pi Zero 2W runs it with limited headroom. The original Pi Zero has only 512 MB RAM — too tight for production with more than one service. Use a Pi 3B+ or newer for stable operation.
Do I need to open ports on my router?
Only ports 80 and 443 if you want services reachable from the internet. If you use Cloudflare Tunnel instead of direct exposure, no open ports are needed at all.
Sources
- Traefik official documentation — traefik.io — complete reference for configuration, providers and middlewares
- Traefik ACME/Let's Encrypt documentation — DNS challenge setup and list of supported providers
- Let's Encrypt rate limits — letsencrypt.org — what you hit if you test too aggressively
- Cloudflare API Tokens — developers.cloudflare.com — create scoped tokens instead of using global API keys