개요
Microsoft ASP.NET Blazor Server 애플리케이션을 NGINX 리버스 프록시 뒤에 배치한 환경에서 브라우저가 WebSocket 연결에 실패하고 Long Polling으로 폴백되는 문제를 추적하여 해결하였다.
처음에는 단순한 Upgrade 헤더 전달 문제처럼 보였지만, 실제로는 아래와 같은 문제가 있었다.
- HTTP/1.1 연결에서 101 Switching Protocol 응답 코드를 받으면 프로토콜이 WebSocket으로 전환된다. 즉, HTTP/2.0 연결을 사용하면 안 된다.
- NGINX 1.25.1 버전 미만에서는 같은 포트에서 HTTP/2와 HTTP/0.9-1.1 연결을 공유할 수 없다. 어떠한 서버 블록에 http2 지시자가 있으면 같은 포트를 공유하는 모든 서버 블록에서 http2를 사용한다.
문제 추적
보통은 NGINX 뒤의 Blazor Server와의 WebSocket 연결 문제를 다루는 대부분의 글은 UseForwardedHeaders()를 사용하여 프록시에서 온 헤더를 신뢰하는 것을 다루고 있다.
하지만 아무리 해당 설정을 올바르게 구성해도 연결 문제가 해결되지 않았다.
Blazor 관련 요청에 대해 앱 쪽에서 아래 코드를 사용하여 직접 로그를 찍어보자.
app.Use(async (ctx, next) =>
{
if (ctx.Request.Path.StartsWithSegments("/_blazor"))
{
Console.WriteLine(
$"BLAZOR {ctx.Request.Method} {ctx.Request.Path}{ctx.Request.QueryString} " +
$"scheme={ctx.Request.Scheme} proto={ctx.Request.Protocol} " +
$"remote={ctx.Connection.RemoteIpAddress} " +
$"upgrade={ctx.Request.Headers.Upgrade} conn={ctx.Request.Headers.Connection}");
}
await next();
});
BLAZOR GET /_blazor?id=... scheme=https proto=HTTP/1.1 remote=172.16.32.1 upgrade= conn=Upgrade
Remote IP와 Scheme 모두 올바르게 프록시에서 넘긴 값을 수용하고 있다. 즉, 앱에서는 문제가 없다.
Blazor Server의 일반적인 WebSocket 경로는 다음을 기대한다.
- HTTP/1.1
- Connection: Upgrade
- Upgrade: websocket
실제 클라이언트와 프록시도 HTTP/1.1 연결을 사용해야 하는 것이다. NGINX 측의 Access Log를 참조하자.
172.16.32.1 - - [20/Mar/2026:06:21:29 +0000] "GET /_blazor?id=IBzHFtEghJKWqn0ml0WzOQ HTTP/2.0" 101 47111 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"
NGINX에서는 프로토콜이 HTTP/2.0으로 확인된다. 이것은 프록시와 앱이 정상적으로 HTTP/1.1 연결을 사용하지만, 클라이언트와 프록시는 HTTP/2.0 연결을 사용한다는 것이다.
해당 서버 블록에서는 분명히 http2 지시자를 제거하였다.
server {
listen 443 ssl;
server_name example.cloudint.corp;
...
}
OpenSSL에서 실제 ALPN 협상 결과를 살펴보는 것으로, 협상 과정에서 어떤 프로토콜이 선택되는지 살펴보자.
root@ubuntu:~#
openssl s_client \
-connect example.cloudint.corp:443 \
-servername example.cloudint.corp \
-alpn h2,http/1.1 < /dev/null | grep -i "ALPN protocol"
ALPN protocol: h2
ALPN 프로토콜로 h2를 선택하였다. 이것은 NGINX가 실제로는 해당 서버 블록에 대해 HTTP/2를 advertise하고 있다는 것이다.
해결 방법
먼저 NGINX를 1.25.1 이상 버전으로 업데이트하고, 서버 블록에서 listen 지시자 옆에 있는 구형 http2 지시자를 모두 제거한다. 또한 시스템의 OpenSSL이 1.0.2h 버전 이상일 때에만 가상 호스트를 기준으로 ALPN을 고를 수 있다.
server {
listen 443 ssl http2;
server_name example.cloudint.corp;
server_tokens off;
...
}
위와 같은 http2를 사용하는 server 블록을 아래와 같이 신형 http2 지시자를 사용하도록 변경한다. 단, Blazor Server App에 대해서는 http2 off로 명시적으로 http2를 사용하지 않도록 지정한다.
server {
listen 443 ssl;
server_name example.cloudint.corp;
server_tokens off;
http2 on;
...
}
모든 설정을 완료하였다면 다시 ALPN 협상 결과를 살펴보자.
root@ubuntu:~#
openssl s_client \
-connect example.cloudint.corp:443 \
-servername example.cloudint.corp \
-alpn h2,http/1.1 < /dev/null | grep -i "ALPN protocol"
ALPN protocol: http/1.1
이제 정상적으로 WebSocket 연결이 가능할 것이다.

리버스 프록시 환경에서 Blazor Server WebSocket 연결에 문제가 있다면 Upgrade 해더만 보지 말고 아래 사항을 점검해보자.
- 앱이 https를 올바르게 인식하는가?
- 브라우저 => NGINX가 HTTP/1.1 연결을 올바르게 사용하는가?
- 실제 WebSocket Handshake가 앱까지 들어오는가?