> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/fastapi/fastapi/llms.txt
> Use this file to discover all available pages before exploring further.

# HTTPS and SSL/TLS Configuration

> Configure HTTPS, SSL/TLS certificates, and reverse proxies for secure FastAPI deployments.

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

<Warning>
  Running production APIs without HTTPS exposes sensitive data and user credentials to attackers.
</Warning>

## How HTTPS Works

HTTPS is **not just "HTTP with encryption enabled"**. It's more complex than that.

### Key Concepts

<Steps>
  <Step title="Certificates Required">
    The server needs **TLS certificates** from a trusted third party (Certificate Authority).
  </Step>

  <Step title="Certificates Expire">
    Certificates have a **lifetime** (typically 90 days with Let's Encrypt) and must be renewed.
  </Step>

  <Step title="TCP-Level Encryption">
    Encryption happens at the **TCP level**, below HTTP. The certificate is used before HTTP communication begins.
  </Step>

  <Step title="Domain-Specific">
    Each certificate is tied to a **specific domain name**, not an IP address.
  </Step>

  <Step title="SNI Extension">
    Server Name Indication (SNI) allows multiple HTTPS certificates on a single IP address.
  </Step>
</Steps>

<Info>
  For an interactive explanation of HTTPS basics, visit [https://howhttps.works/](https://howhttps.works/).
</Info>

## TLS Termination Proxy

The standard approach is using a **TLS Termination Proxy**:

```
Client (HTTPS) → TLS Proxy → FastAPI App (HTTP)
```

The proxy:

1. Receives encrypted HTTPS requests
2. Decrypts them using TLS certificates
3. Forwards plain HTTP to your FastAPI app
4. 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

<Tip>
  Your FastAPI application runs plain HTTP internally. The TLS proxy handles all HTTPS complexity.
</Tip>

## 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

<Steps>
  <Step title="Domain Verification">
    Prove you control the domain (DNS challenge or HTTP challenge).
  </Step>

  <Step title="Certificate Issuance">
    Let's Encrypt issues a certificate valid for 90 days.
  </Step>

  <Step title="Automatic Renewal">
    Renewal happens automatically before expiration.
  </Step>
</Steps>

<Info>
  Let's Encrypt certificates are identical in functionality to paid certificates, just with automated issuance.
</Info>

## TLS Termination Proxy Options

### Traefik (Recommended)

**Traefik** is a modern reverse proxy with automatic HTTPS.

**docker-compose.yml**:

```yaml theme={null}
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

<Tip>
  Traefik is the easiest option for Docker-based deployments with automatic certificate management.
</Tip>

### 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**:

```yaml theme={null}
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:
```

<Info>
  Caddy is the simplest option - HTTPS is automatic with just the domain name in the config.
</Info>

### Nginx with Certbot

**Nginx** is a popular web server that can act as a reverse proxy.

**nginx.conf**:

```nginx theme={null}
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**:

```yaml theme={null}
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:

```bash theme={null}
docker compose run --rm certbot certonly --webroot --webroot-path=/var/www/certbot -d api.example.com
```

<Note>
  Nginx requires more manual configuration than Traefik or Caddy but offers maximum flexibility.
</Note>

### 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:

```bash theme={null}
# 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
```

## Proxy Headers Configuration

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:

```bash theme={null}
# 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**:

```dockerfile theme={null}
CMD ["fastapi", "run", "app/main.py", "--port", "80", "--proxy-headers"]
```

<Warning>
  Only enable `--proxy-headers` when running behind a **trusted** proxy. Never enable it for directly exposed applications.
</Warning>

### Advanced Proxy Configuration

For specific proxy IPs:

```bash theme={null}
uvicorn main:app --forwarded-allow-ips="192.168.1.100,192.168.1.101"
```

Programmatic configuration:

```python theme={null}
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)
    )
```

## Cloud Platform HTTPS

Most cloud platforms handle HTTPS automatically.

### AWS (Application Load Balancer)

```yaml theme={null}
# Configure ALB with SSL certificate
# FastAPI runs plain HTTP internally
ALB (HTTPS) → Target Group → ECS/EC2 (HTTP:8000)
```

Your FastAPI app:

```bash theme={null}
uvicorn main:app --host 0.0.0.0 --port 8000 --proxy-headers
```

### Google Cloud Run

Cloud Run provides automatic HTTPS:

```bash theme={null}
# 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:

```bash theme={null}
az webapp up --name myapp --runtime "PYTHON:3.12"

# HTTPS enabled at https://myapp.azurewebsites.net
```

<Info>
  Cloud platforms typically handle HTTPS, certificates, and renewal automatically. Your app just needs `--proxy-headers`.
</Info>

## Kubernetes Ingress

Kubernetes uses **Ingress** controllers for HTTPS.

### With cert-manager

**ingress.yaml**:

```yaml theme={null}
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**:

```yaml theme={null}
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: your-email@example.com
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - http01:
        ingress:
          class: nginx
```

Apply:

```bash theme={null}
kubectl apply -f cluster-issuer.yaml
kubectl apply -f ingress.yaml
```

<Tip>
  cert-manager automates Let's Encrypt certificates in Kubernetes, handling both issuance and renewal.
</Tip>

## Local Development with HTTPS

For local development, use **mkcert** for trusted certificates.

### Install mkcert

```bash theme={null}
# 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

```bash theme={null}
# Install local CA
mkcert -install

# Generate certificate
mkdir certs
cd certs
mkcert localhost 127.0.0.1 ::1
```

### Use with Uvicorn

```bash theme={null}
uvicorn main:app \
  --ssl-keyfile=./certs/localhost+2-key.pem \
  --ssl-certfile=./certs/localhost+2.pem
```

Access at: `https://localhost:8000`

<Warning>
  Only use self-signed certificates for development. Production must use certificates from trusted CAs like Let's Encrypt.
</Warning>

## Security Best Practices

### Modern TLS Configuration

Nginx example with strong security:

```nginx theme={null}
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:

```python theme={null}
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:

```nginx theme={null}
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
```

<Tip>
  Enable HSTS after confirming HTTPS works correctly. HSTS is difficult to reverse once enabled.
</Tip>

## 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:

```html theme={null}
<!-- ❌ 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

### Proxy Headers Not Working

**Problem**: FastAPI sees proxy IP instead of client IP.

**Solution**: Verify both proxy and FastAPI configuration:

```nginx theme={null}
# 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;
```

```bash theme={null}
# 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:

1. **Development**: Use `mkcert` for local HTTPS
2. **Production (Docker)**: Use Traefik or Caddy for automatic HTTPS
3. **Production (Cloud)**: Use platform's built-in HTTPS
4. **Production (Kubernetes)**: Use Ingress with cert-manager

<Tip>
  Start with Traefik or Caddy for the simplest automatic HTTPS setup. Both handle certificates with zero configuration.
</Tip>
