server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; gzip on; gzip_types text/plain text/css text/javascript application/javascript application/json image/svg+xml; gzip_min_length 1024; location = /health { access_log off; add_header Content-Type text/plain; return 200 "ok\n"; } location = /index.html { add_header Cache-Control "no-store, no-cache, must-revalidate" always; } # SPA route that shares the "/auth" prefix used by the auth API block # below. Exact matches always win over prefix/regex locations in nginx, # so this keeps the client-side callback page from being swallowed by # the API proxy. location = /auth/callback { try_files $uri /index.html; } location / { try_files $uri $uri/ /index.html; } # Generic passthrough used by a few call sites that fetch "/api/..." # directly. Prefix is stripped: /api/foo -> $BACKEND_URL/foo location /api/ { proxy_pass ${BACKEND_URL}/; proxy_http_version 1.1; proxy_set_header Host $http_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_read_timeout 300s; proxy_connect_timeout 300s; proxy_send_timeout 300s; client_max_body_size 50M; } # Per-module API prefixes the SPA calls directly (see the VITE_*_APP_URL # build args in the Dockerfile and src/shared/api-url.ts). Path is kept # as-is (no stripping) so it lines up with what $BACKEND_URL serves. # This is what lets the app work behind a public tunnel: the browser # only ever talks to this same origin, never to the backend's local IP. location ~ ^/(auth|efficiency|efficiency-core|optimumoh|pfi|lcca|reliability|risk-matrix|monitoring|rbd|ahm|ops-dock)/ { proxy_pass ${BACKEND_URL}; proxy_http_version 1.1; proxy_set_header Host $http_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_read_timeout 300s; proxy_connect_timeout 300s; proxy_send_timeout 300s; client_max_body_size 50M; } # IRCFA is served by a separate backend host and doesn't expect an # "/ircfa" prefix itself, so it's stripped here: /ircfa/chat -> $BACKEND_URL_IRCFA/chat location /ircfa/ { proxy_pass ${BACKEND_URL_IRCFA}/; proxy_http_version 1.1; proxy_set_header Host $http_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_read_timeout 300s; proxy_connect_timeout 300s; proxy_send_timeout 300s; client_max_body_size 50M; } location ~* \.(js|css|woff2?|ttf|eot|ico)$ { expires 1y; add_header Cache-Control "public, immutable"; access_log off; } location ~* \.(png|jpg|jpeg|gif|svg|webp)$ { expires 7d; add_header Cache-Control "public"; access_log off; } add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; }