6장. HTTP/1.1·HTTP/2·HTTP/3는 무엇이 다를까

HTTP/1.1과 HTTP/2와 HTTP/3는 무엇이 다른가

HTTP/1.1은 요청마다 연결을 새로 맺나요?

아닙니다. HTTP/1.1은 지속 연결을 기본으로 써서 메시지 경계를 알 수 있으면 다음 요청과 응답에도 연결을 재사용합니다. 없는 것은 한 연결 안에 여러 독립 스트림을 식별해 프레임을 교차 전송하는 다중화 계층입니다.

하나의 웹페이지를 여는 데는 보통 문서 하나만 필요하지 않습니다. HTML을 받은 브라우저는 CSS와 JavaScript, 글꼴, 아이콘, 상품 이미지, API 데이터 같은 여러 자원을 추가로 요청합니다. 요청의 의미는 5장에서 본 것과 같습니다. 브라우저는 메서드와 대상을 제시하고 서버는 상태 코드와 표현으로 답합니다. 달라지는 것은 여러 메시지를 연결 위에서 구분하고 운반하는 방식입니다.

HTTP 버전의 진화를 “1은 느리고 3은 가장 빠르다”로 외우면 실제 현상을 설명하기 어렵습니다. 자원 수와 크기, 네트워크 손실, UDP 허용 여부, 서버 처리와 캐시 상태가 모두 결과에 영향을 줍니다.

각 버전이 같은 HTTP 의미를 어떻게 표현하고, 여러 요청을 어떻게 진행하며, 손실 지연이 어디까지 퍼지는지를 비교하겠습니다.

같은 의미를 다른 선상 형식으로 옮깁니다

HTTP 의미와 선상 표현(wire representation)은 분리해서 봐야 합니다. 다음 요청은 어느 버전에서든 “https://shop.example.com/products/42의 표현을 GET한다”는 같은 의도를 가집니다. 그러나 실제 연결 위 표현은 다릅니다.

HTTP/1.1에서는 사람이 읽을 수 있는 요청 줄과 헤더 필드 줄을 사용합니다.

GET /products/42 HTTP/1.1
Host: shop.example.com
Accept: text/html

HTTP/2와 HTTP/3에서는 이 제어 정보를 의사 헤더에 담습니다. 다음은 이해를 위한 논리적 표시이며 실제 바이트를 그대로 인쇄한 것이 아닙니다.

:method = GET
:scheme = https
:authority = shop.example.com
:path = /products/42
accept = text/html

응답의 200도 HTTP/1.1에서는 HTTP/1.1 200 OK라는 상태 줄에 나타나지만 HTTP/2·3에서는 :status = 200으로 표현됩니다. HTTP/2·3 요청 하나하나에 HTTP/2나 HTTP/3라는 텍스트 버전 문자열이 붙는 것도 아닙니다. 연결을 설정하고 프로토콜을 협상한 뒤 그 연결의 규칙에 따라 프레임을 해석합니다.

세 버전의 큰 차이를 먼저 정리하면 다음과 같습니다.

구분 HTTP/1.1 HTTP/2 HTTP/3
주된 선상 형식 줄 단위 텍스트 메시지 바이너리 프레임 QUIC 스트림 위 바이너리 프레임
기반 전송 주로 TCP TCP QUIC(UDP 위에서 동작)
여러 요청 진행 지속 연결·선택적 파이프라이닝, 여러 TCP 연결 활용 가능 한 연결의 여러 스트림을 다중화 한 QUIC 연결의 여러 스트림을 다중화
필드 압축 전용 압축 형식 없음 HPACK QPACK
손실 영향의 핵심 같은 연결의 순서와 응답 대응 제약 TCP 손실이 여러 활성 스트림을 함께 멈출 수 있음 손실된 스트림의 순서 복구가 다른 스트림 전달을 직접 막지 않음
보안 연결 HTTPS라면 TLS 사용 웹에서는 일반적으로 TLS와 함께 사용 QUIC에 TLS 1.3 통합

