· Nginx· reverse proxy· SSL· Let's Encrypt· rate limiting· SaaS· FastAPI· Docker· sikkerhed
Nginx reverse proxy til SaaS — SSL, rate limiting og routing
Nginx konfiguration til SaaS-platform: SSL/TLS termination med Let's Encrypt, rate limiting, proxy_pass til FastAPI, static file serving og sikkerhedsheaders.
Af M-Bus Gateway
M-Bus Gateway platformen kører Nginx som reverse proxy foran FastAPI. Her er konfigurationen der håndterer SSL, rate limiting og routing.
Arkitektur
Internet
↓ HTTPS 443
[Nginx — reverse proxy]
↓ HTTP 8000 ↓ HTTP 3000
[FastAPI server] [React UI (static)]
↓
[Mosquitto MQTT 8883] (Nginx proxier ikke MQTT — direkte)
Nginx håndterer:
→ SSL/TLS termination (Let's Encrypt)
→ HTTP → HTTPS redirect
→ Rate limiting (per IP)
→ Gzip-komprimering
→ Sikkerhedsheaders (HSTS, CSP, X-Frame-Options)
→ Static file serving (React UI fra /var/www/ui)
→ Proxy_pass til FastAPI (/api/v1/)
Basis Nginx-konfiguration
# /etc/nginx/sites-available/mbus-gateway
# Rate limiting: Definer zoner øverst (ikke i server-block):
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/m;
limit_req_zone $binary_remote_addr zone=auth:10m rate=10r/m;
# HTTP → HTTPS redirect:
server {
listen 80;
server_name mbus-gateway.dk www.mbus-gateway.dk;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name mbus-gateway.dk;
# Let's Encrypt certifikater:
ssl_certificate /etc/letsencrypt/live/mbus-gateway.dk/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mbus-gateway.dk/privkey.pem;
# TLS-konfiguration (moderne profil):
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# HSTS — tving HTTPS i browsere (inkl. subdomæner):
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# Sikkerhedsheaders:
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' wss:" always;
# Gzip:
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1024;
# API-endpoints (FastAPI):
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://127.0.0.1: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;
# Timeout (generøst for PDF-generering):
proxy_read_timeout 120s;
proxy_connect_timeout 10s;
}
# Auth-endpoints: Strengere rate limiting:
location /api/v1/auth/login {
limit_req zone=auth burst=5 nodelay;
proxy_pass http://127.0.0.1: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;
}
# React UI — statiske filer:
location / {
root /var/www/ui;
try_files $uri $uri/ /index.html; # SPA-routing
# Cache statiske assets (JS/CSS med content-hash):
location ~* \.(js|css|woff2|png|svg|ico)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
# Health check endpoint (ingen rate limiting, ingen auth):
location /health {
proxy_pass http://127.0.0.1:8000/health;
access_log off;
}
}
Let's Encrypt automatisk fornyelse
# Installer certbot:
apt install certbot python3-certbot-nginx
# Hent certifikat (Nginx-plugin stopper og genstarter automatisk):
certbot --nginx -d mbus-gateway.dk -d www.mbus-gateway.dk \
--non-interactive --agree-tos --email admin@mbus-gateway.dk
# Certbot tilføjer automatisk fornyelsescron:
# /etc/cron.d/certbot: 0 */12 * * * certbot renew --quiet
# Test fornyelse:
certbot renew --dry-run
Docker Compose med Nginx
# docker-compose.yml
services:
nginx:
image: nginx:1.25-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/sites/:/etc/nginx/sites-available/:ro
- /var/www/ui:/var/www/ui:ro # React UI build
- /etc/letsencrypt:/etc/letsencrypt:ro
- nginx_logs:/var/log/nginx
depends_on:
- server
restart: unless-stopped
server:
build: .
expose:
- "8000" # Kun intern adgang — Nginx proxier
environment:
- DATABASE_URL=postgresql+asyncpg://...
restart: unless-stopped
volumes:
nginx_logs:
Rate limiting: Beskyt mod brute force
# Avanceret rate limiting med burst og delay:
# Login — max 10 forsøg/min, burst af 5:
location /api/v1/auth/login {
limit_req zone=auth burst=5 nodelay;
limit_req_status 429;
# Return JSON ved rate limit (ikke standard Nginx HTML):
error_page 429 = @rate_limited;
proxy_pass http://127.0.0.1:8000;
...
}
location @rate_limited {
default_type application/json;
return 429 '{"detail": "For mange forsøg. Vent 1 minut."}';
}
# API generelt — 30 req/min, burst af 20:
location /api/ {
limit_req zone=api burst=20 nodelay;
...
}
Konklusion
Nginx som reverse proxy for FastAPI giver SSL-terminering, rate limiting og statisk file serving i ét lag — uden at belaste Python-processer. Let's Encrypt automatiserer certifikathåndtering. Strengere rate limiting på auth-endpoints er kritisk — uden det kan brute-force-angreb teste tusindvis af passwords pr. minut.