Traefik on Raspberry Pi with Docker and Wildcard Certs
Introduction
You’re probably thinking, “Why would anyone want to run a reverse proxy on a Raspberry Pi?” Well, because we can. And it’s cheap, quiet, and surprisingly capable if you treat it right. In this guide, we’ll get Traefik running on your Pi, toss in Docker, and throw some wildcard certificates on top like sprinkles on a busted cupcake. I’ll show you the actual setup with wildcard certs using DNS challenges, how to label your containers without pulling your hair out, and what not to do unless you want your Pi to crash mid-stream. Sound fun? Thought so.
Key Takeaways
- Raspberry Pi runs Traefik just fine for small stacks.
- Wildcard certs need DNS API access, not HTTP.
- Docker labels make or break routing, so triple-check them.
- Keep your system secure and monitor cert renewals.
- Don’t underestimate how fast a Pi can fill its SD card.
Why Traefik Works Great on a Raspberry Pi
You might be wondering if your little Pi can handle a full-blown reverse proxy. Short answer: yes. Long answer: yes, but don’t expect it to run 50 containers and still brew your coffee.
Let’s break it down:
- Low resource footprint: Traefik doesn’t eat up RAM like it’s at an all-you-can-eat buffet. It runs fine on 512MB if you’re not going wild.
- Built-in dashboard: No need to bolt on Grafana just to see what’s routing where. Traefik has a clean, usable dashboard built in.
- Automatic HTTPS: It can grab and renew Let’s Encrypt certs with zero human input. If only it could do taxes.
- Native Docker integration: Traefik talks to Docker directly. No crazy config files just to make containers reachable.
If your Pi already runs a few containers, adding Traefik won’t tip it over the edge. Just maybe avoid pairing it with Nextcloud, Plex, and Bitcoin mining. That’s a meltdown cocktail.
Choosing Your Raspberry Pi Model and OS
All right, before you go flashing SD cards like a blackjack dealer, let’s pick the right gear.
Recommended Models:
- Raspberry Pi 4B (2GB or higher)
- Raspberry Pi 5 (if you’re feeling fancy)
- Compute Module 4 with IO board
Older models like the 3B+ will technically work, but you’ll start feeling the lag once you add more containers.
Storage Tip:
Use an SSD over SD cards unless you enjoy file corruption and mystery crashes. SD cards are like cheap sunglasses—they work until they don’t.
OS Choice:
Go with Raspberry Pi OS Lite (based on Debian Bookworm). It’s minimal, boots fast, and doesn’t waste cycles rendering a GUI you’ll never use.
Headless Setup Basics:
- Flash the image using Raspberry Pi Imager
- Enable SSH by dropping an empty
sshfile in/boot - Set up Wi-Fi (if needed) with
wpa_supplicant.conf - Assign a static IP in your router or in
/etc/dhcpcd.conf
Once that’s done, boot it up, SSH in, and we’re cooking.
Setting Up Docker and Docker Compose on Raspberry Pi
Now comes the fun part: installing Docker without breaking your Pi or your spirit.
Install Docker Engine:
Run the official convenience script:
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
Post-install Setup:
Unless you love typing sudo every time, add your user to the Docker group:
sudo usermod -aG docker $USER
Then reboot. Or log out and back in if you hate waiting.
Install Docker Compose V2:
It’s included with newer Docker versions, but make sure you have it:
docker compose version
If that command gives you sass, follow Docker’s guide to install Compose CLI.
Folder Structure Tip:
Organize your stuff. A folder for Traefik, a folder per service. Don’t be that person with 300 files in /home/pi.
Use ARM-Compatible Images:
Your Pi doesn’t speak x86. Make sure you’re using images built for arm or arm64. Most decent maintainers label their tags accordingly.
At this point, your Pi is Docker-ready and just waiting for a reason to complain.
Traefik Basics and Static Configuration
All right, let’s talk Traefik. This thing runs in two modes: static and dynamic config. Static is what it needs to boot, dynamic is everything it watches after that.
Main Concepts:
- Entrypoints: Where Traefik listens (e.g., :80, :443)
- Routers: Match requests to rules
- Services: Where it sends the request after routing
- Middlewares: Stuff it does to the request on the way
Basic traefik.yml Static Config:
entryPoints:
web:
address: ":80"
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
Where It Goes:
Mount that file into /etc/traefik/traefik.yml. Keep it tidy. You’ll also want to create an acme.json file and chmod 600 it so Traefik doesn’t throw a tantrum.
That’s your base config. From here, the real fun begins with dynamic stuff like labels.
Dynamic Configuration with Docker Labels
Now for the part where people either fall in love with Traefik or rage-quit. Labels. They’re magical, confusing, and occasionally rage-inducing.
How Labels Work:
Labels are added to your Docker containers (usually in docker-compose.yml). They tell Traefik how to route, secure, and modify traffic.
Bare Minimum Labels:
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(`app.example.com`)"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
Add Middleware Like a Boss:
- "traefik.http.middlewares.force-https.redirectscheme.scheme=https"
- "traefik.http.routers.myapp.middlewares=force-https"
Docker Compose Tip:
If you’re debugging and something’s not working, 90% of the time it’s a typo in a label. YAML is sensitive. So is Traefik. Double-check quotes, backticks, and naming.
Pro Move:
Use anchors and aliases in YAML if you’re repeating labels across services. It’s not fancy, it’s just sane.
Once you get your first container routing securely with HTTPS, you’ll feel like a wizard. Then you’ll try adding a second one and realize you forgot a slash somewhere.
DNS Challenge for Wildcard Certificates
Let’s talk wildcard certs. If you want *.example.com to work without manually renewing 50 certs, you need the DNS challenge. Not the HTTP one. Trust me.
Why DNS-01?
Because Let’s Encrypt only hands out wildcard certs via DNS-01. That means proving ownership by adding special TXT records through your DNS provider.
What You Need:
- A domain name (buy one, or use a free DNS like DuckDNS)
- A DNS provider that supports API access (Cloudflare, DigitalOcean, etc.)
- An API token with DNS edit permissions
Using Cloudflare? Here’s what your traefik.yml section might look like:
certificatesResolvers:
letsencrypt:
acme:
email: you@example.com
storage: /letsencrypt/acme.json
dnsChallenge:
provider: cloudflare
delayBeforeCheck: 0
Don’t Forget the Env Variables:
Put these in a .env file or pass them securely:
CF_API_EMAIL=you@example.com
CF_API_TOKEN=some-long-token
Permissions Reminder:
Your API token should have just enough rights to edit DNS records. Not full account access unless you like living on the edge.
When you boot up Traefik, it’ll hit Let’s Encrypt, trigger a DNS challenge, and you’ll get your shiny wildcard cert. If it fails, don’t panic—check your logs.
Sample Docker Compose Stack with Wildcard Cert
All right, here’s where it all comes together. You’ve got Docker, Traefik, DNS API creds, now let’s glue it with a working docker-compose.yml.
Directory Structure:
traefik/
├── docker-compose.yml
├── traefik.yml
├── acme.json
└── .env
Example docker-compose.yml:
version: '3.8'
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.api.rule=Host(`traefik.example.com`)"
- "traefik.http.routers.api.service=api@internal"
- "traefik.http.routers.api.entrypoints=websecure"
- "traefik.http.routers.api.tls.certresolver=letsencrypt"
whoami:
image: containous/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"
Permissions Check:
touch acme.json
chmod 600 acme.json
This setup gets you Traefik with a working dashboard, wildcard TLS, and one sample service. Add more containers the same way and adjust your labels.
Common Pitfalls and Fixes
All right, you’ve probably hit a snag or five by now. Don’t worry, you’re not cursed, just in the club.
“ACME challenge failed”
- Check your API token permissions
- Ensure your domain’s DNS is actually pointing to the provider you’re using
- Look at Traefik’s logs, not your browser
“404 Not Found”
- Likely a label typo or wrong hostname
- Traefik only routes requests that match a defined router rule
“Wildcard cert not issuing”
- You’re using HTTP challenge instead of DNS
- Make sure you set up
certificatesResolverscorrectly - Check propagation delays for DNS TXT records
“Dashboard won’t load”
- Try accessing via
traefik.example.com, not the IP - Confirm the dashboard router exists and has TLS enabled
“Why is my Pi on fire?”
- Too many containers, SD card nearing death, or running at full CPU all the time
- Monitor with
htop, add a heatsink, and don’t run a Minecraft server on the side
Debugging Traefik is 80% labels and 20% DNS grief. Logs are your best friend. Look at both docker logs traefik and acme.json content if certs fail.
Traefik Dashboard and Logs
Now for the part where you stare at a web UI and pretend you’re a sysadmin.
Accessing the Dashboard:
- Make sure it’s enabled in
traefik.yml:
api:
dashboard: true
- Route it with a secure label:
- "traefik.http.routers.api.rule=Host(`traefik.example.com`)"
- "traefik.http.routers.api.service=api@internal"
- Then visit
https://traefik.example.com
Logging Levels:
- INFO is the default. Use DEBUG only if you like noise.
- Traefik logs go to stdout. Capture them with:
docker logs -f traefik
acme.json:
- This is where certs live. If something’s wrong with issuance, it’ll show up here.
- Format is JSON. You can open it, just don’t edit it.
Docker Compose Logs:
- For multi-container stacks:
docker-compose logs -f
System Logs on the Pi:
- Check
journalctl -u docker.service dmesgif your USB SSD is freaking out
If your dashboard shows routers and services lighting up green, you’re winning. If it’s blank, check your labels and DNS.
Storage, Performance, and Reliability on a Pi
Running services 24/7 on a Pi sounds great until it corrupts your SD card and ghosts you.
Use an SSD:
- SD cards wear out. Fast.
- USB 3.0 SSDs are faster, more durable, and won’t randomly die mid-upgrade.
Mount Points Matter:
- Traefik’s acme.json should be on persistent storage.
- Use absolute paths in your
docker-compose.yml
Watch Resource Usage:
htopshows CPU and RAMvcgencmd measure_tempfor thermal datadf -hto see how full your storage is
Common Bottlenecks:
- Network speed: Pi 3 has 100Mbps, Pi 4 and 5 have Gigabit
- USB bus contention: don’t plug SSD and webcam into the same bus
- CPU: expect slowdowns with encryption or lots of containers
Preventative Maintenance:
- Set up regular reboots (cron or systemd timer)
- Keep your firmware and OS updated
- Don’t yank the power cord like it’s a toaster
A little care goes a long way. Or just ignore it until it breaks, then spend your Sunday rebuilding it. Your call.
Securing Your Setup
All right, you’ve got your Pi exposed to the internet. Time to stop tempting fate and lock it down.
Use HTTPS Only:
- Redirect all HTTP traffic to HTTPS using middleware
- "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https"
- "traefik.http.routers.myapp.middlewares=https-redirect"
Firewall the Pi:
- Install
ufw:
sudo apt install ufw
sudo ufw allow 22
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable
- Or use iptables if you’re feeling brave
Block Unused Ports:
- Only expose 80 and 443
- Avoid publishing internal ports unless needed
Enable Authentication for the Dashboard:
- "traefik.http.middlewares.auth.basicauth.users=admin:$apr1$xyz..."
- "traefik.http.routers.api.middlewares=auth"
Use htpasswd to generate that hashed password. Don’t type it by hand unless you’re a cryptographic wizard.
Keep API Tokens Secret:
- Never hard-code them in the compose file
- Use
.envfiles and.gitignorethem
Securing Traefik on a Pi isn’t hard, but skipping it will land you on Shodan faster than you can say “port 443 open.”
Monitoring and Maintaining the Stack
If you want your stack to stay up longer than a paper towel tent, you need some basic upkeep.
Watchtower for Auto-Updates:
- Automatically updates Docker images
- Add it to your
docker-compose.ymlwith:
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
restart: always
Use Portainer for GUI Management:
- Nice dashboard to check container health
- Deploy via Docker and access it like any other service
Monitor Logs:
- Use
docker-compose logs -fregularly - Set up log rotation or you’ll eat up storage
Check Cert Renewal:
- Traefik renews automatically, but check
acme.jsontimestamps - Expired cert? Probably your DNS API creds failed
API Key Rotation:
- Most DNS providers expire or revoke tokens
- Replace them in your
.envand restart Traefik
Maintenance is boring but less painful than rebuilding your setup during a storm because your cert expired and nobody can access their baby monitor feed.
Adding More Services Behind Traefik
All right, now that Traefik is doing its thing, let’s add more containers behind it without creating chaos.
Popular Self-Hosted Tools:
- Nextcloud
- Jellyfin or Plex
- Bitwarden
- Home Assistant
- Grafana
Add Services Like This:
services:
nextcloud:
image: nextcloud
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, Not Paths:
app.example.comis easier to route thanexample.com/app- Less middleware, fewer headaches
Label Consistency Helps:
- Copy-paste from working containers
- Keep routers, services, and middleware names predictable
Testing Routes:
- Use
curl -vordigto confirm DNS is working - Watch Traefik logs when spinning up new services
Scaling out is just repeating the process. Once Traefik’s handling two services, it’ll handle twenty. Your Pi might not, but Traefik can.
Scaling or Migrating Beyond Raspberry Pi
At some point, your Pi might start wheezing. That’s your cue to scale or migrate.
Backing Up Your Config:
- Save
traefik.yml,acme.json,.env, and your compose files - Back up volumes too, if they hold data (Nextcloud, databases)
Move to a New Host:
- Copy files over via
rsyncorscp - Rebuild with
docker-compose up -d - Update your DNS records to the new IP
Test on a VM First:
- Clone your setup on a virtual machine
- Helps catch dependency issues or path weirdness
Cloud-Friendly:
- Traefik runs great on small VPS instances (1–2 GB RAM)
- Works with Kubernetes, Docker Swarm, or plain Compose
Considerations for Scaling:
- Use external storage (NFS, Samba, cloud)
- Offload heavy services (media servers, DBs) to more powerful machines
You don’t have to abandon your Pi—just promote it to light-duty stuff like Pihole or Home Assistant once your needs outgrow it.
FAQs
Q: Can I use Traefik without wildcard certs?
A: Sure, but wildcard certs simplify your life when you’ve got multiple subdomains.
Q: What’s the difference between static and dynamic config?
A: Static is read once on startup, dynamic gets monitored and updated live.
Q: Can I run Traefik and NGINX together?
A: Technically yes, but unless you love config file spaghetti, pick one.
Q: Why isn’t my cert renewing?
A: Usually bad API keys, broken DNS, or expired tokens. Logs will tell you.
Q: Can I use a free DNS provider?
A: Yep. DuckDNS, FreeDNS, and some others work fine with the DNS challenge.