표의 마지막 두 줄을 과장해서 읽으면 안 됩니다. HTTP/3도 손실 복구와 혼잡 제어를 하며, 같은 연결의 스트림들은 네트워크 경로와 연결 수준 자원을 공유합니다. 반대로 HTTP/1.1도 연결을 매 요청마다 반드시 새로 만드는 규칙이 아닙니다. 각 버전이 해결한 문제와 남은 문제를 층별로 살펴보겠습니다.

HTTP/1.1은 한 연결을 계속 사용할 수 있습니다

HTTP/1.1 메시지는 시작 줄, 헤더 필드 줄, 빈 줄, 선택적 메시지 본문으로 구성됩니다. 텍스트 기반이라 패킷 캡처나 원시 로그에서 구조를 알아보기 쉽지만, 줄 경계와 메시지 길이를 정확히 해석해야 합니다. 서로 다른 중간 구성요소가 애매한 길이나 공백을 다르게 파싱하면 보안 문제가 생길 수 있으므로 ‘사람이 읽기 쉽다’와 ‘파싱이 단순하다’는 같은 말이 아닙니다.

HTTP/1.1은 기본적으로 지속 연결(persistent connection)을 사용합니다. 한 요청과 응답이 끝났다고 TCP 연결을 곧바로 닫지 않고, 메시지 경계를 알 수 있다면 다음 요청과 응답에도 재사용할 수 있습니다. 따라서 “HTTP/1.1은 요청마다 TCP와 TLS를 새로 연결한다”는 설명은 틀립니다. 서버나 클라이언트가 Connection: close를 알리거나 시간 초과와 오류가 생기면 연결은 닫힐 수 있습니다.

한 지속 연결에서 응답을 기다리지 않고 여러 요청을 먼저 보내는 파이프라이닝(pipelining)도 규격에 존재합니다. 서버는 안전한 메서드의 요청들을 병렬로 처리할 수 있지만 응답은 요청을 받은 순서대로 보내야 합니다. 앞선 응답 생성이나 큰 콘텐츠 전송이 늦어지면 뒤에서 준비된 응답도 순서를 기다릴 수 있습니다. 이것이 HTTP 애플리케이션 계층의 HOL 블로킹입니다.

실제 클라이언트는 한 서버에 여러 TCP 연결을 열어 동시에 요청하는 방식으로 이 제약을 피해 왔습니다. 각 연결에서 별도의 요청을 진행하면 하나의 느린 응답이 다른 연결의 응답까지 직접 막지는 않습니다. 하지만 연결 수가 늘면 핸드셰이크와 서버 자원을 더 사용하고, 각 TCP 연결이 별도의 혼잡 제어 상태를 가집니다. 규격은 모든 환경에 적용할 고정 연결 개수를 정하지 않고 클라이언트가 보수적으로 관리하도록 합니다.

HTTP/1.1의 특징을 “무조건 한 번에 하나”라고 줄이면 지속 연결·파이프라이닝·여러 연결이라는 현실을 놓칩니다. 더 정확한 결론은 한 연결 안에 여러 독립 스트림을 식별해 프레임을 교차 전송하는 다중화 계층이 없다는 것입니다.

HTTP/2는 메시지를 프레임과 스트림으로 나눕니다

HTTP/2는 HTTP의 메서드·상태 코드·헤더 필드 의미를 바꾸지 않고, 연결에 매핑하는 방식을 재설계했습니다. 기본 프로토콜 단위는 프레임(frame)입니다. HEADERS 프레임은 압축된 필드 영역을, DATA 프레임은 콘텐츠의 일부를 전달합니다. SETTINGS, WINDOW_UPDATE 같은 프레임은 연결 설정과 흐름 제어를 지원합니다.

각 요청과 응답의 교환은 하나의 스트림(stream)에 연결됩니다. 프레임에는 어느 스트림에 속하는지 식별하는 정보가 있으므로 같은 TCP 연결에서 여러 스트림의 프레임을 번갈아 보낼 수 있습니다. 예를 들어 큰 이미지의 DATA 프레임 사이에 CSS 응답과 API 응답의 프레임을 끼워 보낼 수 있습니다. 이를 다중화(multiplexing)라고 합니다.

