The go-to resource for upgrading Python, Django, Flask, and your dependencies.

Serving FastAPI Behind Traefik with Automatic HTTPS and Path-Based Routing


When you deploy FastAPI applications to production, you’ll typically need a reverse proxy to manage HTTPS termination, load balancing, and request routing. Traefik integrates particularly well in Docker environments through simple service labels. This enables automatic service discovery and dynamic configuration without maintaining static config files.

In this guide, we’ll set up Traefik to proxy FastAPI requests under a path prefix like /api (stripping it for the app), obtain automatic HTTPS certificates via Let’s Encrypt, and cover production deployment basics.

Why Choose Traefik for FastAPI?

When selecting a reverse proxy, consider your deployment environment and needs. Here’s how Traefik compares to Nginx and Caddy:

ProxyAutomatic HTTPSPath RoutingDocker IntegrationDashboard
TraefikLet’s EncryptLabels & middlewareNative (docker.sock)Built-in
NginxCertbot/manuallocation blocksPlugins/LuaNone
CaddyAutomaticSimple directivesPartialBasic

Traefik excels in Docker setups—we define routing directly on service labels. This enables dynamic discovery as containers start/stop, without reloading configs. Nginx provides precise control but often needs static files updated and reloaded. Caddy keeps things minimal for basic sites.

For FastAPI apps, all handle ASGI passthrough and WebSockets well. Traefik’s Docker-native approach suits microservices; Nginx fits traditional servers; Caddy for quick prototypes.

You might prefer Nginx if your team knows it well or you need custom modules—it’s widely deployed for good reason.

Prerequisites

You’ll need:

  • Docker and Docker Compose v2.20+
  • A domain (e.g., fastapi.example.com) with an A record pointing to your server’s IP
  • An email for Let’s Encrypt registration
  • Firewall rules allowing inbound traffic on ports 80 and 443

Docker Compose Setup

version: '3.8'

services:
  traefik:
    image: traefik:v3.0
    restart: unless-stopped
    ports:
      - 80:80
      - 443:443
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
    command:
      - --api.dashboard=true
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.web.http.redirections.entryPoint.to=websecure
      - --entrypoints.web.http.redirections.entryPoint.scheme=https
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.letsencrypt.acme.httpchallenge=true
      - --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
      - --certificatesresolvers.letsencrypt.acme.email=you@example.com
      - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json

  fastapi:
    build: .
    restart: unless-stopped
    labels:
      - traefik.enable=true
      - traefik.http.routers.fastapi.rule=Host(`fastapi.example.com`) && PathPrefix(`/api`)
      - traefik.http.routers.fastapi.tls.certresolver=letsencrypt
      - traefik.http.routers.fastapi.entrypoints=websecure
      - traefik.http.routers.fastapi.middlewares=strip-api-prefix
      - traefik.http.middlewares.strip-api-prefix.stripprefix.prefixes=/api
      - traefik.http.services.fastapi.loadbalancer.server.port=8000
      - traefik.http.services.fastapi.loadbalancer.healthcheck.path=/health
    depends_on:
      - traefik

Traefik Configuration Explained

In the traefik service command:

  • --api.dashboard=true: Enables the web dashboard (add routing separately).
  • --providers.docker=true and exposedbydefault=false: Watches Docker for labeled services only.
  • Entry points: web (80) redirects to websecure (443).
  • certificatesresolvers.letsencrypt.acme...: Uses HTTP-01 challenge for domain validation.

Volumes: /var/run/docker.sock allows Traefik to inspect running containers; ./letsencrypt persists acme.json (must be 600 perms).

FastAPI Routing Labels

  • traefik.http.routers.fastapi.rule: Matches host + path.
  • tls.certresolver=letsencrypt: Requests cert for matched host.
  • middlewares=strip-api-prefix: Defined globally, strips /api.
  • loadbalancer.server.port=8000: Forwards to Uvicorn.
  • healthcheck.path=/health: Liveness probe.

Pitfalls: Docker sock read-only; domain must resolve publicly for Let’s Encrypt; initial HTTP challenge needs port 80 open.

FastAPI Application Files

Dockerfile:

FROM tiangolo/uvicorn-gunicorn-fastapi:python3.12

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

requirements.txt: fastapi uvicorn[standard]

main.py:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "FastAPI behind Traefik OK"}

@app.get("/health")
async def health():
    return {"status": "healthy"}

Deployment and Testing

First, create the certs directory:

mkdir -p letsencrypt
chmod 700 letsencrypt

Start the stack:

docker compose up -d

Check logs:

docker compose logs -f traefik

You’ll see Traefik starting entrypoints and providers. Once running, test:

curl https://fastapi.example.com/api/
# Expected: {"message": "FastAPI behind Traefik OK"}

curl https://fastapi.example.com/api/health
# Expected: {"status": "healthy"}

For the Traefik dashboard, add a router rule and basic auth middleware to your traefik service labels.

Path-Based Multi-App Routing

Add second service whoami:

  whoami:
    image: traefik/whoami
    labels:
      - traefik.enable=true
      - traefik.http.routers.whoami.rule=Host(`fastapi.example.com`)
      - traefik.http.routers.whoami.tls.certresolver=letsencrypt
      - traefik.http.services.whoami.loadbalancer.server.port=80

/ → whoami, /api → FastAPI.

Production Considerations

Once basic setup works, consider these for production:

  • Scaling FastAPI: docker compose up --scale fastapi=3. Traefik distributes load. Trade-off: higher resource use vs. better throughput/availability.
  • Secrets Management: Use Docker secrets or .env files for email and other sensitive data instead of command flags.
  • Observability: Enable --metrics.prometheus on Traefik; integrate with Prometheus/Grafana.
  • Additional Middleware: Add rate limiting, IP whitelisting via extra labels.
  • Docker Swarm: Switch to Swarm mode for clustering; use overlay networks for service discovery across nodes.

Verification: Run docker compose ps to check services; inspect ./letsencrypt/acme.json for certificates.

Troubleshooting Common Issues

IssueLikely CauseFix
Let’s Encrypt cert failsPort 80 blocked; domain not resolvingCheck firewall (ufw status, iptables); verify DNS (dig domain); test challenge path
404 on /api pathsRule mismatch or no prefix stripInspect Traefik logs (docker compose logs traefik); confirm labels and middleware
No automatic HTTPSMissing redirect or TLS configVerify entrypoint redirections and tls.certresolver=letsencrypt label

Related:

This Docker Compose setup provides a maintainable foundation for serving FastAPI in production.

Sponsored by Durable Programming

Need help maintaining or upgrading your Python application? Durable Programming specializes in keeping Python apps secure, performant, and up-to-date.

Hire Durable Programming