Traefik on Raspberry Pi with Docker and Wildcard Certs

Traefik Docker Wildcard Setup on Raspberry Pi

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.json file 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.json file 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.
Traefik on Raspberry Pi with Docker and wildcard certs architecture diagram showing incoming requests on ports 80 and 443 routed by Traefik to labelled containers with wildcard TLS certificate from Let's Encrypt DNS-01 challenge

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

ComponentMinimumRecommended
Raspberry PiPi 4 (2GB)Pi 4 (4GB) or Pi 5
Storage32GB microSDUSB 3.0 SSD
Power supply5V/3A USB-COfficial Pi 4 or Pi 5 adapter
NetworkWi-FiGigabit 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

LabelPurpose
traefik.enable=trueExpose this container to Traefik
traefik.http.routers.NAME.ruleHostname matching rule
traefik.http.routers.NAME.entrypointsWhich entrypoint handles this router
traefik.http.routers.NAME.tls.certresolverWhich cert resolver to use
traefik.http.services.NAME.loadbalancer.server.portOverride port if container exposes multiple
traefik.http.routers.NAME.middlewaresApply 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


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.