ERR_INVALID_CHUNKED_ENCODING: 깨진 프레이밍 해결
ERR_INVALID_CHUNKED_ENCODING는 응답이 잘린 게 아니라 청크 프레이밍 자체가 깨진 것. double-chunking이나 잉여 출력을 찾으세요. 무료 HTTP 진단으로 바로 확인.
내 도메인에 이 문제가 있는지 지금 확인
무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.
문제
Chrome 콘솔에 net::ERR_INVALID_CHUNKED_ENCODING가 뜨고, 종종 200 (OK) 상태와 함께 나오면서 페이지가 빈 화면이거나 반쯤 깨진 채 렌더됩니다. 서버는 응답했습니다 — 상태 줄과 헤더는 도착했죠 — 그런데 본문이 Chrome이 파싱할 수 없는 chunked 스트림으로 프레이밍돼 있습니다. 이건 도중에 잘려버린 응답이 아닙니다. chunk를 서술하는 바이트 자체가 틀렸습니다.
증상
- 콘솔에
net::ERR_INVALID_CHUNKED_ENCODING가 뜨고, 요청이 성공한 것처럼 보이게 만드는200 (OK)가 자주 따라붙습니다. - 페이지가 비었거나 내용이 빠져 있고, JavaScript·CSS가 잘려 로드돼 폼 제출과 동적 동작이 깨집니다.
- 응답 헤더에
Transfer-Encoding: chunked가 있습니다. - 같은 엔드포인트에서 일관되게 실패합니다 — 잘린 스트림의 간헐적·크기 의존적 flaky함이 아닙니다.
- 같은 URL이 curl이나 더 관대한 브라우저에선 멀쩡히 렌더돼 Chrome 버그처럼 보입니다.
이 에러의 실제 의미
서버가 길이를 미리 모른 채 본문을 보낼 때, HTTP/1.1은 chunked transfer coding(RFC 9112 §7.1)을 씁니다. 본문은 chunk의 연속이고, 각 chunk는 엄격한 형태를 가집니다: chunk 크기를 16진수로 적은 줄, CRLF, chunk 데이터, 또 CRLF, 그다음 chunk — 마지막에 zero-length chunk(0\r\n\r\n)가 끝을 표시할 때까지. 이 크기 줄들이 각 chunk가 어디서 시작하고 끝나는지 클라이언트에게 알려주는 뼈대입니다.
ERR_INVALID_CHUNKED_ENCODING(Chromium net error -270)는 그 뼈대가 깨졌다는 Chrome의 말입니다. 스트림 어딘가에서 16진수 크기 줄을 기대했는데 파싱되지 않는 것이 나온 겁니다 — 16진수 아닌 문자, 빠진 CRLF, 뒤따르는 바이트와 안 맞는 크기, 또는 chunk 사이에 낀 쓰레기. Chrome은 이 문법을 엄격하게 파싱하고 복구를 시도하지 않으며, 그냥 중단합니다.
중요한 구분: 이건 chunk가 유효한데 스트림만 일찍 끝나는 ERR_INCOMPLETE_CHUNKED_ENCODING이 아닙니다. 여기선 chunk가 깨졌습니다. 응답이 부족했던 게 아니라 잘못 조립된 겁니다. 이건 프레이밍을 작성한 쪽을 지목합니다: 애플리케이션, 프레임워크의 HTTP 계층, 또는 크기를 고치지 않고 본문을 다시 건드린 프록시.
주요 원인 3가지
- 이중 또는 충돌하는 프레이밍. 응답이
Content-Length와Transfer-Encoding: chunked를 둘 다 담고 있거나, 경로상의 무언가가 이미 chunk된 본문을 또 chunk합니다. RFC 9112 §6.1에 따르면 chunked가 우선하고 길이는 버려야 하지만, 자체 chunk 마커를 직접 쓰는 hand-rolled HTTP 계층이 서버로 하여금 또 chunk하게 두면 유효한 chunk로 파싱되지 않는 바이트열이 나옵니다. 자체 chunk 크기를 틀리게 내보내는 프레임워크·런타임 뒤에 있는 전형적 원인입니다. - 크기 줄을 오염시키는 잉여 바이트. chunk는 16진수 크기와 CRLF로 시작하고, 그 구조 밖에는 아무것도 나오면 안 됩니다. 애플리케이션이 framer가 계산하지 않은 무언가를 스트림에 출력하면 — 응답 도중 echo된 PHP notice·warning, 닫는
?>뒤의 공백·개행, 에디터가 붙인 UTF-8 BOM, 흘러나온 디버그 출력 — 바이트 오프셋이 밀리고 Chrome이 읽는 다음 “크기 줄”이 더는 유효한 16진수가 아니게 됩니다. 첫 삽입 바이트부터 프레이밍이 산술적으로 어긋납니다. - 본문을 재작성하면서 프레이밍은 안 고치는 중간 장비. 압축 프록시, WAF, TLS 검사 장비가 본문을 변형하면서 — 스크립트 주입, 내용 삭제, 인코딩 변경 — chunk 크기를 다시 계산하지 않으면 선언된 크기가 실제 바이트와 어긋납니다. 오리진은 올바르게 프레이밍했는데 중간 장비가 깨뜨린 겁니다.
DechoNet으로 진단
- HTTP 진단은 브라우저와 로컬 네트워크 밖, 우리 서버에서 URL을 가져와 응답 헤더와 상태를 보여줍니다.
Transfer-Encoding: chunked를 확인해주고 거기서도 응답이 깨져 있으면, 문제는 오리진이나 upstream 프록시에 있습니다 — 브라우저·확장·로컬 보안 소프트웨어가 아니라. - 외부 fetch는 깨끗한데 브라우저만 실패하면, 프레이밍이 당신 쪽 연결에서 깨지는 겁니다: 로컬 프록시, 백신 웹 실드, 본문을 변형하는 회사 TLS 검사 장비. 탐색 범위가 즉시 당신 머신과 네트워크 경로로 좁혀집니다.
- 이 실패는 결정론적이라, 서버 대 클라이언트를 가리는 데 외부 fetch 한 번이면 충분합니다 — 간헐적 잘림처럼 계속 두드릴 필요가 없습니다.
해결 체크리스트
- chunked인지 고정 길이인지 확인. 응답 헤더에서
Transfer-Encoding: chunked를 확인하세요. chunked 없이Content-Length만 보이면 다른 프레이밍 버그인ERR_CONTENT_LENGTH_MISMATCH를 보고 있는 겁니다. - 렌더된 페이지 말고 raw 회선을 보라. URL에
curl --raw -v를 돌려 chunk 크기 줄을 직접 읽으세요. 깨진 16진수, 튀어나온 빈 줄, 본문 맨 위의 BOM이 DevTools에선 절대 안 보이는 방식으로 여기서 보입니다. - 이중 프레이밍을 없애라. 앱·프레임워크가 자체 chunk 마커를 쓰는 동안 서버도
Transfer-Encoding: chunked를 적용하지 않는지, 어떤 응답도Content-Length와 chunked를 둘 다 담지 않는지 확인하세요. 프레이밍은 하나만 골라 한 계층이 소유하게. - 잉여 출력을 사냥하라. 본문에 출력된 PHP notice·warning,
?>뒤 공백·개행, 에디터가 붙인 UTF-8 BOM, 응답 스트림에 새어든 디버그echo·print를 확인하세요. chunk 구조 밖의 어떤 바이트든 프레이밍을 오염시킵니다. - 오리진이 멀쩡하면 중간을 의심하라. 애플리케이션이 단독으론 유효한 프레이밍을 내보내는데 브라우저는 쓰레기를 본다면, 프록시·WAF·압축 계층·TLS 검사 장비가 크기를 안 고치고 본문을 재작성하는 겁니다. 그걸 우회해서 테스트하세요.
- 클라이언트 대 서버 격리. 외부 HTTP 진단이나 다른 네트워크의
curl --raw로 URL을 가져오세요. 외부에서 재현 → 오리진이나 upstream 프록시 수정. 브라우저에서만 실패 → 로컬 프록시, 회사 MITM, 백신을 보세요.
에스컬레이션 시점
- raw 응답이 오리진에선 올바르게 프레이밍됐는데 그 앞의 CDN·엣지가 깨진 chunk를 서빙한다면, 그 엣지를 소유한 쪽에 에스컬레이션하세요 — 본문이 당신이 통제 못 하는 계층에서 재작성되고 있고, 재프레이밍을 건너뛴 변환은 그쪽 설정에서만 보입니다.
- 깨진 프레이밍이 당신 코드가 아니라 프레임워크·런타임의 HTTP 스택에서 온다면, 라이브러리 버그로 취급하세요: 버전을 고정하거나 올리고, 그 위에 얹은 수동 chunking을 끄세요. 출력을 downstream에서 정화하려 들지 말고.
관련 도구
관련 가이드
가이드 공유