# /etc/nginx/sites-available/nik4nao.xyz # WebSocket upgrade helper map $http_upgrade $connection_upgrade { default upgrade; '' close; } ############################ # HTTP: ACME + Redirect ############################ server { listen 80; server_name nik4nao.xyz; # Let's Encrypt HTTP-01 challenge location ^~ /.well-known/acme-challenge/ { root /var/www/html; default_type "text/plain"; allow all; access_log off; } # Serve /robots.txt from disk on HTTP location = /robots.txt { alias /var/www/html/robots.txt; default_type text/plain; } # Redirect everything else to HTTPS location / { return 301 https://$host$request_uri; } } ############################ # HTTPS: Proxy /watch-party/ ############################ server { listen 443 ssl http2; server_name nik4nao.xyz; root /var/www/html; access_log /var/log/nginx/nik4naoxyz_access.json json if=$is_external; error_log /var/log/nginx/nik4naoxyz_error.log warn; # --- Certbot-managed TLS files --- ssl_certificate /etc/letsencrypt/live/nik4nao.xyz/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/nik4nao.xyz/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # --- Security & indexing headers --- add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer" always; add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always; add_header X-Frame-Options "DENY" always; add_header X-Robots-Tag "noindex, nofollow, noimageindex, nosnippet, noarchive" always; # Custom error pages for public site proxy_intercept_errors on; error_page 404 /errors/404.html; error_page 500 502 503 504 /errors/50x.html; # (Optional) Block noncompliant AI bots (requires $block_ai map in nginx.conf) if ($block_ai) { return 403; } # Serve /robots.txt on HTTPS location = /robots.txt { alias /var/www/html/robots.txt; default_type text/plain; } # Enforce trailing slash for subpath and land / on /watch-party/ location = /watch-party { return 301 /watch-party/; } location = / { try_files /nik4nao-xyz-landing.html =404; } # Cache versioned static assets aggressively (adjust path if needed) location ^~ /watch-party/assets/ { proxy_pass http://192.168.7.96:3000; add_header Cache-Control "public, max-age=31536000, immutable"; } # Main app proxy (keep /watch-party/ prefix) location /watch-party/ { proxy_pass http://192.168.7.96:3000; 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; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } # API proxy with basic abuse controls location /api/ { proxy_pass http://192.168.7.96:3000; # upstream /api 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; # Limits (zones defined in nginx.conf) limit_req zone=api_rps burst=20 nodelay; limit_conn perip 20; # Timeouts & body limits client_max_body_size 10m; client_body_timeout 15s; proxy_connect_timeout 5s; proxy_send_timeout 60s; proxy_read_timeout 60s; } # Deny anything unexpected # Pretty error pages (served from disk, not directly reachable) location = /errors/404.html { root /var/www; internal; } location = /errors/50x.html { root /var/www; internal; } # Deny anything unexpected location / { return 404; } }