# SignalR WebSocket upgrade helper (see /hubs/ block). Must be in http context or # nginx fails config test: [emerg] unknown "connection_upgrade" variable. map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { # HTTP -> HTTPS listen 80; server_name app.rolac.org api.rolac.org; location /.well-known/acme-challenge/ { root /var/www/certbot; } location / { return 301 https://$host$request_uri; } } server { listen 443 ssl; server_name app.rolac.org; ssl_certificate /etc/letsencrypt/live/app.rolac.org/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/app.rolac.org/privkey.pem; location /api/ { # nginx defaults to 1 MB, which 413s phone-camera receipt uploads before they # reach the API. Keep this >= the largest API [RequestSizeLimit] (offerings = 50 MB) # so the per-endpoint limits in the controllers stay the real authority. client_max_body_size 50m; proxy_pass http://api:8080/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; } # SignalR hubs -> api container (Kestrel), same as /api/. Without this, /hubs/* # falls through to "location /" (the static app), whose nginx 405s the negotiate # POST so the connection never reaches the backend. Upgrade/Connection headers + # http_version 1.1 let the WebSocket transport establish instead of long-polling. location /hubs/ { proxy_pass http://api:8080/hubs/; 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; } location / { proxy_pass http://app:80; } }