도입
HTTP를 공부하다 보면 hop-by-hop 헤더라는 표현을 자주 보게 됩니다. 이름만 보면 단순히 “중간에 거치는 헤더”처럼 들리지만, 실제 의미는 더 정확합니다.
이 개념은 프록시, 게이트웨이, CDN, 로드 밸런서처럼 중간자가 있는 환경에서 특히 중요합니다. 어떤 필드는 최종 목적지까지 그대로 전달되어야 하지만, 어떤 필드는 현재 연결에서만 의미가 있으므로 다음 홉으로 그대로 넘기면 안 됩니다. hop-by-hop 헤더는 바로 이 둘을 구분하는 개념입니다.
필요성
애플리케이션 개발자는 보통 HTTP를 클라이언트와 서버의 단순한 1:1 통신으로 생각하기 쉽습니다. 하지만 실제 운영 환경에는 리버스 프록시, API 게이트웨이, CDN, 서비스 메시, 로드 밸런서 같은 중간자가 많습니다.
이 환경에서 connection-specific 정보가 다음 홉으로 잘못 전달되면, 뒤쪽 서버가 잘못된 연결 상태를 가정하거나, 지원하지 않는 필드를 받아 프로토콜 위반으로 처리하거나, 심하면 보안 문제로 이어질 수 있습니다. 따라서 hop-by-hop 헤더는 단순 문법이 아니라 중간자 환경의 안전 규칙에 가깝습니다.
정의
HTTP Semantics는 `Connection` 헤더가 현재 연결에만 적용되는 필드를 선언적으로 구분하는 장치라고 설명합니다. 이때 그 즉시 수신자에게만 의미가 있는 필드를 hop-by-hop, 체인상의 모든 수신자에게 의미가 있는 필드를 end-to-end로 구분합니다.
쉽게 말하면 hop-by-hop 헤더는 “지금 이 연결에만 필요한 메타데이터”이고, end-to-end 헤더는 “최종 목적지까지 유지되어야 하는 메타데이터”입니다. 이 차이를 모르면 프록시가 무엇을 전달하고 무엇을 끊어야 하는지 판단하기 어렵습니다.
핵심 원리
현재 HTTP Semantics 기준에서 hop-by-hop 여부를 구분하는 중심은 `Connection` 헤더입니다. 어떤 필드가 현재 연결에 대한 제어 정보를 제공한다면, 발신자는 그 필드 이름을 `Connection` 안에 함께 적어야 합니다.
중간자는 메시지를 포워딩하기 전에 `Connection`에 적힌 각 connection-option 이름과 동일한 헤더/트레일러 필드를 제거하고, `Connection` 헤더 자체도 제거하거나 자기 연결에 맞는 값으로 다시 만들어야 합니다. 즉, hop-by-hop 헤더는 “자동으로 다 알 수 있는 목록”이 아니라, 기본적으로는 `Connection`이 선언하는 연결 전용 필드들입니다.
GET / HTTP/1.1
Host: example.com
Connection: close
GET /chat HTTP/1.1
Host: example.com
Connection: Upgrade
Upgrade: websocket
기본 구조
| 구성 요소 | 의미 | 실무 포인트 |
|---|---|---|
| Connection | 현재 연결에 대한 제어 옵션 선언 | hop-by-hop 필드 구분의 중심 |
| Connection Option | 현재 연결 전용 필드 이름 또는 옵션 토큰 | 대소문자 구분 없이 해석 |
| Intermediary | 프록시, 게이트웨이, CDN 같은 중간자 | 포워딩 전에 hop-by-hop 항목 제거 책임이 있음 |
| End-to-End Header | 모든 수신자 체인에 의미가 있는 필드 | 예를 들어 Cache-Control을 Connection 옵션으로 쓰면 안 됨 |
패턴 1. Connection 헤더가 중심인 이유
RFC 9110은 어떤 필드가 현재 연결을 위한 제어 정보를 실을 때, 발신자가 반드시 그 필드 이름을 `Connection` 헤더 안에 적어야 한다고 설명합니다. 그리고 중간자는 그 이름들을 보고 동일한 이름의 헤더나 트레일러를 제거해야 합니다.
또한 `Connection` 옵션은 현재 메시지에 실제 필드가 꼭 존재하지 않아도 될 수 있지만, 반대로 connection-specific 필드가 `Connection` 옵션 없이 나타났다면 대개 중간자가 잘못 전달한 것일 가능성이 높다고 설명합니다. 즉, Connection은 단순 참고 정보가 아니라 중간자 처리 규칙을 활성화하는 핵심 스위치입니다.
GET /resource HTTP/1.1
Host: example.com
Connection: TE
TE: trailers
위 예시처럼 `TE`를 보내려면 `Connection: TE`도 함께 보내야 합니다. 이 규칙이 없으면 중간자가 `TE`를 end-to-end 필드로 오해하고 다음 홉으로 잘못 전달할 수 있습니다.
패턴 2. 대표적인 connection-specific 필드
공식 문서는 중간자가 `Connection`에 이름이 올라왔는지와 상관없이, 의미상 제거가 필요한 것으로 알려진 필드들도 제거하거나 교체해야 한다고 설명하면서 대표 예를 직접 나열합니다.
| 필드 | 의미 | 실무 포인트 |
|---|---|---|
| Connection | 현재 연결 제어 옵션 목록 | 포워딩 전에 자체도 제거 대상 |
| Keep-Alive | 역사적으로 연결 유지 협상에 쓰인 필드 | 프록시 체인에서 잘못 전달되면 오동작 여지 |
| Proxy-Connection | 예전 프록시 호환 시도에서 나온 비표준 필드 | 현재는 보내지 말도록 권고됨 |
| TE | 전송 코딩과 trailer 수용 능력 표시 | HTTP/1.1에서는 Connection 옵션 동반 필요 |
| Transfer-Encoding | 현재 연결에서의 전송 인코딩/프레이밍 | HTTP/2·HTTP/3에서는 금지 |
| Upgrade | 같은 연결 위 프로토콜 전환 제안 | `Connection: Upgrade`와 같이 가야 함 |
여기서 특히 `Proxy-Connection`은 주의할 만합니다. HTTP/1.0 시절 프록시 호환 문제를 풀기 위한 시도로 등장했지만, 다단 프록시 환경에서는 같은 문제가 반복되어 결국 잘 작동하지 않았고, 현재 RFC 9112는 클라이언트가 이 필드를 보내지 않도록 권고합니다.
패턴 3. TE 와 Trailer, HTTP/2·HTTP/3 차이
TE는 클라이언트가 transfer coding과 trailer section 처리 능력을 알리는 필드입니다. HTTP/1.1에서는 이 필드를 보내는 쪽이 반드시 `Connection: TE`도 함께 보내야 하므로, 분명한 connection-specific 필드입니다.
반면 Trailer는 “이 메시지의 trailer section에 어떤 필드가 올 수 있는가”를 미리 알리는 필드입니다. 이 필드는 trailer metadata 예고에 가깝고, 현재 RFC 9110이 대표적인 connection-specific 제거 목록으로 직접 든 필드에는 포함되지 않습니다. 즉, 실무에서 `TE`와 `Trailer`를 같은 급의 hop-by-hop 필드로 기계적으로 묶으면 틀리기 쉽습니다.
또한 HTTP/2와 HTTP/3에서는 상황이 더 엄격합니다. 두 프로토콜 모두 `Connection`을 이용한 connection-specific 필드 방식을 쓰지 않으며, `Connection`, `Keep-Alive`, `Proxy-Connection`, `Transfer-Encoding`, `Upgrade` 같은 필드를 포함한 메시지는 malformed로 처리해야 합니다. 예외는 `TE`뿐인데, 이때도 값은 오직 `trailers`만 허용됩니다.
# HTTP/1.1에서는 가능
Connection: TE
TE: trailers
# HTTP/2 / HTTP/3에서는 TE가 있더라도 값은 "trailers"만 허용
TE: trailers
프록시와 게이트웨이 처리 원칙
RFC 9110은 중간자가 받은 `Connection` 헤더를 먼저 파싱하고, 그 안에 들어 있는 각 connection-option 이름과 동일한 헤더/트레일러를 제거한 뒤, `Connection` 자체도 제거하거나 새 연결의 제어 옵션으로 교체하라고 요구합니다.
또한 어떤 필드가 content 전체 수신자에게 의미가 있는 end-to-end 필드라면, 그것을 `Connection` 옵션으로 올리면 안 됩니다. 공식 문서는 `Cache-Control`을 대표적인 반례로 직접 듭니다. 즉, connection-specific와 representation/control metadata를 혼동하면 안 됩니다.
프록시가 해야 하는 일
1) Connection 헤더를 먼저 본다
2) 거기 적힌 이름과 같은 필드를 제거한다
3) Connection 헤더 자체도 제거하거나 새로 쓴다
4) 다음 홉에 맞는 연결 제어 정보만 다시 만든다
한계와 주의점
HTTP/1.1에서는 `Connection`이 connection-specific 필드를 선언하는 중심입니다. 하지만 HTTP/2와 HTTP/3에서는 그 메커니즘 자체가 사라졌습니다. 따라서 “예전부터 프록시에서 쓰던 헤더니까 그냥 넣자”는 태도는 매우 위험합니다.
또한 connection-specific 필드를 제거했다고 해서 모든 프록시 문제가 해결되는 것도 아닙니다. Hop-by-hop 헤더는 연결 전용 메타데이터를 다루는 규칙일 뿐이고, 캐시, 인증, 압축, 보안 헤더 같은 다른 end-to-end 의미까지 대신 설명해 주지는 않습니다.
- HTTP/1.1 규칙을 HTTP/2·HTTP/3에 그대로 적용하지 않기
- `Connection`에 end-to-end 헤더 이름을 넣지 않기
- `Proxy-Connection` 같은 역사적 필드를 습관적으로 쓰지 않기
- `TE`와 `Trailer`를 기계적으로 같은 급의 필드로 보지 않기
자주 하는 실수
TE,Upgrade같은 필드를 보내면서Connection옵션을 빼먹음- 프록시가
Connection과 지정된 필드를 제거하지 않고 그대로 전달함 Cache-Control같은 end-to-end 필드를 hop-by-hop처럼 다룸- HTTP/2/3에서도
Connection,Keep-Alive,Transfer-Encoding를 그대로 사용함 Proxy-Connection을 아직도 정식 필드처럼 사용함Trailer와TE를 문맥 없이 같은 것으로 취급함
실무 루틴
- 먼저 HTTP/1.1인지, HTTP/2인지, HTTP/3인지 확인한다.
- 필드가 현재 연결 전용인지 end-to-end인지 분리한다.
- 연결 전용이라면
Connection과의 관계를 점검한다. - 프록시나 게이트웨이가 포워딩 전에 제거/교체하는지 확인한다.
TE,Upgrade,Transfer-Encoding는 특히 주의해서 본다.- 역사적 필드(
Proxy-Connection)는 가능한 한 새 설계에서 피한다.
디버깅
Connection 헤더가 있는지, 있다면 어떤 옵션을 선언하는지 본다.Connection과 지정된 필드가 제대로 제거되는지 추적한다.TE라면 값이 `trailers` 외 다른 값을 갖는지와 Connection: TE 동반 여부를 본다.체크리스트
- HTTP 버전은 무엇인가?
- Connection 헤더가 있는가?
- 거기 선언된 이름과 같은 필드를 제거했는가?
- end-to-end 필드를 Connection 옵션으로 잘못 올리지는 않았는가?
- HTTP/2 / HTTP/3에서 금지 필드를 그대로 보내고 있지는 않은가?
요약
- ✅ hop-by-hop 헤더는 현재 연결의 즉시 수신자에게만 의미가 있다.
- ✅ end-to-end 헤더는 체인의 모든 수신자에게 의미가 있다.
- ✅ HTTP/1.1에서는 Connection 헤더가 hop-by-hop 여부를 선언하는 중심이다.
- ✅ 중간자는 Connection에 적힌 필드들과 Connection 자체를 제거하거나 교체해야 한다.
- ✅ 대표적인 connection-specific 필드 예로 Proxy-Connection, Keep-Alive, TE, Transfer-Encoding, Upgrade가 자주 언급된다.
- ✅ Cache-Control 같은 end-to-end 필드는 Connection 옵션으로 쓰면 안 된다.
- ✅ HTTP/2와 HTTP/3에서는 connection-specific 필드가 금지되며 TE만 trailers 값으로 예외 허용된다.
- ✅ 실무에서는 프록시 체인과 프로토콜 버전 경계를 같이 봐야 hop-by-hop 문제를 정확히 잡을 수 있다.