# Real client IP — traffic arrives via Cloudflare → NPM (172.16.22.21), so # $remote_addr would otherwise be NPM for every request and rate limits would # be shared by all clients. Walk X-Forwarded-For right-to-left past trusted hops. # Cloudflare ranges: https://www.cloudflare.com/ips/ set_real_ip_from 172.16.22.21; set_real_ip_from 173.245.48.0/20; set_real_ip_from 103.21.244.0/22; set_real_ip_from 103.22.200.0/22; set_real_ip_from 103.31.4.0/22; set_real_ip_from 141.101.64.0/18; set_real_ip_from 108.162.192.0/18; set_real_ip_from 190.93.240.0/20; set_real_ip_from 188.114.96.0/20; set_real_ip_from 197.234.240.0/22; set_real_ip_from 198.41.128.0/17; set_real_ip_from 162.158.0.0/15; set_real_ip_from 104.16.0.0/13; set_real_ip_from 104.24.0.0/14; set_real_ip_from 172.64.0.0/13; set_real_ip_from 131.0.72.0/22; set_real_ip_from 2400:cb00::/32; set_real_ip_from 2606:4700::/32; set_real_ip_from 2803:f800::/32; set_real_ip_from 2405:b500::/32; set_real_ip_from 2405:8100::/32; set_real_ip_from 2a06:98c0::/29; set_real_ip_from 2c0f:f248::/32; real_ip_header X-Forwarded-For; real_ip_recursive on; # Rate limiting zones — included inside http{} block limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=5r/m; limit_req_zone $binary_remote_addr zone=tv_limit:10m rate=10r/m; server { listen 80; server_tokens off; root /usr/share/nginx/html; index index.html; # Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self' ws: wss:; font-src 'self' data:;" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # Gzip compression gzip on; gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml; gzip_min_length 1024; # Rate-limited auth endpoints (checked before the generic /api/ block) location ~ ^/api/(auth/(login|register)|admin/login)$ { limit_req zone=auth_limit burst=3 nodelay; limit_req_status 429; proxy_pass http://backend: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; } # Rate-limited TV dashboard endpoint (public, token-based) location ~ ^/api/dashboard/ { limit_req zone=tv_limit burst=5 nodelay; limit_req_status 429; proxy_pass http://backend: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; } # API proxy → FastAPI backend location /api/ { proxy_pass http://backend: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; } # WebSocket proxy → FastAPI backend location /ws/ { limit_req zone=tv_limit burst=5 nodelay; limit_req_status 429; proxy_pass http://backend:8000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_read_timeout 3600s; } # Vue Router — all other paths serve index.html (SPA fallback) location / { try_files $uri $uri/ /index.html; } }