본문으로 건너뛰기
Advertisement

정적 파일 서빙 최적화

Nginx가 정적 파일을 빠르게 서빙하는 것은 웹 성능 최적화의 핵심입니다. expires, cache-control, sendfile, gzip 설정을 올바르게 구성하면 클라이언트의 로딩 속도를 크게 향상시킬 수 있습니다.


sendfile — 커널 레벨 파일 전송

일반적인 파일 전송은 파일 내용을 사용자 공간(user space)으로 읽고 다시 소켓으로 보내는 과정을 거칩니다. sendfile을 활성화하면 커널에서 직접 소켓으로 파일을 전송하여 메모리 복사를 줄이고 CPU 부하를 낮춥니다.

[일반 방식]
디스크 → 커널 버퍼 → 사용자 공간 → 소켓 버퍼 → 네트워크
(복사 2회 발생)

[sendfile]
디스크 → 커널 버퍼 → 소켓 버퍼 → 네트워크
(커널 내부에서만 처리, 복사 1회)
http {
sendfile on; # sendfile() 시스템콜 사용
tcp_nopush on; # sendfile과 함께: TCP 패킷을 최대 크기로 묶어 전송
tcp_nodelay on; # 마지막 패킷을 지연 없이 즉시 전송
}

tcp_nopushtcp_nodelay를 함께 활성화하면 대용량 파일은 최대 패킷 크기로 묶어 보내고, 마지막 패킷은 즉시 전송합니다.


캐시 제어 (expires, Cache-Control)

브라우저 캐시를 적절히 설정하면 재방문 사용자가 서버에 불필요한 요청을 보내지 않아 트래픽과 응답 시간이 모두 줄어듭니다.

expires 지시어

server {
root /var/www/html;

# 이미지 — 7일 캐시
location ~* \.(png|jpg|jpeg|gif|webp|svg|ico)$ {
expires 7d;
}

# 폰트 — 1년 캐시 (변경 거의 없음)
location ~* \.(woff|woff2|ttf|otf|eot)$ {
expires 1y;
}

# JS/CSS — 빌드 해시가 있으면 1년 (immutable)
location ~* \.(js|css)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}

# HTML — 캐시 안 함 (항상 최신 내용 확인)
location ~* \.html$ {
expires -1; # 또는 expires 0;
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
}

Cache-Control 헤더 값 설명

설명
publicCDN, 프록시 등 중간 캐시 허용
private브라우저 캐시만 허용, CDN 캐시 금지
no-cache캐시 저장은 하되 사용 전 서버에 재검증
no-store캐시 저장 금지 (민감한 데이터)
must-revalidate만료된 캐시는 반드시 서버에 재검증
immutable캐시 유효 기간 내 절대 변경 없음 (강력 캐시)
max-age=NN초 동안 캐시 유효

ETag와 조건부 요청

expires/Cache-Control과 함께 ETag를 활용하면 캐시 만료 후에도 파일이 변경되지 않았으면 304 Not Modified를 반환하여 바이트 전송 없이 처리합니다.

location ~* \.(html|xml|json)$ {
etag on; # ETag 자동 생성 (기본값 on)
expires 1h;
add_header Cache-Control "public, must-revalidate";
}

Gzip 압축

텍스트 기반 파일을 압축하여 전송하면 네트워크 트래픽을 50~80% 줄일 수 있습니다.

http {
gzip on;

# 압축 레벨 (1~9, 6이 속도와 압축률의 균형점)
gzip_comp_level 6;

# 압축할 최소 파일 크기 (너무 작은 파일은 압축 효과 없음)
gzip_min_length 1024;

# HTTP 1.0 프록시에도 압축 적용 (기본 HTTP 1.1만 적용)
gzip_http_version 1.0;

# 프록시에서 캐시할 때 Accept-Encoding 기준으로 분리
gzip_vary on;

# 압축할 MIME 타입 (text/html은 기본 포함)
gzip_types
text/plain
text/css
text/javascript
text/xml
application/javascript
application/x-javascript
application/json
application/xml
application/xml+rss
image/svg+xml
font/woff
font/woff2;

# 이미 압축된 파일 형식은 제외 (이중 압축 방지)
# JPEG, PNG, GIF, ZIP, PDF 등은 기본적으로 압축 안 함
}

Gzip 효과 확인

# Gzip 적용 확인 (Content-Encoding: gzip 헤더 확인)
curl -H "Accept-Encoding: gzip" -I http://example.com/app.js

# 응답 헤더에 다음이 있어야 함:
# Content-Encoding: gzip
# Vary: Accept-Encoding

Open File Cache — 파일 디스크립터 캐시

정적 파일을 자주 요청받는 환경에서는 파일 열기(open) 시스템콜 횟수를 줄이기 위해 파일 디스크립터를 캐시합니다.

http {
# 최대 1000개 파일 디스크립터 캐시, 20초 미사용 시 제거
open_file_cache max=1000 inactive=20s;

# 캐시 유효성 검사 주기 (30초마다 파일 변경 여부 확인)
open_file_cache_valid 30s;

# 캐시에 올리기 위한 최소 접근 횟수 (20초 내 2번 이상 접근)
open_file_cache_min_uses 2;

# 파일 없음(404) 에러도 캐시
open_file_cache_errors on;
}

정적 파일 서빙 종합 설정 예시

실무에서 사용하는 정적 파일 서빙 최적화 설정입니다.

server {
listen 80;
server_name static.example.com;
root /var/www/static;

# 파일 디스크립터 캐시
open_file_cache max=5000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;

# 버전 해시가 붙은 정적 에셋 (예: app.abc123.js)
# 파일 내용이 바뀌면 파일명도 바뀌므로 1년 강력 캐시
location ~* \.[0-9a-f]{8,}\.(js|css)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}

# 이미지·폰트 — 30일 캐시
location ~* \.(png|jpg|jpeg|gif|webp|ico|svg|woff|woff2|ttf)$ {
expires 30d;
add_header Cache-Control "public";
access_log off;
}

# HTML — no-cache (항상 최신 확인)
location ~* \.html$ {
add_header Cache-Control "no-cache";
}

# 기본
location / {
try_files $uri $uri/ =404;
expires 7d;
add_header Cache-Control "public";
}
}

정리

최적화 항목설정효과
sendfile on커널 직접 전송CPU 사용량 감소, 처리량 향상
tcp_nopush onTCP 패킷 묶음 전송네트워크 효율 향상
expires브라우저 캐시 기간재방문 시 요청 수 감소
Cache-Control: immutable강력 캐시 (변경 없음 보장)불필요한 재검증 요청 제거
gzip on텍스트 압축전송량 50~80% 감소
open_file_cache파일 디스크립터 캐시I/O 시스템콜 감소
Advertisement