OneWebDesk

gzip·brotli 압축 적용: 전송량 70% 줄이기

텍스트 리소스 압축이 주는 효과와 nginx·Apache 설정법, 적용 확인과 흔한 실수.

HTTP 압축은 서버 설정 몇 줄로 텍스트 전송량을 70~80% 줄이는, 웹 성능 튜닝에서 가장 확실한 한 수입니다. 238KB짜리 HTML이 gzip을 거치면 30KB 안팎으로 줄어들고, 그만큼 첫 화면이 빨리 뜨고 모바일 데이터도 절약됩니다. CDN을 쓰면 대부분 자동으로 켜지지만, 직접 운영하는 nginx·Apache 서버에서는 설정이 빠져 있거나 절반만 적용된 경우가 의외로 많습니다.

이 가이드는 압축이 적용되는 대상과 브라우저–서버 간 협상 원리부터, 바로 붙여 넣을 수 있는 nginx·Apache 설정 블록, brotli 사전압축, 그리고 이미지 이중 압축·Vary 누락 같은 흔한 실수까지 정리합니다. 내 사이트가 지금 압축을 쓰고 있는지는 압축 적용 확인 도구로 URL만 넣으면 바로 알 수 있습니다.

무엇을 압축해야 하나 — 텍스트만, 이미지는 절대 금지

gzip·brotli 같은 범용 압축은 반복 패턴이 많은 텍스트에서 효과가 큽니다. HTML 태그, CSS 속성명, JS 식별자, JSON 키는 같은 문자열이 수없이 반복되므로 70~80%씩 줄어듭니다. 반면 JPG·PNG·WebP· MP4·WOFF2는 이미 포맷 자체가 압축이라 다시 압축해도 1~2% 줄거나 오히려 커지고, CPU만 낭비합니다.

리소스압축?이유·기대 절감
HTML, CSS, JS반복 패턴 多 — 70~80% 절감
JSON, XML, SVGAPI 응답·아이콘 — 60~85% 절감
.txt, RSS, 소스맵텍스트 계열은 전부 대상
JPG, PNG, WebP, AVIF아니오이미 압축된 포맷 — 절감 거의 0, CPU 낭비
MP4, WOFF2, ZIP, PDF(대부분)아니오내부 압축 포함 — 이중 압축 무의미

내 페이지에서 텍스트 리소스가 전체 용량의 몇 %를 차지하는지는 페이지 무게 측정 도구로 리소스 유형별 분포를 보면 압축의 기대 효과를 미리 가늠할 수 있습니다.

협상 원리 — Accept-Encoding과 Content-Encoding

압축은 브라우저와 서버가 자동으로 협상합니다. 순서는 단순합니다.

  1. 브라우저가 요청에 Accept-Encoding: gzip, deflate, br, zstd헤더를 실어 “나는 이런 형식을 풀 수 있다”고 알립니다. 요즘 브라우저는 전부 gzip과 brotli(br)를 지원합니다.
  2. 서버는 그중 자기가 지원하는 형식으로 본문을 압축하고, 응답에 Content-Encoding: gzip(또는 br)을 붙입니다.
  3. 브라우저가 헤더를 보고 자동으로 압축을 풀어 렌더링합니다. 개발자는 클라이언트 쪽에서 할 일이 없습니다.

여기서 중요한 헤더가 하나 더 있습니다. 같은 URL이 클라이언트에 따라 압축본·비압축본으로 다르게 나가므로, 응답에 Vary: Accept-Encoding을 붙여 중간 캐시(CDN·프록시)가 두 버전을 구분해 저장하게 해야 합니다. 이게 빠지면 gzip을 못 푸는 클라이언트에 압축본이 캐시로 전달되는 사고가 납니다.

nginx 설정 — 실전 예: 238KB HTML → 30KB

nginx는 gzip 모듈이 기본 내장입니다. http 블록에 아래를 넣으면 끝입니다.

# /etc/nginx/nginx.conf — http { } 안에
gzip on;
gzip_comp_level 5;           # 1~9. 5가 속도/압축률 균형점
gzip_min_length 256;         # 256바이트 미만은 압축 안 함
gzip_vary on;                # Vary: Accept-Encoding 자동 추가
gzip_proxied any;            # 프록시 뒤에서도 압축
gzip_types
  text/css text/plain text/xml
  application/javascript application/json
  application/xml application/rss+xml
  image/svg+xml font/ttf;    # text/html 은 기본 포함이라 쓰면 경고

실제로 이 설정을 적용한 서버에서 238,412바이트짜리 HTML 문서를 요청하면 Content-Encoding: gzip과 함께 약 30KB(87% 절감)로 내려옵니다. 곧바로 체감되는 차이는 다운로드 시간입니다. 4G(가정 5Mbps) 기준 전송 시간이 약 380ms에서 48ms로 줄어듭니다. 적용 전후 응답 속도 변화는 응답 속도 측정 도구로 비교해 보세요.

brotli는 같은 텍스트에서 gzip보다 15~20% 더 줄여 줍니다. nginx 기본 배포판에는 없어서 ngx_brotli 모듈을 추가로 빌드/설치해야 하며, 설정은 거의 대칭입니다.

# ngx_brotli 설치 후
brotli on;
brotli_comp_level 5;         # 동적 압축은 4~6 권장 (11은 정적 전용)
brotli_types text/css application/javascript application/json image/svg+xml;

모듈 빌드가 부담스럽다면 Cloudflare 같은 CDN을 앞단에 두는 것만으로 brotli가 자동 적용됩니다. 이 경우 원본 서버는 gzip만 켜 둬도 충분합니다.

Apache와 정적 사전압축(.br/.gz)

Apache는 mod_deflate로 처리합니다.

