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:
| Proxy | Automatic HTTPS | Path Routing | Docker Integration | Dashboard |
|---|---|---|---|---|
| Traefik | Let’s Encrypt | Labels & middleware | Native (docker.sock) | Built-in |
| Nginx | Certbot/manual | location blocks | Plugins/Lua | None |
| Caddy | Automatic | Simple directives | Partial | Basic |
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=trueandexposedbydefault=false: Watches Docker for labeled services only.- Entry points:
web(80) redirects towebsecure(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.prometheuson 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
| Issue | Likely Cause | Fix |
|---|---|---|
| Let’s Encrypt cert fails | Port 80 blocked; domain not resolving | Check firewall (ufw status, iptables); verify DNS (dig domain); test challenge path |
404 on /api paths | Rule mismatch or no prefix strip | Inspect Traefik logs (docker compose logs traefik); confirm labels and middleware |
| No automatic HTTPS | Missing redirect or TLS config | Verify 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