HTTPS is essential for production FastAPI applications. Understanding how HTTPS works will help you configure it correctly.
Why HTTPS Matters
HTTPS (HTTP over TLS) provides:
- Encryption - Protects data in transit from eavesdropping
- Authentication - Verifies the server’s identity
- Integrity - Prevents tampering with data
- Trust - Required by browsers for modern web features
- SEO - Search engines favor HTTPS sites
Running production APIs without HTTPS exposes sensitive data and user credentials to attackers.
How HTTPS Works
HTTPS is not just “HTTP with encryption enabled”. It’s more complex than that.
Key Concepts
Certificates Required
The server needs TLS certificates from a trusted third party (Certificate Authority).
Certificates Expire
Certificates have a lifetime (typically 90 days with Let’s Encrypt) and must be renewed.
TCP-Level Encryption
Encryption happens at the TCP level, below HTTP. The certificate is used before HTTP communication begins.
Domain-Specific
Each certificate is tied to a specific domain name, not an IP address.
SNI Extension
Server Name Indication (SNI) allows multiple HTTPS certificates on a single IP address.
TLS Termination Proxy
The standard approach is using a TLS Termination Proxy:
Client (HTTPS) → TLS Proxy → FastAPI App (HTTP)
The proxy:
- Receives encrypted HTTPS requests
- Decrypts them using TLS certificates
- Forwards plain HTTP to your FastAPI app
- Encrypts responses before sending to client
Why Use a Proxy?
- Certificate management - Centralized certificate storage and renewal
- Multiple applications - One proxy can handle HTTPS for many apps
- Zero downtime - Renew certificates without restarting your app
- Simplicity - FastAPI app doesn’t need to handle TLS
Your FastAPI application runs plain HTTP internally. The TLS proxy handles all HTTPS complexity.
Let’s Encrypt
Let’s Encrypt provides free, automated TLS certificates.
Benefits
- Free - No cost for certificates
- Automated - Automatic issuance and renewal
- Trusted - Accepted by all major browsers
- Short-lived - 90-day lifetime improves security
- Standard - Uses industry-standard cryptography
How It Works
Domain Verification
Prove you control the domain (DNS challenge or HTTP challenge).
Certificate Issuance
Let’s Encrypt issues a certificate valid for 90 days.
Automatic Renewal
Renewal happens automatically before expiration.
Let’s Encrypt certificates are identical in functionality to paid certificates, just with automated issuance.
TLS Termination Proxy Options
Traefik (Recommended)
Traefik is a modern reverse proxy with automatic HTTPS.
docker-compose.yml:
services:
traefik:
image: traefik:v3.0
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.email=your-email@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./letsencrypt:/letsencrypt"
app:
build: .
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`api.example.com`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=letsencrypt"
Traefik automatically:
- Obtains Let’s Encrypt certificates
- Renews certificates before expiration
- Routes requests to your application
- Redirects HTTP to HTTPS
Traefik is the easiest option for Docker-based deployments with automatic certificate management.
Caddy
Caddy automatically enables HTTPS with zero configuration.
Caddyfile:
api.example.com {
reverse_proxy localhost:8000
}
That’s it! Caddy automatically:
- Obtains certificates from Let’s Encrypt
- Renews certificates
- Redirects HTTP to HTTPS
- Configures secure TLS settings
Docker Compose:
services:
caddy:
image: caddy:2
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
app:
build: .
expose:
- "8000"
volumes:
caddy_data:
caddy_config:
Caddy is the simplest option - HTTPS is automatic with just the domain name in the config.
Nginx with Certbot
Nginx is a popular web server that can act as a reverse proxy.
nginx.conf:
server {
listen 80;
server_name api.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$server_name$request_uri;
}
}
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
docker-compose.yml:
services:
nginx:
image: nginx:latest
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
depends_on:
- app
certbot:
image: certbot/certbot
volumes:
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
app:
build: .
expose:
- "8000"
Obtain initial certificate:
docker compose run --rm certbot certonly --webroot --webroot-path=/var/www/certbot -d api.example.com
Nginx requires more manual configuration than Traefik or Caddy but offers maximum flexibility.
HAProxy
HAProxy is a high-performance load balancer.
haproxy.cfg:
frontend https_front
bind *:443 ssl crt /etc/haproxy/certs/
default_backend fastapi_backend
backend fastapi_backend
balance roundrobin
server app1 localhost:8000 check
Certificate setup:
# Combine cert and key
cat /etc/letsencrypt/live/api.example.com/fullchain.pem \
/etc/letsencrypt/live/api.example.com/privkey.pem \
> /etc/haproxy/certs/api.example.com.pem
When behind a proxy, FastAPI needs to know about the original request.
The Problem
Your FastAPI app receives:
- HTTP requests (not HTTPS)
- From proxy IP (not client IP)
- On internal domain (not public domain)
The Solution
Proxies send forwarded headers:
X-Forwarded-For - Original client IP
X-Forwarded-Proto - Original protocol (https)
X-Forwarded-Host - Original host header
FastAPI Configuration
Enable proxy header trust:
# Trust proxy headers from all IPs (when behind trusted proxy)
fastapi run --proxy-headers main.py
# Or with Uvicorn
uvicorn main:app --proxy-headers
Docker Dockerfile:
CMD ["fastapi", "run", "app/main.py", "--port", "80", "--proxy-headers"]
Only enable --proxy-headers when running behind a trusted proxy. Never enable it for directly exposed applications.
Advanced Proxy Configuration
For specific proxy IPs:
uvicorn main:app --forwarded-allow-ips="192.168.1.100,192.168.1.101"
Programmatic configuration:
import uvicorn
if __name__ == "__main__":
uvicorn.run(
"main:app",
host="0.0.0.0",
port=8000,
proxy_headers=True,
forwarded_allow_ips="*", # Trust all IPs (only when behind proxy)
)
Most cloud platforms handle HTTPS automatically.
AWS (Application Load Balancer)
# Configure ALB with SSL certificate
# FastAPI runs plain HTTP internally
ALB (HTTPS) → Target Group → ECS/EC2 (HTTP:8000)
Your FastAPI app:
uvicorn main:app --host 0.0.0.0 --port 8000 --proxy-headers
Google Cloud Run
Cloud Run provides automatic HTTPS:
# Deploy container
gcloud run deploy --image gcr.io/project/app --platform managed
# HTTPS URL automatically provided
# https://app-xxx-uc.a.run.app
Azure App Service
App Service includes automatic HTTPS:
az webapp up --name myapp --runtime "PYTHON:3.12"
# HTTPS enabled at https://myapp.azurewebsites.net
Cloud platforms typically handle HTTPS, certificates, and renewal automatically. Your app just needs --proxy-headers.
Kubernetes Ingress
Kubernetes uses Ingress controllers for HTTPS.
With cert-manager
ingress.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: fastapi-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- api.example.com
secretName: fastapi-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: fastapi-service
port:
number: 80
cluster-issuer.yaml:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
Apply:
kubectl apply -f cluster-issuer.yaml
kubectl apply -f ingress.yaml
cert-manager automates Let’s Encrypt certificates in Kubernetes, handling both issuance and renewal.
Local Development with HTTPS
For local development, use mkcert for trusted certificates.
Install mkcert
# macOS
brew install mkcert
brew install nss # For Firefox
# Linux
sudo apt install libnss3-tools
wget https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64
chmod +x mkcert-v1.4.4-linux-amd64
sudo mv mkcert-v1.4.4-linux-amd64 /usr/local/bin/mkcert
Create Certificates
# Install local CA
mkcert -install
# Generate certificate
mkdir certs
cd certs
mkcert localhost 127.0.0.1 ::1
Use with Uvicorn
uvicorn main:app \
--ssl-keyfile=./certs/localhost+2-key.pem \
--ssl-certfile=./certs/localhost+2.pem
Access at: https://localhost:8000
Only use self-signed certificates for development. Production must use certificates from trusted CAs like Let’s Encrypt.
Security Best Practices
Modern TLS Configuration
Nginx example with strong security:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
HTTP Strict Transport Security (HSTS)
Force HTTPS for all future requests:
from fastapi import FastAPI
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
app = FastAPI()
# Redirect HTTP to HTTPS
app.add_middleware(HTTPSRedirectMiddleware)
# Only allow specific hosts
app.add_middleware(TrustedHostMiddleware, allowed_hosts=["api.example.com"])
Or configure in proxy:
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
Enable HSTS after confirming HTTPS works correctly. HSTS is difficult to reverse once enabled.
Troubleshooting
Certificate Not Trusted
Problem: Browser shows “Certificate not trusted” error.
Solutions:
- Verify domain DNS points to your server
- Check certificate includes correct domain
- Ensure certificate chain is complete
- Wait for DNS propagation (up to 48 hours)
Mixed Content Warnings
Problem: HTTPS page loading HTTP resources.
Solution: Ensure all resources use HTTPS:
<!-- ❌ Wrong -->
<img src="http://example.com/image.jpg">
<!-- ✅ Correct -->
<img src="https://example.com/image.jpg">
<!-- ✅ Protocol-relative -->
<img src="//example.com/image.jpg">
Certificate Renewal Failing
Problem: Let’s Encrypt renewal fails.
Solutions:
- Check port 80 is accessible (HTTP challenge)
- Verify DNS records are correct
- Check certbot/Traefik logs
- Ensure webroot path is correct
Problem: FastAPI sees proxy IP instead of client IP.
Solution: Verify both proxy and FastAPI configuration:
# Nginx - ensure headers are set
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
# FastAPI - enable proxy headers
fastapi run --proxy-headers main.py
Recap
HTTPS for FastAPI involves:
- ✅ TLS Termination Proxy - Handles HTTPS externally (Traefik, Caddy, Nginx)
- ✅ Let’s Encrypt - Free automated certificates
- ✅ Proxy Headers - Enable with
--proxy-headers flag
- ✅ Automatic Renewal - Use tools that renew certificates automatically
- ✅ Security Headers - Add HSTS and other security headers
Best approach:
- Development: Use
mkcert for local HTTPS
- Production (Docker): Use Traefik or Caddy for automatic HTTPS
- Production (Cloud): Use platform’s built-in HTTPS
- Production (Kubernetes): Use Ingress with cert-manager
Start with Traefik or Caddy for the simplest automatic HTTPS setup. Both handle certificates with zero configuration.