조회수: 21

ERR_INCOMPLETE_CHUNKED_ENCODING: 응답 잘림

ERR_INCOMPLETE_CHUNKED_ENCODING는 청크 응답이 마지막 청크 전에 끊긴 것입니다. 백엔드 크래시·nginx 임시경로·프록시를 3단계로 격리합니다. 무료 즉시 HTTP 진단.

내 도메인에 이 문제가 있는지 지금 확인

무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.

Problem

페이지가 렌더링을 시작하다 멈춥니다 — 콘텐츠 절반, 깨진 이미지, JSON 대부분을 반환하고 나머지가 없는 API 호출. Chrome 콘솔에 net::ERR_INCOMPLETE_CHUNKED_ENCODING가 뜹니다. 서버가 응답을 보냈고, 브라우저가 읽기 시작했고, 그런 다음 응답이 “끝났다”고 말하기 전에 바이트가 동났습니다.

Symptoms

  • 페이지가 부분적으로만 로드됩니다 — 위쪽은 렌더링되고 아래쪽은 없거나 요소 중간에서 잘립니다.
  • DevTools 네트워크 탭에 요청이 (failed)/(canceled)로 표시되고 콘솔에 ERR_INCOMPLETE_CHUNKED_ENCODING가 나옵니다.
  • 간헐적입니다: 같은 URL이 한 번은 되고 다음엔 안 되거나, 큰 페이지에서만 실패합니다.
  • 응답 헤더에 Content-Length가 아니라 Transfer-Encoding: chunked가 있습니다.
  • 정적 자산은 멀쩡하고, 실패는 동적 페이지나 스트리밍 API 엔드포인트에 몰립니다.

What This Error Actually Means

서버가 응답을 보내기 시작하기 전에 길이를 모를 때 — DB 쿼리로 조립한 페이지, 스트리밍 내보내기, 즉석에서 생성되는 모든 것 — Content-Length 헤더를 설정할 수 없습니다. HTTP/1.1은 이걸 청크 전송 코딩(RFC 9112 §7.1)으로 해결합니다: 본문을 여러 청크로 나눠 보내고, 각 청크 앞에 크기를 16진수로 붙이고, 전체를 last-chunk — 크기 0인 청크(0\r\n) 뒤에 선택적 트레일러와 마지막 빈 줄 — 로 끝냅니다. 그 0바이트 청크가 “다 보냈다” 신호입니다. 그게 오기 전까지 클라이언트는 계속 읽습니다.

ERR_INCOMPLETE_CHUNKED_ENCODING는 그 last-chunk가 나타나기 전에 연결이 닫혔다고 Chrome이 알려주는 것입니다. 서버는 응답을 청크로 프레이밍했고, 더 온다고 약속했고, 그런 다음 소켓이 조용해졌습니다. Chrome은 잘린 걸 아는 본문을 렌더링하지 않습니다 — 꼬리가 조용히 빠진 페이지를 보여주느니 중단합니다.

새겨둘 핵심: 이건 손상 오류나 체크섬 실패가 아닙니다. 도착한 바이트는 멀쩡합니다. 문제는 프레이밍은 “계속”이라 했는데 스트림은 “안녕”이라 했고, 둘이 어긋났다는 것입니다. 그 어긋남은 거의 항상 상위의 무언가가 응답을 만드는 도중에 죽었거나 포기했다는 뜻입니다.

Top 3 Causes

  1. 백엔드가 응답 도중 크래시하거나 타임아웃했다. 무거운 페이지에서 PHP-FPM 워커가 memory_limit에 걸리고, max_execution_time이 발동하고, 앱이 헤더와 본문 일부를 이미 내보낸 뒤 예외를 던지고, 앱 서버가 OOM 킬러에 죽습니다. 헤더(Transfer-Encoding: chunked 포함)는 이미 나갔고, 종료 청크는 결코 안 나갑니다. 간헐적이고 크기에 좌우되는 실패의 전형적 원인입니다 — 한도에 닿을 만큼 크거나 느린 요청에서만 문제가 터지니까요.
  2. 리버스 프록시가 응답 버퍼링을 못 끝낸다. nginx는 업스트림 응답을 임시 파일에 버퍼링하는데, 그걸 못 쓰면 — 임시/캐시 경로의 잘못된 소유권(잘 알려진 chown www-data /var/lib/nginx 수정), 꽉 찬 디스크, 소진된 proxy_temp_path — 응답을 도중에 포기합니다. 오리진 앱은 멀쩡하고 프록시가 꼬리를 떨어뜨리는 것입니다.
  3. HTTP/1.0 백엔드가 프록시를 통해 청크를 말한다. 청크 코딩은 HTTP/1.1 기능이라 HTTP/1.0에서 쓰면 안 됩니다. 앱이 청크 응답을 내보내는데 nginx가 그것과 HTTP/1.0으로 대화하면(proxy_pass 기본값) nginx가 깔끔하게 재조립하지 못하고 브라우저는 깨진 스트림을 봅니다. 수정은 proxy_http_version 1.1;과 업스트림의 Connection 헤더 비우기입니다.