다중화는 모든 서버 작업이 물리적으로 동시에 실행된다는 보장이 아닙니다. 서버의 작업 큐, CPU, 데이터베이스 연결, HTTP/2 흐름 제어, 전송 우선순위에 따라 어떤 스트림은 기다릴 수 있습니다. 핵심은 프로토콜이 한 응답을 끝까지 보내야만 다음 응답 바이트를 보낼 수 있는 구조에서 벗어나 스트림별 프레임을 구분할 수 있게 했다는 점입니다.

의사 헤더는 요청 줄을 대체합니다

HTTP/2는 :로 시작하는 의사 헤더를 통해 메시지 제어 데이터를 전달합니다. 요청에는 일반적으로 :method, :scheme, :authority, :path가 사용되고 응답에는 :status가 사용됩니다. 의사 헤더는 일반 헤더 필드가 아니며 정해진 문맥과 순서를 지켜야 합니다.

개발자 도구에서 :authority가 보이면 HTTP/1.1의 Host와 비슷한 목적지 정보로 이해할 수 있습니다. 그러나 둘을 선상 형식까지 같은 필드라고 취급해서는 안 됩니다. 프록시나 게이트웨이는 한 버전의 제어 데이터를 다른 버전에 맞게 변환할 수 있습니다. 이 변환 과정에서 대상 정보가 서로 다르게 해석되지 않도록 일관성이 중요합니다.

HPACK은 반복되는 필드를 줄입니다

웹 요청에는 사용자 에이전트 정보와 수용 형식, 쿠키 같은 필드가 반복됩니다. HTTP/2의 HPACK은 정적·동적 테이블과 문자열 압축을 이용해 반복 전송을 줄입니다.

HPACK은 HTML이나 이미지 콘텐츠를 압축하는 gzip, br과 역할이 다릅니다. HPACK은 HTTP 필드 영역을 위한 형식이고, Content-Encoding은 표현 데이터에 적용된 인코딩을 나타냅니다. 또한 ‘바이너리’나 ‘압축’이 곧 암호화를 뜻하지 않습니다. 전송 기밀성과 무결성은 TLS가 담당합니다.

TCP의 순서 보장은 그대로 남습니다

HTTP/2 스트림은 애플리케이션 계층에서 대체로 독립적으로 진행됩니다. 한 스트림의 서버 처리가 늦어져도 다른 스트림의 준비된 프레임을 보낼 수 있습니다. 이로써 HTTP/1.1의 응답 순서 때문에 생기는 애플리케이션 계층 HOL 블로킹을 크게 줄입니다.

하지만 HTTP/2는 하나의 TCP 연결 위에서 동작합니다. TCP는 연결 전체를 하나의 순서 있는 바이트 흐름으로 애플리케이션에 전달합니다. 중간의 TCP 세그먼트가 손실되면 그 뒤에 도착한 바이트에 다른 HTTP/2 스트림의 프레임이 포함되어 있어도 빈 구간을 복구할 때까지 애플리케이션에 순서대로 전달할 수 없습니다. HTTP/2가 TCP의 HOL 블로킹까지 없앴다고 말할 수 없는 이유입니다.

이 현상은 HTTP/2가 HTTP/1.1보다 늘 느리다는 뜻도 아닙니다. 여러 연결을 줄이고 필드를 압축하며 스트림을 다중화하는 이점이 있습니다. 다만 손실이 있는 경로에서는 하나의 TCP 연결에 모인 활성 스트림들이 함께 멈추는 구간이 생길 수 있다는 구조적 경계입니다.

HTTP/3는 HTTP를 QUIC 스트림에 매핑합니다

HTTP/3는 같은 HTTP 의미를 QUIC 전송 위에 매핑합니다. QUIC은 UDP 데이터그램을 기반으로 동작하지만 UDP의 단순한 전달 기능만 사용하는 애플리케이션이 아닙니다. QUIC 자체가 연결 설정, 암호화, 확인 응답, 손실 복구, 흐름 제어, 혼잡 제어, 여러 스트림을 제공합니다. “UDP는 확인을 하지 않으니 HTTP/3도 손실된 데이터를 무시한다”는 설명은 틀립니다.

QUIC에는 TLS 1.3이 통합되어 있습니다. 전송 매개변수 협상과 TLS 키 합의를 함께 진행하도록 설계됐으며, 이전 상태가 있으면 0-RTT 초기 데이터를 사용할 수도 있습니다. 다만 4장에서 본 재생 위험과 수용 조건은 그대로 남습니다.

