diff --git a/deploy/vm/nginx/conf.d/rolac.conf b/deploy/vm/nginx/conf.d/rolac.conf index 15fe986..6c3d970 100644 --- a/deploy/vm/nginx/conf.d/rolac.conf +++ b/deploy/vm/nginx/conf.d/rolac.conf @@ -1,3 +1,12 @@ +# SignalR/WebSocket: when the client sends "Upgrade: websocket" we must forward +# "Connection: upgrade"; on ordinary requests (negotiate POST, long-polling) the +# Upgrade header is empty and we send "Connection: close" instead. A conf.d file +# is included inside the http{} context, so this top-level map is valid here. +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + server { listen 80; server_name _; @@ -23,6 +32,25 @@ server { proxy_set_header X-Forwarded-Proto $scheme; } + # SignalR hubs -> api container. Must be proxied to Kestrel like /api/; without + # this block /hubs/* fell through to "location /" (the static app), whose nginx + # 405s the negotiate POST so the connection never reaches the backend. The + # Upgrade/Connection headers + http_version 1.1 let the WebSocket transport + # establish instead of degrading to long-polling. + location /hubs/ { + set $upstream_api api; + proxy_pass http://$upstream_api:8080$request_uri; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + 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_buffering off; + proxy_read_timeout 100s; + } + # Everything else -> the Angular static app (its own nginx does SPA fallback). location / { set $upstream_app app;