ERR_HTTP2_SERVER_REFUSED_STREAM: Chrome 해결
ERR_HTTP2_SERVER_REFUSED_STREAM는 서버가 요청을 처리하지 않고 거부했다는 뜻입니다. 로드밸런서·HTTP/2 한도·쿠키를 점검하세요. 무료 HTTP 진단으로 바로 확인.
내 도메인에 이 문제가 있는지 지금 확인
무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.
문제
Chrome이 페이지나 일부 리소스를 ERR_HTTP2_SERVER_REFUSED_STREAM로 로드하지 못합니다. 자주 간헐적이죠 — 새로고침하면 되거나, 매번 다른 파일들에서 발생합니다. 이 오류는 대부분의 “캐시를 지우세요” 조언이 완전히 놓치는 정확한 의미를 갖습니다: 서버는 크래시하지도, 타임아웃하지도 않았습니다. 요청 처리를 의도적으로 거부했고, Chrome이 조용히 재시도한 뒤에도 여전히 거부하고 있습니다.
증상
- Chrome(또는 Edge)이
net::ERR_HTTP2_SERVER_REFUSED_STREAM를 냅니다 — 페이지 전체보다 개별 에셋(JS, CSS, 이미지)에 대해 자주 뜹니다. - 간헐적이고, 새로고침할 때마다 실패하는 요청 집합이 바뀌는 경우가 많습니다 — 깨진 URL이 아니라 연결 수준·용량 문제라는 강한 신호입니다.
- 같은 CDN·IP 뒤에 있는 여러 사이트에 걸쳐 나타날 수 있습니다.
- 사이트 쿠키를 지우거나, 확장 프로그램을 끈 시크릿 모드에서 사라지기도 합니다.
이 오류의 진짜 의미
HTTP/2는 하나의 장수 연결 위에 여러 요청을 독립된 스트림으로 실어 나릅니다. 서버는 스트림을 거부할 수 있고, 깔끔한 두 가지 방법이 있습니다. 오류 코드 REFUSED_STREAM(0x7)을 담은 RST_STREAM 프레임을 보내거나, 오류 코드 NO_ERROR에 당신 요청의 스트림 ID보다 낮은 Last-Stream-ID를 담은 GOAWAY 프레임을 보내 — “이 연결을 정리하는 중이고 이 지점 이후는 처리하지 않겠다”고 클라이언트에게 알리죠. Chrome은 두 패턴을 모두 인식하고, 요청이 이런 식으로 거부되면 ERR_HTTP2_SERVER_REFUSED_STREAM을 냅니다.
결정적 디테일은 거부가 요청이 처리되지 않았다는 약속이라는 점입니다. RFC 9113 §8.1.1은 명시합니다: REFUSED_STREAM으로 리셋된 스트림의 요청은 “안전하게 재시도할 수 있다.” 그래서 이 오류가 첫 발생에 사용자에게 보이는 일은 드뭅니다 — Chrome이 힌트를 받아들여 새 연결에서 요청을 자동 재시도하니까요. 그러니 실제로 이 오류를 본다면 재시도마저 거부됐다는 뜻입니다. 거부는 배포 중 한 번의 우아한 GOAWAY가 아니라 계속 반복되는 상태인 것이죠.
이게 진단 전체를 다시 짭니다. 당신은 깨진 페이지를 찾는 게 아닙니다. 서버·로드밸런서·프록시가 왜 Chrome에게 “지금은 안 돼”를 반복하는지를 찾는 겁니다 — 용량, 우아한 종료의 루프, 아니면 노골적인 HTTP/2 버그.
가장 흔한 원인 3가지
- HTTP/2 구현에 버그가 있는 로드밸런서·리버스 프록시 - 실무에서 가장 흔한 원인이고, 오리진은 멀쩡해서 답답합니다. 프론트엔드 장비의 HTTP/2 스택이 특정 조건에서 스트림을 거부하는 버그를 내보낸 적이 있습니다 — 잘 알려진 사례로 F5 BIG-IP의 HTTP/2 프로파일이 Chrome 클라이언트에 스트림을 거부한 건이 있고, HAProxy·nginx 기반 프록시에도 유사 보고가 있습니다. 요청이 앱에 닿지도 못하고 엣지가 죽입니다. 수정은 프록시 쪽입니다: 패치하거나, (임시방편으로) HTTP/2 프로파일을 재검토하세요.
- 연결이 동시성·우아한 종료 경계에 부딪힘 - 서버에는
SETTINGS_MAX_CONCURRENT_STREAMS한도가 있습니다. 예의 바른 클라이언트는 한도에 닿으면 새 스트림을 큐에 넣지만, 취약한 서버·프록시는 대신 거부할 수 있습니다. 별개로, 배포·스케일다운·keepalive 만료 때 서버는 연결을 비우려 GOAWAY를 보냅니다 — 정상이고, Chrome은 다른 데서 재시도해야 하죠. 로드밸런서가 브라우저를 계속 정리 중인 연결로 되돌려보내 모든 재시도가 종료 중인 곳에 떨어지면 보이는 오류가 됩니다. - 과대한 요청 헤더, 흔히 비대한 쿠키 - 쿠키가 잔뜩 쌓이거나(또는 거대한 JWT) 요청 헤더 블록을 부풀립니다. 일부 HTTP/2 구현은
SETTINGS_MAX_HEADER_LIST_SIZE를 잘못 처리해 깔끔한 431을 반환하는 대신 스트림을 거부합니다. 사이트 쿠키를 지우면 가끔 되는 이유이고 — 앱이 쿠키를 다시 만들면 재발하는 이유이기도 합니다.
DechoNet으로 진단하기
- HTTP 진단은 당신 브라우저와 네트워크 바깥에서 사이트를 가져옵니다. 브라우저는 계속 실패하는데 여기서 깨끗한 응답이 오면 문제는 당신 쪽 — 합쳐지거나 재사용된 연결, 확장 프로그램, 비대한 쿠키 — 이지 오리진이 아닙니다.
- SSL 진단은 TLS와 ALPN이 건강한지 확인합니다. HTTP/2는 TLS 핸드셰이크 중에 협상되므로, 깨끗한 결과는 스트림 오류 아래 계층을 배제합니다.
- 포트 진단은 443 포트 도달 가능성을 확인해, 진짜 연결 문제와 HTTP/2 수준의 거부를 갈라냅니다.
해결 체크리스트
- 클라이언트냐 서버냐부터 가르세요. 외부 HTTP 진단을 돌리거나 확장 프로그램을 끈 시크릿 모드에서 사이트를 여세요. 외부에선 깨끗한데 브라우저에서 깨지면 당신 쪽 쿠키·확장·연결 재사용을 의심하세요.
- 해당 사이트의 쿠키를 지우세요(또는 새 프로필에서 테스트). 그걸로 고쳐지면 요청 헤더가 과대했던 것입니다 — 앱이 쿠키에 담는 걸 줄이고, 서버가 스트림 거부 대신 제대로 431을 반환하게 하세요.
- 많은 사용자에게, 또는 한 엣지 뒤 여러 사이트에 실패하면 프록시·LB의 HTTP/2 버그로 다루세요: 로드밸런서·CDN·리버스 프록시 소프트웨어를 업데이트하고 벤더의 HTTP/2 스트림 거부 수정 노트를 확인하세요.
- 배포·스케일링 이벤트 주변에 몰리면 우아한 종료를 보세요: GOAWAY 비우기와 로드밸런서 연결 처리가 클라이언트를 종료 중인 연결에서 깨끗이 옮겨가게 하는지(되돌려보내지 않는지) 확인하세요.
- 변경 후 HTTP 진단을 다시 돌려 요청이 거부가 아니라 처리되고 있는지 확인하세요.
에스컬레이션 시점
- 외부 HTTP 진단은 오리진이 건강하다고 하는데 오류가 CDN·로드밸런서를 통해서만 나타나면, 그것이 RST_STREAM REFUSED_STREAM(또는 GOAWAY 비우기)을 보내고 있다는 사실과 함께 그 벤더에 에스컬레이션하세요 — 그건 당신 애플리케이션이 아니라 프론트엔드 HTTP/2 문제입니다.
- 트래픽 양과 함께 움직이면 용량 또는
MAX_CONCURRENT_STREAMS처리 문제로 올리세요: 앞단 계층이 부하 시 스트림을 큐에 넣지 않고 거부하는 것입니다. - 릴리스 시간대와 엮이면 배포·로드밸런서 설정을 담당하는 쪽에 넘기세요 — 클라이언트가 옮겨갈 수 있는 속도보다 빠르게 연결이 비워지고 있습니다.
관련 도구
관련 가이드
가이드 공유