HTTP/3는 HTTP/2와 비슷한 바이너리 프레이밍 개념을 사용하지만, 스트림과 흐름 제어 같은 책임의 상당 부분을 QUIC에 맡깁니다. 일반적인 요청과 응답은 하나의 양방향 QUIC 스트림을 사용합니다. 연결 설정과 QPACK 상태 전달을 위한 단방향 제어 스트림도 있습니다.

손실 영향은 스트림 경계에서 줄어듭니다

QUIC은 스트림마다 신뢰할 수 있는 순서 있는 바이트 전달을 제공합니다. 한 스트림의 데이터가 손실되면 그 스트림은 빈 구간의 복구를 기다립니다. 다른 스트림의 데이터가 정상적으로 도착했다면 TCP 연결 전체의 바이트 순서를 기다리지 않고 해당 스트림에서 애플리케이션으로 전달할 수 있습니다. HTTP/3가 줄이는 것은 손실 지연이 관련 없는 스트림까지 전파되는 범위입니다.

그렇다고 한 스트림 손실이 다른 스트림에 아무 영향도 주지 않는 것은 아닙니다. QUIC 연결의 스트림들은 같은 네트워크 경로와 연결 수준 혼잡 제어를 공유합니다. 손실이 혼잡 신호로 해석되면 연결 전체가 보낼 수 있는 양이 줄 수 있습니다. 연결 수준 흐름 제어 한도나 서버 자원 부족도 여러 스트림에 영향을 줄 수 있습니다. HTTP/3는 지연을 제거하지 않고 전송 순서 의존성을 더 세밀한 경계로 나눕니다.

QPACK은 QUIC의 스트림 특성에 맞춘 압축입니다

HTTP/3는 HPACK이 아니라 QPACK을 사용합니다. HPACK은 압축된 필드 영역이 정해진 순서로 전달된다는 HTTP/2의 조건을 활용합니다. QUIC에서는 서로 다른 스트림의 데이터가 독립적으로 전달되므로 한 스트림의 필드가 참조하는 동적 테이블 항목이 아직 다른 제어 스트림에서 도착하지 않았을 수 있습니다. HPACK을 그대로 QUIC에 얹으면 이때 뒤따르는 필드 영역의 디코딩이 함께 멈춥니다. QPACK은 헤더 디코딩 단계에서 생기는 이 HOL 블로킹을 피하려고 설계된 형식입니다.

QPACK은 동적 테이블 갱신을 별도 단방향 스트림으로 보내고 각 필드 영역이 필요한 상태를 참조하게 합니다. 아직 도착하지 않은 항목을 참조하면 해당 스트림이 기다릴 수 있습니다. 디코더는 차단 가능한 스트림 수를 알리고 인코더는 그 한도를 지켜야 합니다. QPACK도 모든 의존성을 없애지는 않습니다.

QPACK은 QUIC 패킷 전체를 압축하는 기술도 아니고 콘텐츠 압축을 대신하지도 않습니다. HTTP/3의 헤더와 트레일러 필드 영역을 효율적으로 표현하는 형식입니다.

브라우저는 HTTP/3 종단점을 어떻게 알까요

브라우저가 항상 첫 방문에는 HTTP/2로 접속하고 다음 방문부터만 HTTP/3를 쓴다고 단정할 수는 없습니다. 클라이언트가 HTTP/3 종단점을 이미 알고 있거나 다른 발견 정보를 얻었다면 대상 주소와 UDP 포트로 QUIC 연결을 직접 시도할 수 있습니다. 반대로 HTTP/3를 지원하는 서버라도 클라이언트가 그 사실을 모르거나 네트워크에서 UDP가 차단되면 TCP 기반 HTTP를 사용할 수 있습니다.

대표적인 발견 방법이 대체 서비스(Alternative Services)입니다. 서버는 기존 HTTP 응답의 Alt-Svc 헤더 필드로 같은 오리진을 제공하는 HTTP/3 종단점을 알릴 수 있습니다.

Alt-Svc: h3=":443"; ma=86400

