How to Set Up Traefik v3 on Fedora Server with Podman: Automated SSL and Routing

Fedora tutorial - IT technology blog
Fedora tutorial - IT technology blog

When migrating around 20 internal microservices from Docker Swarm to Fedora Server running Podman, my biggest hurdle was configuring Nginx. Every time a new container was spun up, the same tedious routine repeated: open nginx.conf, write an upstream block, reload the service, and run Certbot to fetch SSL. A single misplaced semicolon could bring down the entire proxy cluster. Traefik completely eliminates this friction: it listens directly to events from Podman’s socket, routes traffic via labels, and automatically obtains Let’s Encrypt certificates.

Fedora is an ideal environment for Podman thanks to Red Hat’s early kernel updates and deep cgroups v2 integration. Combining Traefik v3 with Podman on Fedora 39/40 yields a lightweight, rock-solid proxy cluster that requires minimal maintenance.

Quick Start (5-Minute Deployment)

Have a domain ready with its A/AAAA records pointed to your Fedora server’s IP address, and open ports 80 and 443 in the firewall:

sudo firewall-cmd --permanent --add-port={80,443}/tcp
sudo firewall-cmd --reload

1. Enable the Podman System Socket

Traefik communicates with Podman through a Docker API-compatible socket. Enable the systemd socket:

sudo systemctl enable --now podman.socket
# Verify that the socket is active
curl -s --unix-socket /run/podman/podman.sock http://d/v1.41/version | jq .Version

2. Create Directories and Configuration Files for Traefik

Create a dedicated directory at /opt/traefik to store configuration files and certificates:

sudo mkdir -p /opt/traefik
cd /opt/traefik
sudo touch acme.json
sudo chmod 600 acme.json

Create the static configuration file traefik.yml:

api:
  dashboard: true
  insecure: false

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false

certificatesResolvers:
  letsencrypt:
    acme:
      email: "[email protected]"
      storage: "/acme.json"
      httpChallenge:
        entryPoint: web

Note: Replace [email protected] with your actual email address to receive expiration notifications.

3. Launch Traefik with Podman Compose

Create the file /opt/traefik/compose.yml:

version: "3.8"

services:
  traefik:
    image: docker.io/library/traefik:v3.0
    container_name: traefik
    restart: always
    ports:
      - "80:80"
      - "443:443"
    security_opt:
      - label=disable
    volumes:
      - /run/podman/podman.sock:/var/run/docker.sock:ro
      - /opt/traefik/traefik.yml:/traefik.yml:ro,z
      - /opt/traefik/acme.json:/acme.json:z
    networks:
      - proxy-net

networks:
  proxy-net:
    name: proxy-net

Run the Traefik container:

podman compose up -d

4. Deploy a Test Application with SSL

Deploy a simple Nginx web server to verify automatic certificate issuance:

podman run -d --name demo-web \
  --network proxy-net \
  --label "traefik.enable=true" \
  --label "traefik.http.routers.demo.rule=Host(\`app.example.com\`)" \
  --label "traefik.http.routers.demo.entrypoints=websecure" \
  --label "traefik.http.routers.demo.entrypoints=websecure" \
  --label "traefik.http.routers.demo.tls.certresolver=letsencrypt" \
  --label "traefik.http.services.demo.loadbalancer.server.port=80" \
  docker.io/library/nginx:alpine

Open https://app.example.com in your browser. You should see a valid HTTPS connection in about 3 to 5 seconds, without configuring a single line of Nginx on the host.

Core Technical Concepts to Understand

The Provider Mechanism and Docker API Compatibility

Traefik does not poll containers periodically. Instead, it listens directly to the event stream from podman.socket.

Whenever you run podman run or stop a container, Traefik catches the signal instantly. Its in-memory routing table updates in under 10 milliseconds. The entire process occurs behind the scenes without reloading processes or dropping existing TCP connections.

Because Podman provides a standard Docker API over a Unix socket, Traefik can directly leverage its built-in docker provider without third-party plugins.

Handling SELinux Obstacles on Fedora Server

Fedora runs SELinux in Enforcing mode by default. If files or sockets are mounted without the appropriate security context, the container will instantly throw a Permission Denied error.

Key points to remember:

  • The :z volume flag assigns a shared SELinux label (shared context container_file_t) to configuration files like traefik.yml and acme.json.
  • Never use the :z flag on the system socket file /run/podman/podman.sock. Relabeling system sockets can break systemd permissions. Instead, disable SELinux label confinement specifically for the Traefik container using security_opt: ["label=disable"].

The Let’s Encrypt SSL Issuance Lifecycle

When the first request hits app.example.com on port 443:

  1. Traefik checks acme.json.
  2. If no certificate exists, Traefik triggers its ACME client to request a new one from Let’s Encrypt.
  3. Let’s Encrypt issues an HTTP-01 challenge back to port 80. Traefik intercepts this specific request and returns the valid verification token.
  4. Upon successful validation, Let’s Encrypt issues the certificate pair along with the private key.
  5. Traefik writes the details to acme.json and loads them directly into memory. When the certificate has fewer than 30 days remaining, Traefik automatically renews it.

Advanced Configuration

1. Secure the Traefik Dashboard with Basic Auth

The dashboard has public access disabled by default (insecure: false). To expose it via a dedicated subdomain (such as traefik.example.com), attach a router to the internal api@internal service alongside an authentication middleware:

# Generate password hash (install: sudo dnf install -y httpd-tools)
htpasswd -nb admin StrongPassword_2026@Secure

Add the following labels to the Traefik service in compose.yml:

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
      - "traefik.http.routers.dashboard.middlewares=auth"
      - "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$xyz...hash_string_above"

Note: In compose files, every $ character in the password hash must be escaped as $$ to prevent compose from interpreting it as an environment variable.

2. Issue Wildcard Certificates via DNS-01 Challenge (Cloudflare)

The HTTP-01 method has limitations: it cannot issue wildcard certificates like *.example.com and requires port 80 to be publicly accessible from the internet. For internal services, the DNS challenge is the ideal alternative:

# Append to traefik.yml
certificatesResolvers:
  dns-cloudflare:
    acme:
      email: "[email protected]"
      storage: "/acme.json"
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - "1.1.1.1:53"
          - "8.8.8.8:53"

Add the Cloudflare API token as an environment variable in Traefik’s compose file:

    environment:
      - CF_DNS_API_TOKEN=vj98aX_example_cloudflare_token

Production Best Practices and Field Experience

A few crucial tips to help you avoid production pitfalls:

  • File permissions for acme.json: Traefik will refuse to start if this file has permissions other than 600. If you accidentally chmod it to 644 or copy it from another machine, Traefik will throw the error permissions 0644 for /acme.json are too open.
  • Watch out for Let’s Encrypt rate limits: When testing your configuration, temporarily point to the staging server by adding caServer: "https://acme-staging-v02.api.letsencrypt.org/directory". Let’s Encrypt enforces a strict limit of 5 failed validation attempts per account/hostname per hour. Exceeding this will temporarily block your IP from issuing certificates.
  • Enable JSON Access Logs for easier troubleshooting: Add accessLog: { format: json } to your configuration file. JSON formatting makes it straightforward to filter and ship logs via Fluent Bit or Promtail into Grafana Loki, giving full visibility into upstream response times and container HTTP statuses.
  • 404 errors caused by network mismatches: In 90% of cases, seeing a 404 error after configuring labels means the application container hasn’t joined the proxy-net network alongside Traefik. Podman’s default bridge network does not provide automatic container-to-container DNS resolution.
Share: