Traefik on Raspberry Pi with Docker and wildcard certs gives you a reverse proxy that routes multiple subdomains to different containers under a single wildcard TLS certificate, all managed automatically. Install Docker, write a static traefik.yml config, add a DNS API token for the Let’s Encrypt DNS-01 challenge, and label each container with routing rules. Traefik handles certificate issuance and renewal without manual intervention.
Last tested: Raspberry Pi OS Bookworm Lite 64-bit | April 11, 2026 | Raspberry Pi 4 Model B (4GB) | Traefik v2.11 | Docker 26
Key Takeaways
- Traefik runs well on Raspberry Pi 4 for small to medium container stacks. It has a low memory footprint and handles automatic HTTPS without additional tooling.
- Wildcard certificates require the DNS-01 challenge. HTTP-01 challenge cannot issue wildcard certs. You need a DNS provider with API access.
- Docker labels on each container define routing rules. A typo in a label is the most common cause of 404 errors and routing failures.
- The
acme.jsonfile must exist and have permissions set to 600 before Traefik starts or certificate storage will fail. - Run Traefik from a USB SSD rather than microSD. The
acme.jsonfile and container logs generate continuous writes that wear SD cards quickly. - Never expose the Traefik dashboard without authentication. Add basicauth middleware before making it publicly accessible.

