# Nginx reverse proxy config for Digital Ocean droplet # Proxies HTTPS traffic to Vigilar at home via WireGuard tunnel # # Install: cp vigilar.conf /etc/nginx/sites-available/vigilar # ln -s /etc/nginx/sites-available/vigilar /etc/nginx/sites-enabled/ # nginx -t && systemctl reload nginx # # TLS: certbot --nginx -d vigilar.yourdomain.com # Rate limiting zones limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s; limit_req_zone $binary_remote_addr zone=stream:10m rate=5r/s; limit_conn_zone $binary_remote_addr zone=connlimit:10m; # HLS segment cache — reduces repeat requests hitting the home uplink proxy_cache_path /var/cache/nginx/vigilar_hls levels=1:2 keys_zone=hls_cache:10m max_size=256m inactive=30s use_temp_path=off; # Upstream: Vigilar on home server via WireGuard tunnel upstream vigilar_home { server 10.99.0.2:49735; # If home server goes down, fail fast keepalive 4; } # Redirect HTTP → HTTPS server { listen 80; server_name vigilar.yourdomain.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name vigilar.yourdomain.com; # TLS (managed by certbot) ssl_certificate /etc/letsencrypt/live/vigilar.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/vigilar.yourdomain.com/privkey.pem; 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; # Security headers add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; 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; # Connection limits — protect 22 Mbps home uplink # Max 10 simultaneous connections per IP limit_conn connlimit 10; # --- HLS streams (bandwidth-critical path) --- # Cache .ts segments on the droplet to avoid re-fetching from home # when multiple remote viewers request the same segment location ~ ^/cameras/.+/hls/.+\.ts$ { proxy_pass http://vigilar_home; 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; # Cache segments for 10s — they're 2s segments, so this covers # multiple viewers watching the same feed without re-fetching proxy_cache hls_cache; proxy_cache_valid 200 10s; proxy_cache_key $uri; add_header X-Cache-Status $upstream_cache_status; # Rate limit: 5 segment requests/sec per IP limit_req zone=stream burst=20 nodelay; } # HLS playlists — don't cache (they update every segment) location ~ ^/cameras/.+/hls/.+\.m3u8$ { proxy_pass http://vigilar_home; 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; # No cache — playlists must be fresh proxy_cache off; add_header Cache-Control "no-cache, no-store, must-revalidate"; } # --- SSE event stream --- location /events/stream { proxy_pass http://vigilar_home; 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; # SSE: disable buffering, long timeout proxy_buffering off; proxy_cache off; proxy_read_timeout 3600s; proxy_send_timeout 3600s; chunked_transfer_encoding on; proxy_set_header Connection ''; proxy_http_version 1.1; } # --- API endpoints --- location ~ ^/system/api/ { proxy_pass http://vigilar_home; 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; limit_req zone=api burst=10 nodelay; } # --- Static assets (cache aggressively on droplet) --- location /static/ { proxy_pass http://vigilar_home; proxy_set_header Host $host; proxy_cache hls_cache; proxy_cache_valid 200 1h; add_header X-Cache-Status $upstream_cache_status; } # --- Service worker (must not be cached stale) --- location = /static/sw.js { proxy_pass http://vigilar_home; proxy_set_header Host $host; proxy_cache off; add_header Cache-Control "no-cache"; } # --- Everything else (pages, PWA manifest, etc.) --- location / { proxy_pass http://vigilar_home; 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; proxy_http_version 1.1; limit_req zone=api burst=20 nodelay; } # Deny access to config/sensitive paths location ~ ^/(config|migrations|scripts|tests) { deny all; } # Max upload size (for config changes, etc.) client_max_body_size 1m; # Logging access_log /var/log/nginx/vigilar_access.log; error_log /var/log/nginx/vigilar_error.log; }