이번 글은 성능(CWV) 관점에서 TTFB·캐시·압축을 중심으로, 징후→원인→해결→재검증 순서로 정리합니다.
1) 문제 징후: CWV에서 LCP/INP가 나빠졌는데, 체감은 ‘첫 화면이 늦게 뜸’
대부분의 경우 LCP가 밀릴 때는 “LCP 리소스(대표 이미지/폰트/히어로 영역 CSS)가 늦게 도착하거나, HTML 첫 응답(TTFB)이 늦어서 렌더링이 시작조차 늦는” 패턴이 많습니다. INP도 스크립트가 무거워 메인 스레드를 오래 잡고 있으면 체감과 함께 점수가 내려갑니다.
- Firefox → 개발자도구 → Network에서 문서(HTML) 요청의 Waiting(TTFB)가 이전보다 늘어났는지 확인
- 정적 리소스(JS/CSS/이미지)의 응답 헤더에 Content-Encoding이 비어 있는지(압축 미적용) 확인
- 정적 리소스에 Cache-Control이 짧거나(no-cache/0) 매번 재다운로드되는지 확인
- Firefox → Performance에서 로딩 초반에 긴 작업(Long task)처럼 CPU를 오래 쓰는 구간이 늘었는지 확인
이 단계의 목표는 “느려진 지점이 서버 첫 응답(TTFB)인지, 정적 전달(압축/캐시)인지, 프론트 실행(스크립트/폰트)인지”를 분리하는 것입니다.
아래부터는 서버(Nginx)에서 자주 나오는 원인과 해결을 묶어서 진행합니다.
2) 원인 후보: TTFB 증가(Upstream 지연, 버퍼링, Keep-Alive/HTTP2 미활성)
TTFB가 나빠졌다면 대개 Nginx 자체가 느리다기보다 Upstream(예: PHP-FPM, 앱 서버)이 늦거나, Nginx가 응답을 비효율적으로 전달하면서 체감이 늘어납니다. 또 HTTP/2가 꺼져 있거나 Keep-Alive가 비정상적으로 짧으면 리소스 요청이 많을수록 왕복이 늘어 LCP가 밀립니다.
- Upstream 처리 지연: 앱/DB 슬로우 쿼리, 워커 부족, 타임아웃 직전의 대기
- 버퍼 설정 이슈: 큰 응답을 과하게 디스크로 스필(spill)하거나, 반대로 버퍼가 너무 작아 지연
- 연결 재사용 문제: keepalive가 비활성/짧음, HTTP/2 미사용
Firefox Network에서 HTML 문서가 특히 느리면(Waiting이 큼) 이 쪽 가능성이 큽니다.
3) 해결: Nginx에서 TTFB 체감부터 낮추는 기본 점검(로그/타임아웃/HTTP2)
먼저 “어떤 요청이 얼마나 느린지”를 서버 로그에서 빠르게 보이게 만든 다음, HTTP/2와 타임아웃/버퍼를 현실적으로 조정합니다.
- access log에 $request_time, $upstream_response_time을 포함해 느린 구간이 Nginx인지 Upstream인지 분리
- HTTPS 사용 중이라면 HTTP/2 활성화 확인(리소스가 많은 페이지에서 체감 차이가 큼)
- 과도하게 짧은 proxy_read_timeout / fastcgi_read_timeout로 재시도가 발생하지 않는지 점검
- 응답이 큰 페이지에서만 느리면 proxy_buffers / fastcgi_buffers를 현실적으로(너무 작지도, 무작정 크지도 않게) 조정
주의할 점은, 버퍼/타임아웃을 “무조건 크게”가 아니라 문제 페이지(느린 URL) 기준으로 재현 → 조정 → 재측정하는 방식으로 가야 한다는 겁니다. 설정만 바꾸고 숫자가 좋아졌는지 확인하지 않으면 CWV가 더 흔들릴 수 있습니다.
4) 원인 후보: 압축 미적용(Content-Encoding 없음) 또는 잘못된 타입만 압축됨
JS/CSS/SVG/JSON 같은 텍스트 리소스는 압축이 체감에 직접적으로 영향을 줍니다. 압축이 꺼져 있거나, 압축 대상 타입이 빠져 있거나, 프록시/업스트림에서 이미 압축된 걸 Nginx가 다시 만지면서 비효율이 생기면 전송 시간이 늘어 LCP가 악화됩니다.
- Firefox Network에서 JS/CSS 응답 헤더에 Content-Encoding: gzip 또는 br가 없다
- 같은 파일이 환경/서버에 따라 어떤 곳은 압축되고 어떤 곳은 안 된다(설정 누락/서버 블록 차이)
- SVG/JSON은 큰데 압축 대상에서 빠져 있다
5) 해결: Nginx 압축(gzip)과 정적 리소스 전달을 ‘딱 필요한 만큼’ 정리
안전하게 시작하려면 gzip부터 정리하는 편이 운영 리스크가 낮습니다(브라우저 호환도 안정적). 아래는 방향성 체크 포인트입니다.
- gzip on 및 gzip_types에 text/css, application/javascript, application/json, image/svg+xml 등을 포함
- 이미 압축된 포맷(예: png, jpg, webp)은 gzip 이득이 적으므로 과도한 압축 적용은 피하기
- 정적 파일은 가능하면 Nginx가 직접 서빙하고(앱을 타지 않게), 필요한 경우 sendfile 같은 기본 최적화 옵션을 점검
- 정적 자산 경로(location)가 여러 곳으로 분기되어 있다면, 실제로 요청되는 경로에 압축/헤더가 적용되는지(서버 블록 우선순위 포함) 확인
개선 여부는 “파일 크기 감소 + 전송 시간 감소”로 보아야 합니다. Firefox Network에서 동일 파일의 transferred size(전송량)와 waterfall을 비교하면 바로 체감됩니다.
6) 원인 후보: 캐시 미스(캐시 헤더 부재, ETag/Last-Modified 혼선, HTML에 과한 no-store)
CWV가 들쭉날쭉하면 “어떤 사용자는 캐시가 먹고, 어떤 사용자는 매번 새로 받는” 패턴이 숨어 있는 경우가 많습니다. 특히 정적 자산에 캐시 헤더가 없거나, ETag/Last-Modified가 자주 바뀌어 304가 기대만큼 나오지 않으면 재방문 성능이 크게 떨어집니다.
- 정적 JS/CSS에 Cache-Control: max-age가 짧거나 없어서 매번 200으로 내려온다
- ETag가 서버/배포 방식 때문에 자주 바뀌어 캐시 효율이 낮다
- HTML에 습관적으로 no-store를 걸어 전체 로딩이 매번 무거워진다(로그인/개인화 페이지는 예외)
7) 해결 후 재검증: Firefox로 헤더/워터폴/TTFB를 다시 확인하는 체크리스트
바꾼 다음에는 반드시 “같은 조건”으로 다시 측정해야 합니다. 캐시가 끼어들면 착시가 생기므로, 재검증은 2회로 나눠 보는 게 안전합니다(첫 방문/재방문).
- 첫 방문(캐시 비움): Network에서 HTML 문서 TTFB가 줄었는지, LCP 리소스가 앞당겨졌는지 확인
- 재방문(캐시 유지): 정적 리소스가 200 재다운로드가 아니라 캐시/304로 처리되는지 확인
- JS/CSS 응답에 Content-Encoding이 기대대로 붙는지 확인
- Performance 프로파일에서 로딩 초반 긴 작업이 줄었는지 확인(특히 INP 체감)
- 서버 로그에서 느린 URL의 $upstream_response_time이 여전히 큰지(앱/DB 이슈가 남았는지) 확인
여기까지 했는데도 TTFB가 크다면, CWV 관점에서 다음 단계는 Nginx가 아니라 Upstream(예: PHP-FPM 워커/캐시, DB 인덱스, 앱 레벨 캐시)로 내려가야 합니다. 반대로 전송/캐시/압축에서 개선이 확인되면, CWV의 변동폭이 먼저 줄어드는 경우가 많습니다.
마무리
CWV를 성능 중심으로 잡을 때는 “점수 올리기”보다 TTFB/압축/캐시 같은 기본 체력을 안정화하는 게 우선입니다. Firefox에서 징후를 정확히 찍고, Nginx에서 원인을 한 가지씩 제거하면 결과가 흔들리지 않습니다.
원하시면 현재 Nginx 설정 일부(민감정보 제거)와 느린 URL의 Network 캡처 기준으로, 어떤 순서로 손대면 위험이 적은지 함께 정리해드릴게요.