Why Traefik on Raspberry Pi
Traefik is a practical reverse proxy for Pi homelab stacks for several reasons. Its memory footprint is small enough that it runs alongside a handful of containers without pressure on a 2GB or 4GB Pi. The Docker provider reads container labels directly, so adding a new service to the proxy requires only a few lines in the compose file rather than editing a separate config file. Certificate management is built in. Traefik requests, stores, and renews Let’s Encrypt certificates automatically. And the built-in dashboard gives a live view of routers, services, and certificate status without installing separate monitoring tools.
The Pi’s Gigabit Ethernet on Pi 4 and Pi 5 handles the network throughput that a typical homelab stack needs. The limiting factor for most builds is not the reverse proxy. It is the backend services themselves. Traefik’s overhead is low enough that it is rarely the bottleneck.
Hardware and OS Setup
Recommended hardware
| Component | Minimum | Recommended |
|---|---|---|
| Raspberry Pi | Pi 4 (2GB) | Pi 4 (4GB) or Pi 5 |
| Storage | 32GB microSD | USB 3.0 SSD |
| Power supply | 5V/3A USB-C | Official Pi 4 or Pi 5 adapter |
| Network | Wi-Fi | Gigabit Ethernet |
Storage choice matters more for this build than for most Pi projects. Traefik writes to acme.json on every certificate issuance and renewal. Container logs add more writes. An SD card under sustained write load will fail faster than one used for read-heavy workloads. A USB SSD eliminates this concern and also improves Docker image pull and container start times. See Booting Raspberry Pi from USB SSD and Preventing SD Card Corruption on Raspberry Pi.
Flash and configure Raspberry Pi OS Bookworm Lite
Use Raspberry Pi Imager to flash Raspberry Pi OS Bookworm Lite 64-bit. In the Imager’s advanced settings (the gear icon), configure hostname, SSH credentials, and Wi-Fi before writing. This replaces the older ssh file and wpa_supplicant.conf methods which no longer apply on Bookworm.
After first boot, update fully:
sudo apt update && sudo apt full-upgrade -y
sudo reboot
Assign a static IP via your router’s DHCP reservation using the Pi’s MAC address. This is more reliable than editing network config files directly on Bookworm where NetworkManager manages interfaces by default.
Step 1: Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# Add your user to the docker group to run commands without sudo
sudo usermod -aG docker $USER
# Log out and back in for the group change to take effect
# Then verify:
docker compose version
Docker Compose V2 is included with current Docker installations. The command is docker compose (no hyphen). If docker compose version returns an error, the Docker installation did not complete successfully. Rerun the install script.
Expected result: docker compose version returns a version string such as Docker Compose version v2.x.x.
Step 2: Create the Traefik Directory Structure
mkdir -p ~/traefik
cd ~/traefik
# Create the acme.json file with correct permissions BEFORE starting Traefik
touch acme.json
chmod 600 acme.json
The acme.json file must exist with 600 permissions before Traefik starts. If Traefik creates it, it may set incorrect permissions and refuse to use it for certificate storage. Setting it up manually first avoids this.
Your directory structure should look like this:
traefik/
├── docker-compose.yml
├── traefik.yml
├── acme.json # chmod 600
└── .env # DNS API credentials
Expected result: ls -la ~/traefik/acme.json shows -rw------- permissions.
Step 3: Write the Static Configuration
Create ~/traefik/traefik.yml with the following content. This is Traefik’s static configuration, read once at startup:
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
api:
dashboard: true
log:
level: INFO
providers:
docker:
exposedByDefault: false
certificatesResolvers:
letsencrypt:
acme:
email: you@example.com
storage: /letsencrypt/acme.json
dnsChallenge:
provider: cloudflare
delayBeforeCheck: 0
Key points in this config: exposedByDefault: false means containers are not automatically exposed. You must explicitly enable each one with a label. The HTTP-to-HTTPS redirect is handled at the entrypoint level rather than per-container middleware, which keeps container labels cleaner. Replace cloudflare with your DNS provider’s identifier from the Traefik ACME providers list.
Step 4: Create the DNS API Credentials
Create ~/traefik/.env with your DNS provider API credentials. For Cloudflare:
CF_API_EMAIL=you@example.com
CF_API_TOKEN=your-cloudflare-api-token
The Cloudflare API token needs only the Zone:DNS:Edit permission scoped to the specific zone. Do not use the Global API Key. Create a scoped token in the Cloudflare dashboard under My Profile, API Tokens, Create Token. Add this file to .gitignore immediately if you are using version control:
echo ".env" >> .gitignore
Other DNS providers use different variable names. Check the Traefik documentation for your provider’s required environment variables. DuckDNS uses DUCKDNS_TOKEN. DigitalOcean uses DO_AUTH_TOKEN.
Expected result: cat .env shows your credentials. cat .gitignore contains .env.
Step 5: Write the Docker Compose File
services:
traefik:
image: traefik:v2.11
container_name: traefik
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./traefik.yml:/etc/traefik/traefik.yml:ro
- ./acme.json:/letsencrypt/acme.json
- /var/run/docker.sock:/var/run/docker.sock:ro
env_file:
- .env
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.example.com`)"
- "traefik.http.routers.traefik-dashboard.service=api@internal"
- "traefik.http.routers.traefik-dashboard.entrypoints=websecure"
- "traefik.http.routers.traefik-dashboard.tls.certresolver=letsencrypt"
- "traefik.http.routers.traefik-dashboard.middlewares=dashboard-auth"
- "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$apr1$$yourhash"
whoami:
image: traefik/whoami
container_name: whoami
restart: always
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.example.com`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
Two important details in this compose file. First, the dashboard uses traefik/whoami as the test service. The old containous/whoami image is unmaintained; use traefik/whoami instead. Second, the basicauth password hash uses double dollar signs ($$) in Docker Compose files because a single $ is interpreted as a variable substitution. Generate the hash with:
sudo apt install apache2-utils -y
htpasswd -nb admin yourpassword
Copy the output and replace every $ with $$ in the compose label.
Step 6: Start Traefik
cd ~/traefik
docker compose up -d
# Watch logs during first startup to confirm certificate issuance
docker compose logs -f traefik
On first start, Traefik contacts Let’s Encrypt, triggers the DNS-01 challenge by adding a TXT record via your DNS API, waits for propagation, then downloads and stores the wildcard certificate in acme.json. This process typically takes 30 to 90 seconds. Watch the logs for Certificate obtained successfully or errors.
If the DNS challenge fails, check in order: API token permissions, DNS provider variable names in .env, and whether the DNS TXT record was actually created (use dig TXT _acme-challenge.example.com to verify).
Expected result: Logs show Certificate obtained successfully. Visiting https://whoami.example.com returns a page showing request headers. Visiting https://traefik.example.com behind the basicauth prompt shows the Traefik dashboard with routers and services listed in green.
Adding More Services
Every additional service follows the same label pattern. Add the service to the same docker-compose.yml and use a unique router name per service:
nextcloud:
image: nextcloud
container_name: nextcloud
restart: always
labels:
- "traefik.enable=true"
- "traefik.http.routers.nextcloud.rule=Host(`cloud.example.com`)"
- "traefik.http.routers.nextcloud.entrypoints=websecure"
- "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
Use subdomains rather than path-based routing (cloud.example.com rather than example.com/cloud). Path-based routing requires additional middleware and many applications do not handle path prefixes correctly without extra configuration. The wildcard certificate covers all subdomains automatically so there is no certificate overhead for adding new services.
Label reference
| Label | Purpose |
|---|---|
traefik.enable=true | Expose this container to Traefik |
traefik.http.routers.NAME.rule | Hostname matching rule |
traefik.http.routers.NAME.entrypoints | Which entrypoint handles this router |
traefik.http.routers.NAME.tls.certresolver | Which cert resolver to use |
traefik.http.services.NAME.loadbalancer.server.port | Override port if container exposes multiple |
traefik.http.routers.NAME.middlewares | Apply middleware to this router |
The router name (NAME) must be unique across all containers. Using the service name as the router name keeps things predictable. Traefik detects label changes on running containers automatically. No restart is needed when adding or modifying labels.
Securing the Setup
Firewall with UFW
sudo apt install ufw -y
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status
Only ports 22, 80, and 443 need to be open externally. Internal service ports (8080, 8123, etc.) should not be published in the compose file. Traefik reaches them over the Docker internal network. Publishing them externally bypasses the proxy entirely.
Dashboard authentication
The dashboard is already behind basicauth in the compose file above. Do not remove this middleware unless you are on a private network with no external access. The dashboard exposes routing rules, middleware configs, and service information that should not be public.
Keep API tokens minimal
Cloudflare API tokens for DNS-01 need only Zone:DNS:Edit on the specific zone. A token with full account access is unnecessary and creates significant risk if the .env file is ever exposed. Rotate tokens periodically and update .env followed by docker compose up -d traefik to apply.
Monitoring and Maintenance
Checking certificate status
# View Traefik logs
docker compose logs -f traefik
# Check acme.json for certificate details (do not edit)
cat acme.json | python3 -m json.tool | grep -A5 "domain"
# Verify DNS challenge record (replace with your domain)
dig TXT _acme-challenge.example.com
Traefik renews certificates automatically when they are within 30 days of expiry. If renewal fails, it is almost always the DNS API credentials. Check that the API token has not expired or been revoked, and that the provider environment variable names in .env match exactly what Traefik’s documentation specifies for that provider.
Log rotation
Docker logs accumulate continuously. Configure log rotation to prevent storage exhaustion on a long-running Pi setup. Add to /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
sudo systemctl restart docker
This limits each container to three 10MB log files, capping log storage at 30MB per container. For a Pi running several containers continuously this is an important maintenance step. See also Setting Up zram on Raspberry Pi for reducing overall write pressure on the storage medium.
Auto-updating containers with Watchtower
watchtower:
image: containrrr/watchtower
container_name: watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
restart: always
environment:
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_SCHEDULE=0 0 4 * * *
Watchtower checks for updated images and replaces running containers. The schedule above runs at 4am daily. Set WATCHTOWER_CLEANUP=true to remove old images after updates, which prevents storage accumulation on a Pi with limited space.
Troubleshooting
ACME challenge failed
Check the API token has DNS edit permissions for the correct zone. Confirm the environment variable names in .env match Traefik’s provider documentation exactly. They are case-sensitive. Run dig TXT _acme-challenge.example.com @8.8.8.8 to check whether the DNS TXT record was actually created. If it was not, the API call failed silently.
404 Not Found for a service
A 404 from Traefik means no router matched the request. Check the container label for the correct hostname rule. Confirm traefik.enable=true is present. Check the Traefik dashboard to see if the router is listed. If it is not, Traefik did not read the labels, which usually means the container is on a different Docker network than Traefik.
Dashboard shows no routers
Traefik uses the Docker socket to read labels. If the socket is not mounted (/var/run/docker.sock:/var/run/docker.sock:ro in volumes) or the container does not have read access to it, no labels will be read. Also confirm the container and Traefik are on the same Docker network. Containers on isolated networks are not visible to Traefik’s Docker provider.
Certificate not renewing
Check acme.json for the certificate expiry date. Check docker compose logs traefik around the expected renewal time (30 days before expiry). Most renewal failures are caused by expired or revoked DNS API tokens. Replace the token in .env and run docker compose up -d traefik to restart with the new credentials.
Pi running hot or slow
vcgencmd measure_temp
vcgencmd get_throttled
htop
Traefik itself uses minimal CPU and RAM. If the Pi is running hot, the bottleneck is almost certainly a backend container rather than Traefik. Check which container is consuming CPU in htop or docker stats. Active cooling is recommended for any Pi running multiple containers continuously.
FAQ
Can I use Traefik without wildcard certificates?
Yes. Remove the dnsChallenge block and use httpChallenge instead. Traefik will issue individual certificates per subdomain using HTTP-01. Each subdomain needs to be publicly reachable on port 80 for the challenge to succeed. Wildcard certificates via DNS-01 are simpler when you have multiple subdomains and a supported DNS provider.
What is the difference between static and dynamic configuration?
Static configuration (traefik.yml) is read once at startup and defines entrypoints, providers, and certificate resolvers. Changing it requires a Traefik restart. Dynamic configuration comes from Docker labels on running containers and is monitored continuously. Traefik picks up label changes without restarting.
Can I run Traefik alongside Nginx?
Technically yes, but both cannot bind to ports 80 and 443 simultaneously on the same host. You would need to route specific traffic through one and use different ports for the other. For a Pi homelab, running both adds complexity without benefit. Choose one reverse proxy for the stack.
Why is my certificate not renewing?
The most common cause is an expired or revoked DNS API token. Traefik cannot issue a DNS challenge if the API call fails. Check the logs around the renewal time, verify the token is still valid in your DNS provider’s dashboard, and update .env if needed. Also confirm acme.json permissions are still 600. Some system updates or backup tools reset file permissions.
Can I use a free DNS provider for wildcard certs?
Yes, if the provider supports the DNS-01 challenge via API. DuckDNS works with Traefik’s built-in DuckDNS provider. FreeDNS and some others require custom scripting. Check the Traefik ACME providers list for supported providers. The domain itself can be a free subdomain as long as the DNS API supports TXT record creation.
References
- https://doc.traefik.io/traefik/
- https://letsencrypt.org/docs/challenge-types/
- https://developers.cloudflare.com/api/
- https://doc.traefik.io/traefik/https/acme/#providers
About the Author
Chuck Wilson has been programming and building with computers since the Tandy 1000 era. His professional background includes CAD drafting, manufacturing line programming, and custom computer design. He runs PidiyLab in retirement, documenting Raspberry Pi and homelab projects that he actually deploys and maintains on real hardware. Every article on this site reflects hands-on testing on specific hardware and OS versions, not theoretical walkthroughs.
Last tested hardware: Raspberry Pi 4 Model B (4GB). Last tested OS: Raspberry Pi OS Bookworm Lite 64-bit. Traefik v2.11, Docker 26.