이 예시는 같은 호스트의 UDP 443번 포트에서 h3를 제공하며 광고를 일정 시간 기억할 수 있다는 뜻입니다. ma는 최대 유효 시간이지 그 연결만 쓰라는 명령은 아닙니다.

DNS의 HTTPS 리소스 레코드도 지원하는 애플리케이션 프로토콜과 대체 종단점 정보를 제공해 HTTP/3로 직접 연결을 시도하도록 도울 수 있습니다. 2장에서 DNS가 주소만 반환하는 전화번호부가 아니라고 설명한 이유가 여기서 다시 드러납니다. 다만 모든 클라이언트와 리졸버 환경이 같은 발견 방식을 지원한다고 가정하면 안 됩니다.

QUIC 연결이 UDP 차단이나 경로 문제로 성립하지 않으면 클라이언트는 TCP 기반 HTTP 버전을 시도할 수 있습니다. 이 폴백(fallback)은 HTTP/3가 실패하면 웹사이트 전체가 반드시 열리지 않는 구조를 피합니다. 대신 실패를 확인하고 대체 경로를 시도하는 시간이 사용자 지연으로 나타날 수 있습니다. 조직망이나 보안 장비에서 HTTP/3만 불안정하다면 서버 애플리케이션보다 UDP 경로와 폴백 동작을 함께 확인해야 합니다.

버전 번호가 올라가면 무엇이 실제로 빨라질까요

프로토콜 버전은 성능을 만드는 여러 조건 중 하나입니다. 다음 상황에서는 기대한 이점이 작거나 결과가 뒤집힐 수 있습니다.

  • 자원이 몇 개 없고 서버 처리 시간이 대부분이라면 다중화 차이가 작습니다.
  • UDP가 차단되거나 품질이 나쁘면 QUIC 시도와 TCP 폴백이 추가 지연을 만들 수 있습니다.
  • 매우 큰 단일 파일 하나만 받는다면 스트림 간 HOL 감소의 이점이 제한적입니다.
  • 서버의 QUIC 구현과 CPU·혼잡 제어가 충분하지 않다면 HTTP/3라는 이름만으로 개선되지 않습니다.
  • 중요한 CSS나 스크립트가 늦게 발견되거나 서버 응답 자체가 느리다면 프로토콜을 바꿔도 첫 화면이 늦을 수 있습니다.

반대로 지연이 크고 손실이 있는 경로에서 한 오리진의 여러 자원을 동시에 받는다면 연결 설정과 스트림 독립성이 의미 있는 차이를 만들 수 있습니다. 어느 조건에서 얼마나 개선되는지는 실제 서비스의 사용자 지역·기기·네트워크를 나누어 측정해야 합니다. 출처와 측정 조건이 없는 “몇 퍼센트 빨라진다”는 숫자를 일반화하지 않는 이유입니다.

Network 패널에서 버전과 병목을 함께 봅니다

Network 패널의 열 설정에서 Protocol 항목을 표시하면 요청이 http/1.1, h2, h3 중 어떤 프로토콜로 처리됐는지 드러납니다. 표기는 브라우저마다 조금 다를 수 있습니다. 같은 페이지 안에서도 오리진과 서버 지원, 연결 상태에 따라 서로 다른 버전이 섞일 수 있습니다.

버전 표시만 보고 성능 결론을 내리지 말고 다음 증거를 함께 봅니다.

1. 같은 오리진의 연결 수와 요청이 겹치는 구간을 봅니다.

2. 연결·TLS·QUIC 협상과 서버 응답 대기를 나눕니다.

3. 큰 자원 하나가 병목인지 작은 자원들의 대기인지 확인합니다.

4. 새로 고침과 반복 방문의 캐시 조건을 기록합니다.

5. 한 번의 결과보다 반복 측정과 실제 사용자 자료를 비교합니다.

HTTP/1.1 제약을 피하려고 쓰던 최적화가 HTTP/2·3에서는 불리할 수도 있습니다. 이미지를 한 스프라이트로 합치면 독립 캐시와 갱신이 어렵고, 호스트를 지나치게 나누면 DNS·연결·TLS 비용이 생깁니다. 파일을 무조건 잘게 쪼개는 것도 정답은 아닙니다. 압축 효율과 캐시 무효화 범위를 함께 측정해야 합니다.

