416 Range Not Satisfiable 원인과 해결
416 Range Not Satisfiable는 Range 헤더가 파일 끝을 넘는 바이트를 요청한 것입니다. 오래된 Content-Length와 캐시 함정을 3단계로 짚어냅니다. 무료 즉시 진단으로 바로 확인.
내 도메인에 이 문제가 있는지 지금 확인
무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.
Problem
Range 헤더가 붙은 요청이 416 Range Not Satisfiable로 돌아옵니다. URL도 맞고, 리소스도 존재하고, 그냥 GET하면 잘 됩니다. 그런데 특정 바이트 조각 — Range: bytes=1000-2000 — 을 요청했고, 서버가 그 조각을 리소스의 현재 크기에 대해 검사했더니 그중 아무것도 존재하지 않습니다. 파일 끝을 넘어선 바이트를 요청한 겁니다.
Symptoms
- 일시정지된 다운로드를 이어받으려는 다운로드 매니저가 계속하지 못하고 416으로 실패.
- 영상·오디오 플레이어가 탐색(seek) 시, 혹은 첫 바이트 범위 탐침에서 416.
Range요청은 416인데 같은 URL을Range없이 요청하면 200.- 같은 서버에서 큰 파일은 되는데 작은 파일은 어떤
Range요청이든 416. - 416 응답에
Content-Range: bytes */<n>이 실려 있고<n>이 요청한 바이트 오프셋보다 작음.
What 416 Actually Means
RFC 9110(§15.5.17)은 416을 “요청의 Range 헤더 필드에 있는 범위 중 어느 것도 선택된 리소스의 현재 범위와 겹치지 않는” 경우로 정의합니다. 바이트 범위는 0-인덱스이고 양끝 포함이라 bytes=0-511은 첫 512바이트입니다. 512바이트 파일에서 bytes=1000-2000을 요청하면 보낼 게 하나도 없습니다 — 요청한 모든 바이트가 끝 너머에 있습니다.
핵심은 416이 일반적인 “잘못된 요청”이 아니라는 겁니다. 당신이 지정한 범위와 리소스가 지금 얼마나 긴지 사이의 불일치를 콕 집는 겁니다. 스펙은 416 응답이 불충족 범위와 실제 길이를 담은 Content-Range 헤더를 포함해야 한다(SHOULD)고도 합니다: Content-Range: bytes */12777. *는 “당신의 범위는 불충족”이고 12777은 현재 크기입니다. 서버는 그냥 거부하는 게 아니라, 당신이 틀린 그 숫자를 건네줘 올바르게 재시도하게 합니다.
Range를 준수하는 것 자체가 선택입니다. 부분 콘텐츠를 지원하지 않는 서버는 헤더를 무시하고 전체 본문을 200으로 돌려줍니다. 그러니 416은 사실 서버가 범위를 한다는 신호이고(Accept-Ranges: bytes를 광고), 문제는 서버 능력이 아니라 당신의 산수라는 뜻입니다.
Top 3 Causes
-
파일이 바뀐 뒤의 오래된 Content-Length - 실전 최대 원인. 클라이언트는 첫 요청에서 리소스 길이를 기록하고, 그 길이에서 유도한 바이트 오프셋에 대해 이어받거나 탐색합니다. 파일이 더 짧은 것으로 교체됐거나, 오리진 앞 CDN이 바이트 수가 다른 gzip·변환 변형을 서빙하기 시작하면, 클라이언트가 믿는 오프셋이 실제로 대화 중인 리소스의 끝을 넘습니다. 이어받는 모든 범위가 경계 밖으로 떨어져 416. 클라이언트가 기억하는 파일 크기 말고는 아무것도 고장 나지 않았습니다.
-
아주 작거나 길이 0인 리소스에 대한 범위 - 엣지 케이스지만 끈질깁니다. nginx는 과거 몇 바이트 미만 파일에 대한
Range요청을 416으로 돌려줬고(trac 티켓 #1031), 어떤 서버든 빈 본문에bytes=0-을 요청하면 416입니다 — 서빙할 0번 바이트가 없으니까. 모든 요청에 무작정Range헤더를 붙이는 헬스체크 탐침·프리페처가 플레이스홀더 파일, 파비콘, 우연히 비어 있는 생성 응답에서 이걸 밟습니다. -
잘못됐거나 뒤집힌 범위 -
bytes=2000-1000(시작이 끝보다 뒤), 파일보다 큰 음수 접미 범위, 혹은Range헤더를 오리진이 충족할 수 없는 형태로 재작성하는 프록시. 모든 파트가 경계 밖인 다중 범위 요청도 단일 416으로 무너집니다.
Diagnose with DechoNet
- HTTP Check로 깨끗한
GET에 대한 리소스의 실제 헤더를 읽으세요. 두 필드가 모든 걸 결정합니다.Accept-Ranges: bytes는 서버가 부분 콘텐츠를 지원하는지 알려줍니다 — 없거나none이면Range헤더를 보내는 건 무의미하니 멈추세요.Content-Length는 리소스의 현재 크기입니다; 클라이언트가 요청하는 바이트 오프셋이 그 숫자보다 크면 416의 원인을 찾은 겁니다. 체크가 보고하는 길이가 클라이언트가 캐시한 값과 다르면 파일이 발밑에서 바뀐 것 — 오래된 길이 케이스이고, 열에 아홉은 이게 답입니다.
Resolution Checklist
- 현재
Content-Length를 읽고 범위의 시작 바이트가 그보다 작은지 확인. 아니라면 리소스가 클라이언트 생각보다 짧습니다. - 416이 나면 응답의
Content-Range: bytes */<n>을 파싱해 권위 있는 현재 길이를 얻고,[0, <n>)안의 범위로 재요청. 0부터 다시 시작할 필요 없습니다. - 이어받기는 앞서 캡처한 ETag나
Last-Modified를If-Range로 보내세요. 리소스가 바뀌었으면 서버가 깨진 부분 응답 대신 전체 200을 반환 — 바로If-Range가 존재하는 이유입니다. -
Accept-Ranges: bytes를 보고하지 않는 리소스에Range헤더를 붙이지 마세요. 무시(200)되거나 거부(416)되며 어느 쪽도 원한 게 아닙니다. - CDN 뒤라면 엣지와 오리진이 바이트 길이에 합의하는지, 압축·이미지 변환이 클라이언트가 측정한 크기와 오리진이 지금 서빙하는 크기 사이를 바꾸지 않는지 확인하세요.
- 요청을 다시 실행해 또 416이 아니라
206 Partial Content(범위 허용) 또는200 OK(전체 본문)가 나오는지 확인.
When to Escalate
- 여러 파일에서 이어받기가 한꺼번에 416이면, 리소스의 바이트 길이가 대량으로 바뀐 겁니다 — 에셋을 재생성한 재배포, 새 압축 계층, 변환 변형을 서빙하는 캐시. 인프라 변경이고, 오리진이나 CDN 설정을 소유한 사람의 몫입니다.
- 미디어 서버가 파일 안쪽에 잘 드는 정상 탐색에 416을 낸다면, 서버가 리소스 길이를 잘못 계산 중일 수 있습니다(동적 본문, 스트림 생성, 실제 가용 바이트와 맞지 않는
Content-Length). 틀린 길이에 대해 검증된 범위는 클라이언트 버그가 아니라 서버 버그입니다.
관련 도구
관련 가이드
가이드 공유