Nginx에 HTTP/3(QUIC) 적용하고 성능 비교하기
HTTP/3는 TCP 대신 UDP 위에서 동작하는 QUIC 프로토콜을 사용합니다. 첫 핸드셰이크가 1-RTT(재방문은 0-RTT)로 끝나고, 한 연결 안의 여러 스트림이 서로 블록되지 않는 구조이기 때문에 패킷 손실이 잦은 모바일 회선이나 장거리 국제 회선에서 체감 성능 개선이 특히 큽니다. Nginx 1.25부터 QUIC가 메인라인에 포함되었고, 1.27 이상에서는 대부분의 배포판 패키지로도 바로 쓸 수 있게 되면서 도입 문턱이 크게 낮아졌습니다.
1. 전제와 버전 확인
- Nginx 1.25 이상 (1.27+ 권장)
- OpenSSL 3.x 또는 QUIC를 지원하는 TLS 라이브러리(BoringSSL/quictls)
- UDP/443 포트 인바운드 허용
- TLS 1.3 서버 인증서
nginx -V 2>&1 | tr ' ' '\n' | grep -E 'http_v3|quic|ssl'
# 출력 예: --with-http_v3_module --with-http_ssl_module
Rocky 9·Ubuntu 24.04의 기본 패키지는 1.20대라 모듈이 빠져 있는 경우가 많습니다. 공식 저장소 사용을 권장합니다.
공식 저장소로 1.27 설치 (Rocky 9)
cat <<'EOF' | sudo tee /etc/yum.repos.d/nginx.repo
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/9/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
EOF
sudo dnf install -y nginx
2. 최소 동작 설정
server {
# HTTP/2 (TCP 443)
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
# HTTP/3 (UDP 443)
listen 443 quic reuseport;
listen [::]:443 quic reuseport;
http3 on;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.3;
ssl_ciphers TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;
ssl_prefer_server_ciphers off;
# 브라우저가 HTTP/3로 업그레이드하도록 광고
add_header Alt-Svc 'h3=":443"; ma=86400' always;
# (선택) 0-RTT 허용
ssl_early_data on;
proxy_set_header Early-Data $ssl_early_data;
root /var/www/example.com;
index index.html;
}
reuseport: 여러 워커가 동일 UDP 포트를 커널 레벨에서 로드밸런싱Alt-Svc: 최초엔 HTTP/2로 응답하고, 헤더를 본 브라우저가 다음 요청부터 HTTP/3로 업그레이드ssl_early_data on: 0-RTT 허용. Replay 공격 위험이 있는 POST/PUT은 애플리케이션에서 별도 방어 필요
3. 방화벽
인바운드 UDP 443을 열어야 합니다. 이 부분을 놓쳐서 “curl –http3으로 안 되네” 하는 경우가 실제로 가장 흔합니다.
# firewalld (Rocky/Alma)
firewall-cmd --permanent --add-port=443/udp
firewall-cmd --reload
# ufw (Ubuntu)
ufw allow 443/udp
클라우드 환경에서는 VPC 시큐리티 그룹/네트워크 ACL에도 같이 추가해야 합니다.
4. 확인
curl
# HTTP/3로 강제
curl -v --http3 https://example.com/ 2>&1 | grep -i 'alpn\|HTTP/3'
# 응답 헤더
curl -sI --http3 https://example.com/ | head
브라우저
크롬 DevTools의 Network 탭 → Protocol 컬럼을 켜면 h3 표시가 나옵니다. 최초 방문은 보통 h2이고, 두 번째 요청부터 h3으로 바뀝니다.
5. 성능 비교
간단한 정적 파일에 대해 서울↔도쿄 회선에서 curl -w 타이밍을 20회 평균한 예시입니다. 실 결과는 네트워크 상태에 따라 크게 달라집니다.
| 구간 | HTTP/2 | HTTP/3 |
|---|---|---|
| DNS lookup | 12 ms | 12 ms |
| TCP connect | 26 ms | — |
| TLS handshake | 38 ms | 24 ms (0-RTT 시 ~0 ms) |
| TTFB | 102 ms | 78 ms |
| 총 전송(1MB) | 220 ms | 185 ms |
주목할 점은 평균보다 “꼬리 지연(tail latency)”입니다. 3G/저속 Wi-Fi처럼 패킷 손실이 있는 환경에서 HTTP/2는 헤드 오브 라인 블로킹으로 P95가 급격히 나빠지는 반면, HTTP/3는 스트림이 독립적이라 완만합니다.
간단한 벤치 스크립트
#!/usr/bin/env bash
url="$1"
for proto in http2 http3; do
echo "== $proto =="
for i in $(seq 1 20); do
curl -s -o /dev/null --"$proto" \
-w "%{time_connect} %{time_appconnect} %{time_starttransfer} %{time_total}\n" \
"$url"
done
done
6. 운영 체크리스트
- MTU 문제: UDP는 단편화에 취약합니다. 일부 국제 회선에서 1500 바이트 전후 패킷이 드롭되면 HTTP/3만 느려지는 현상이 생깁니다.
quic_gso on;(Nginx 1.25.1+) 설정과 함께 경로 MTU를 확인합니다. -
로그: 기본 access log 포맷에
$http3변수가 없으므로 다음처럼 확장합니다.log_format main_h3 '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' 'proto=$server_protocol h3=$http3 rt=$request_time'; access_log /var/log/nginx/access.log main_h3; - Alt-Svc 캐시: 서비스를 내렸다가 다시 올릴 때 이전 공지된 포트·ALPN이 계속 시도될 수 있습니다. 포트를 바꾸려면
Alt-Svc: clear를 일정 기간 보낸 뒤 교체합니다. - 로드밸런서 앞단: 클라우드 L4 로드밸런서가 UDP를 지원해야 합니다. AWS NLB는 UDP 리스너, GCP Global LB는
HTTPS(HTTP/3 on)옵션, CloudFront는 기본 지원. - 프록시 뒤 Nginx: CloudFront/Cloudflare 뒤에서만 HTTP/3를 제공한다면 오리진 Nginx는 HTTP/2만 서비스해도 사용자 경험은 동일합니다.
HTTP/3는 “켜기만 하면 빨라지는 스위치”가 아니라, 모바일·장거리 사용자 비중이 높을수록 효과가 커지는 프로토콜입니다. 사용자 지역 분포와 실 측정치를 보고 전환 여부를 판단하시되, 정적 리소스 배포 사이트·SPA·스트리밍 서비스라면 거의 언제나 도입 이득이 큽니다.
댓글남기기