# .htaccess 또는 VirtualHost
<IfModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/html text/css text/plain
  AddOutputFilterByType DEFLATE application/javascript application/json
  AddOutputFilterByType DEFLATE application/xml image/svg+xml
</IfModule>

한 단계 더 나가면 빌드 시점 사전압축이 있습니다. 정적 자산(JS 번들, CSS)은 배포할 때 미리 app.js.br·app.js.gz를 만들어 두고, 서버는 요청이 오면 압축 없이 파일만 내려 줍니다. 두 가지 장점이 있습니다.

  • CPU 0: 요청마다 압축하지 않으므로 트래픽이 몰려도 서버 부하가 없습니다.
  • 최고 압축률: 실시간이 아니므로 brotli 레벨 11, gzip 레벨 9처럼 가장 높은 레벨을 쓸 수 있습니다. brotli 11은 동적 압축 대비 10% 이상 더 줄어듭니다.

nginx는 gzip_static on;(내장)과 brotli_static on;(ngx_brotli)으로 사전압축 파일을 자동으로 찾아 서빙합니다. Vite·webpack·Next.js 모두 빌드 단계에서 .br·.gz를 생성하는 플러그인이 있습니다.

적용 확인과 흔한 실수 5가지

설정 후 확인은 압축 적용 확인 도구에 URL을 넣거나, curl로 직접 헤더를 보내 보면 됩니다.

curl -sI -H "Accept-Encoding: gzip, br" https://example.com/ | grep -i \
  -e content-encoding -e vary
# content-encoding: br   ← 압축 적용 중
# vary: Accept-Encoding  ← 캐시 안전

주의할 함정들입니다.

  • 이미지·영상까지 gzip_types에 넣는 것: 절감은 없고 요청마다 CPU만 태웁니다. 텍스트 MIME 타입만 나열하세요.
  • 압축 레벨 과다: gzip 9는 5보다 CPU를 몇 배 쓰면서 압축률은 1~2%p밖에 안 좋아집니다. 동적 압축은 gzip 5, brotli 4~6이 정석이고, 최고 레벨은 정적 사전압축에서만 쓰세요.
  • Vary: Accept-Encoding 누락: 중간 캐시가 압축본과 비압축본을 섞어 내보내는 원인. nginx는 gzip_vary on; 한 줄로 해결됩니다.
  • 너무 작은 응답 압축: 수백 바이트짜리는 gzip 헤더 오버헤드 때문에 오히려 커질 수 있습니다. gzip_min_length 256처럼 하한을 두세요.
  • BREACH: 응답 본문에 CSRF 토큰 같은 비밀값과 공격자가 조작 가능한 입력이 함께 들어가는 동적 페이지는, 압축 크기 변화를 관찰해 비밀을 추출하는 BREACH 공격의 조건이 됩니다. 이런 응답은 압축을 끄거나, 토큰을 요청마다 마스킹하는 프레임워크 기능을 쓰세요. 정적 자산·공개 페이지는 해당 없습니다.

마지막으로 효과 측정입니다. 적용 전후를 페이지 무게 측정으로 전송량 변화를, 응답 속도 측정으로 체감 속도 변화를 기록해 두면 성능 개선을 숫자로 증명할 수 있습니다.

자주 묻는 질문

gzip과 brotli 중 무엇을 써야 하나요?
둘 다 켜는 것이 정답입니다. 브라우저가 Accept-Encoding으로 br을 보내면 brotli(gzip 대비 15~20% 추가 절감)를, 아니면 gzip을 주면 됩니다. nginx에 brotli 모듈을 넣기 어렵다면 gzip만으로도 70% 이상 절감되므로 gzip 먼저 켜고, brotli는 CDN에 맡기는 것도 좋은 선택입니다.
이미지(JPG, PNG, WebP)도 gzip으로 압축해야 하나요?
아니요. 이미지·영상·WOFF2 폰트는 포맷 자체가 이미 압축되어 있어 gzip을 다시 적용해도 1~2%밖에 줄지 않고 CPU만 낭비합니다. 이미지 용량은 압축 헤더가 아니라 포맷 변경(WebP/AVIF)과 리사이즈로 줄여야 합니다.
압축이 적용됐는지 어떻게 확인하나요?
응답 헤더에 Content-Encoding: gzip 또는 br이 있으면 적용된 것입니다. curl -sI -H "Accept-Encoding: gzip, br" URL 명령으로 확인하거나, 압축 적용 확인 도구에 URL을 넣으면 인코딩 종류와 절감률까지 한 번에 보여 줍니다. 요청에 Accept-Encoding 헤더가 없으면 서버는 압축하지 않으므로, 브라우저 개발자도구 Network 탭에서 볼 때도 이 점을 유의하세요.
gzip_comp_level은 몇으로 설정해야 하나요?
동적 압축은 5 전후가 균형점입니다. 9로 올려도 압축률은 1~2%p 개선에 그치는데 CPU 사용량은 크게 늘어납니다. 반대로 빌드 시점에 미리 압축해 두는 정적 사전압축(.gz/.br)은 요청 시 CPU를 쓰지 않으므로 gzip 9, brotli 11 같은 최고 레벨을 쓰는 것이 맞습니다.
압축이 보안에 문제가 된다는 말이 있던데 사실인가요?
조건부로 사실입니다. BREACH 공격은 CSRF 토큰 같은 비밀값과 공격자가 반사시킬 수 있는 입력이 같은 압축 응답에 섞일 때 성립합니다. 해당하는 동적 HTML만 압축을 끄거나 토큰 마스킹으로 방어하면 되고, 정적 CSS/JS나 비밀값 없는 공개 페이지는 안심하고 압축해도 됩니다.

이 가이드와 함께 쓰면 좋은 도구

관련 가이드