HTTP 버전의 진화가 남긴 핵심은 “더 빠른 번호”가 아닙니다. HTTP/1.1은 지속 연결과 텍스트 메시지를 사용하고, HTTP/2는 TCP 연결 안에 프레임과 스트림 다중화를 추가했으며, HTTP/3는 QUIC의 스트림으로 손실 복구의 전달 경계를 나눴습니다. 세 버전은 같은 HTTP 의미를 공유하지만 지연이 퍼지는 구조가 다릅니다.

서버에서 HTML 응답이 도착하면 네트워크 릴레이는 브라우저 내부 처리로 넘어갑니다. 다음 장에서는 브라우저가 수신되는 HTML 바이트를 토큰으로 읽고 DOM을 구성하며, CSS와 JavaScript 자원을 발견하는 과정을 살펴보겠습니다.

도해 6. HTTP/1.1·2·3의 전송 구조

도해 6. HTTP/1.1·2·3의 전송 구조

도해 6. HTTP/1.1·2·3의 전송 구조

HTTP/1.1은 줄 단위 메시지, HTTP/2는 TCP 위의 다중화 스트림과 HPACK, HTTP/3는 QUIC 스트림과 QPACK으로 같은 HTTP 의미를 운반합니다. 버전이 올라갈수록 지연이 사라지는 것이 아니라 손실과 순서 의존성이 퍼지는 경계가 달라집니다.

이 장의 근거 문서

  • RFC 9112 — HTTP/1.1의 지속 연결과 메시지 구문
  • RFC 9113 — HTTP/2의 프레임·스트림과 HPACK
  • RFC 9114 — HTTP 의미를 QUIC 스트림에 매핑하는 방식
  • RFC 9204 — QPACK의 동적 테이블과 차단 회피
  • RFC 7838 — 대체 서비스로 HTTP/3 종단점을 알리는 방법

이 장에서 함께 다루는 질문

HTTP 의미와 선상 표현은 어떻게 다른가요?

같은 의도라도 연결 위에서의 표현이 다릅니다. HTTP/1.1 은 사람이 읽을 수 있는 요청 줄과 헤더 필드 줄을 쓰고, HTTP/2 와 3 은 그 제어 정보를 의사 헤더에 담습니다. 상태 코드도 상태 줄이 아니라 의사 헤더로 표현됩니다.

사람이 읽기 쉬우면 파싱도 단순한가요?

같은 말이 아닙니다. 텍스트 기반이라 패킷 캡처나 원시 로그에서 구조를 알아보기는 쉽지만 줄 경계와 메시지 길이를 정확히 해석해야 합니다. 중간 구성요소들이 애매한 길이나 공백을 다르게 파싱하면 보안 문제가 생길 수 있습니다.

HTTP/1.1 의 파이프라이닝은 왜 한계가 있나요?

응답을 요청받은 순서대로 보내야 하기 때문입니다. 앞선 응답 생성이나 큰 콘텐츠 전송이 늦어지면 뒤에서 준비된 응답도 순서를 기다립니다. 실제 클라이언트는 한 서버에 여러 연결을 열어 이 제약을 피해 왔습니다.

HTTP/3 는 손실의 영향을 받지 않나요?

과장해서 읽으면 안 됩니다. HTTP/3 도 손실 복구와 혼잡 제어를 하고 같은 연결의 스트림들은 네트워크 경로와 연결 수준 자원을 공유합니다. 손실된 스트림의 순서 복구가 다른 스트림 전달을 직접 막지 않는다는 것이 차이입니다.

EPUB으로 소장하기

전체 내용과 부록을 EPUB으로 읽고 소장할 수 있습니다.

9,900원

이 장이 실린 책

웹은 어떻게 동작하는가

이 책의 목차 보기

같은 분야의 다른 책

이 글 공유하기

X 스레드 네이버 카카오톡

최종 수정일 2026년 9월 3일

이 콘텐츠는 저자가 기획하고 AI 도구를 집필·정리 과정에 활용했습니다. 구성, 사실 확인, 표현, 최종 판단과 출판 책임은 저자와 보이드로그에 있습니다.