Configurare SignalR dietro nginx senza perdere la connessione WebSocket
Configurare SignalR dietro nginx senza perdere la connessione WebSocket
Per far funzionare SignalR (e quindi un'app Blazor Server) dietro nginx servono due direttive header nel blocco location: proxy_set_header Upgrade $http_upgrade; e proxy_set_header Connection "upgrade";. Senza queste due righe, nginx tratta la richiesta come una normale connessione HTTP e la interrompe dopo la risposta iniziale — la pagina si carica ma resta "morta": nessun bottone risponde, nessun binding aggiorna lo stato, senza nessun errore visibile in console.
Perché succede
SignalR (il layer di comunicazione realtime sotto Blazor Server) prova prima una connessione WebSocket e, se non disponibile, fa fallback su altri trasporti. Un WebSocket nasce come richiesta HTTP che poi "sale di livello" tramite gli header Upgrade/Connection — è un protocollo diverso da quel momento in poi, a lunga durata, non una singola richiesta/risposta. Un reverse proxy configurato con le impostazioni HTTP di default non sa che quella specifica connessione va tenuta aperta e inoltrata byte per byte: la chiude come farebbe con qualsiasi altra richiesta.
Apache ha lo stesso problema ma la soluzione è più macchinosa: richiede il modulo mod_proxy_wstunnel più una RewriteCond dedicata per instradare la richiesta al modulo giusto. nginx gestisce l'upgrade nativamente, bastano le due direttive header.
Configurazione minima che funziona
Blocco location per un'app Blazor Server dietro nginx (esempio con Kestrel in ascolto su 127.0.0.1:5102, sottodominio dedicato all'app):
server { listen 443 ssl; server_name app.example.com;
# ... direttive ssl\_certificate, TLS, ecc.
location / {
proxy\_pass http://127.0.0.1:5102;
proxy\_http\_version 1.1;
proxy\_set\_header Upgrade $http\_upgrade;
proxy\_set\_header 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;
}
}
Tre dettagli che non sono opzionali, anche se a volte vengono dimenticati:
Come verificare che funzioni davvero
Non basta che la pagina si carichi: bisogna verificare che il circuito Blazor sia effettivamente interattivo.
Errori comuni
Domande frequenti
Serve una configurazione diversa se ho più app Blazor Server dietro lo stesso nginx? No, ogni server_name/location che fa da proxy verso un'app Blazor Server ha bisogno delle stesse direttive, indipendentemente da quante app coesistono sulla stessa macchina nginx.
Il problema può essere lato Kestrel invece che lato nginx? Raramente, se l'app gira correttamente in locale senza proxy davanti. Se il WebSocket fallisce anche in locale, il problema è nell'app (es. CORS mal configurato, middleware che intercetta la richiesta prima dell'hub SignalR), non nel reverse proxy.
Devo configurare qualcosa di diverso per SignalR "puro" (non Blazor Server, es. una chat realtime)? No, la configurazione nginx è identica: SignalR usa lo stesso meccanismo di upgrade indipendentemente da cosa gira sopra (circuito Blazor o un hub applicativo custom).