WebSocket은 일반 HTTP 요청과 다르게, HTTP로 시작해서 프로토콜을 업그레이드하는 핸드셰이크를 거칩니다. 그래서 "REST는 되는데 WebSocket만 안 된다"는 상황이 흔합니다. 대부분의 원인은 핸드셰이크 단계의 헤더 누락이거나, 중간 프록시가 업그레이드를 막거나 연결을 일찍 끊는 것입니다.
핸드셰이크부터 이해하기
WebSocket 연결은 클라이언트가 Upgrade: websocket 헤더를 담은 HTTP 요청을 보내고, 서버가 101 Switching Protocols로 답하면서 시작됩니다. 핵심 헤더는 두 개입니다.
Upgrade: websocket
Connection: Upgrade
이 핸드셰이크에 성공하면 101, 무언가 막히면 다른 코드가 돌아옵니다. 즉 응답 코드가 곧 진단의 출발점입니다.
426 Upgrade Required — 가장 흔한 신호
426 Upgrade Required는 서버(또는 프록시)가 "이 엔드포인트는 평범한 HTTP가 아니라 업그레이드된 프로토콜이 필요하다"고 거절하는 것입니다. 보통 클라이언트나 중간 프록시가 Upgrade·Connection 헤더를 제대로 전달하지 못했을 때 뜹니다.
curl로 핸드셰이크를 흉내 내 응답을 직접 봅니다.
curl -i -N \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
-H "Sec-WebSocket-Version: 13" \
http://app.example.com/ws
101 Switching Protocols가 나오면 서버는 정상입니다. 426이나 400이면 헤더 전달 경로(특히 프록시)를 의심합니다.
증상별 원인
| 증상 | 의미 | 1순위 점검 |
|---|---|---|
| 426 / 400 | 업그레이드 헤더 누락·거절 | 프록시가 Upgrade·Connection 전달하는지 |
| 200 (HTML 반환) | 라우팅이 일반 HTTP로 처리됨 | 프록시의 /ws 경로 매칭 |
| 101 후 곧 끊김 | 핸드셰이크는 OK, 연결 유지 실패 | 프록시 idle 타임아웃 |
| 연결 후 일정 시간마다 끊김 | 유휴 연결 종료 | 타임아웃·핑/퐁(heartbeat) |
프록시 설정이 핵심
리버스 프록시 뒤에서는 업그레이드 헤더를 명시적으로 넘겨줘야 합니다. nginx라면 location에 다음이 있어야 101이 통과합니다.
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
101 이후 일정 시간마다 끊긴다면 거의 프록시의 proxy_read_timeout(유휴 한도)입니다. 값을 늘리되, 애플리케이션에서 주기적인 핑/퐁으로 연결을 살아 있게 유지하는 게 더 견고한 해법입니다.
점검 체크리스트
# 핸드셰이크 직접 확인 (101 나와야 정상)
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
-H "Sec-WebSocket-Version: 13" http://<host>/ws
# 426/400 → 프록시 Upgrade·Connection 헤더 전달 확인
# 101 후 주기적 끊김 → proxy_read_timeout + 핑/퐁
WebSocket 핸드셰이크와 프록시 헤더·타임아웃을 직접 끊어보며 진단하는 실습은 네트워크 트랙에서 회원가입 없이 무료로 할 수 있습니다.