Diagnose with DechoNet

  • HTTP 진단은 당신 브라우저와 로컬 네트워크 밖에 있는 우리 서버에서 URL을 가져옵니다. 거기서는 응답이 깔끔하게 완결되는데 브라우저에서 실패하면, 잘림은 당신 쪽에서 일어나는 것입니다 — 로컬 프록시, TLS 검사 장비, 백신 웹 실드 — 오리진이 아니라.
  • 같은 진단이 응답 헤더를 보여줍니다. 서버가 실제로 Transfer-Encoding: chunked를 보내는지(다른 오류를 가리킬 Content-Length가 아니라), CDN이나 리버스 프록시가 경로에 있는지 확인하세요. 프레이밍이 애초에 청크인지 아는 게 결정 트리의 첫 갈림길입니다.
  • 몇 번 다시 실행하세요. 실패가 간헐적이고 크기에 좌우되므로 한 번 깔끔한 요청으로 서버가 무죄가 되진 않지만 — 네트워크 밖에서 잘림을 재현한 요청은 오류가 상위에 있음을 확정하고, 노트북 디버깅을 멈추라고 알려줍니다.

Resolution Checklist

  • 고정 길이가 아니라 청크인지 확인. 응답 헤더에서 Transfer-Encoding: chunked를 확인하세요. 대신 Content-Length가 보이면 엉뚱한 오류를 쫓는 것입니다 — 그건 ERR_CONTENT_LENGTH_MISMATCH 영역입니다.
  • 실패한 요청의 백엔드 로그를 읽어라. OOM 킬, PHP의 memory_limit/max_execution_time, 워커 재시작, 출력 시작 에 터진 미처리 예외를 찾으세요. 타임스탬프가 잘린 요청과 맞아떨어집니다.
  • 발동하는 한도를 올려라. 큰 응답에서 메모리나 실행 시간이면 memory_limit/max_execution_time(PHP)이나 해당 워커 타임아웃을 올리거나 — 더 낫게는, 과대 응답을 만드는 쿼리나 뷰를 고치세요.
  • 프록시의 임시/캐시 디렉터리를 확인하라. nginx라면 워커 사용자가 proxy_temp_path(및 /var/lib/nginx)를 소유하는지, 디스크가 안 찼는지, 버퍼링이 조용히 실패하지 않는지 확인하세요. 에러 로그가 못 쓴 경로를 이름으로 알려줍니다.
  • HTTP/1.0-청크 불일치를 고쳐라. 앱이 청크 응답을 보내면 proxy_http_version 1.1;proxy_set_header Connection "";을 설정해 nginx가 업스트림에 1.1로 말하게 하세요.
  • 클라이언트 대 서버를 격리하라. 다른 네트워크에서 외부 HTTP 진단이나 curl -v로 URL을 가져오세요. 외부에서 재현되면 → 오리진/프록시를 고치고. 당신 브라우저에서만 실패하면 → 로컬 프록시, 회사 MITM, 백신을 보세요.

When to Escalate

  • 백엔드 로그가 깨끗한데 외부 요청도 여전히 잘리면 리버스 프록시나 CDN 소유자에게 넘기세요 — 응답이 당신이 통제 못 하는 엣지에서 죽고 있을 수 있고, 그쪽 로그만이 중단된 업스트림 읽기를 보여줍니다.
  • 배포나 트래픽 급증 직후 실패가 시작됐다면 설정 버그가 아니라 용량 문제로 다루세요: 이제 무언가가 예전엔 여유 있던 한도를 넘을 만큼 커지거나 느려진 것입니다. 커진 응답을 찾는 동안 롤백하거나 한도를 올리세요.

관련 도구

관련 가이드

가이드 공유

[Ad] Guide Detail Inline
← 전체 가이드 보기