글을 고쳐도 캐시가 그대로? — Nginx Helper 자동 퍼지 삽질기 (원인 4가지)

7편에서 FastCGI 캐시를 붙였고, 이번엔 글을 발행할 때마다 캐시를 자동으로 비우는 “자동 퍼지”를 붙이려 했다. Nginx Helper 플러그인만 깔면 5분이면 끝날 줄 알았다. 결론부터 말하면 반나절을 썼다. 권한을 의심하고, 경로를 의심하고, 해시를 손으로 계산하고, 급기야 플러그인 소스 코드까지 열어봤다. 그리고 마지막엔 “사실 처음부터 되고 있었다”는 허무한 진실과 마주했다. 같은 길을 걷는 사람이 나 같은 반나절을 버리지 않도록, 그 삽질을 순서대로 남긴다.

결론부터 — 자동 퍼지가 안 될 때 확인할 4가지.

1. 캐시 제거 방식을 PURGE/url이 아니라 로컬 서버 캐시 파일 삭제로 (기본 Nginx엔 퍼지 모듈이 없다).

2. wp-config.phpRT_WP_NGINX_HELPER_CACHE_PATH 상수를 넣어야 플러그인이 캐시 위치를 안다.

3. fastcgi_cache_key는 반드시 $scheme를 포함해야 한다(플러그인이 강제).

4. 검증은 curl -sI(HEAD)가 아니라 GET으로. HEAD로 보면 다 고쳐도 영원히 HIT만 뜬다.

증상 — 저장해도 계속 HIT, 글이 반영 안 된다

글의 내부 링크 하나를 고치고 저장했다. 그런데 사이트에는 옛날 그대로다. 터미널에서 캐시 상태를 봐도 계속 HIT.

curl -sI https://ddlabx.com/ | grep -i x-fastcgi-cache
# X-FastCGI-Cache: HIT   ← 수정했는데도 그대로

플러그인은 활성화돼 있고 “캐시 제거 활성화”도 체크돼 있는데, 아무 일도 일어나지 않는다. 여기서부터 삽질이 시작됐다.

삽질 1. 캐시 제거 방식이 잘못돼 있었다

Nginx Helper 설정의 “캐시 제거 방법”이 기본값인 GET 요청으로 PURGE/url로 되어 있었다. 이 방식은 ngx_cache_purge라는 Nginx 모듈이 빌드에 들어 있어야 작동한다. 그런데 apt로 설치한 우분투 기본 Nginx엔 그 모듈이 없다. 그래서 플러그인이 “지워달라”고 신호를 보내도 Nginx가 못 알아듣는다.

로컬 서버 캐시 파일 삭제 방식으로 바꿨다. 이건 별도 모듈 없이 플러그인이 캐시 파일을 직접 지운다. 그런데도 여전히 HIT. 원인은 하나가 아니었다.

삽질 2. 플러그인이 캐시 위치를 모르고 있었다

권한부터 의심했다. 로컬 파일 삭제 방식은 PHP(www-data)가 캐시 파일을 직접 지워야 하니까.

sudo ls -ld /var/cache/nginx/fastcgi
# drwxr-xr-x ... www-data www-data ...   ← 권한은 정상

폴더도 파일도 전부 www-data 소유였다. 권한은 범인이 아니었다. 그럼 뭘까 — 플러그인이 어디를 지워야 하는지 아느냐가 문제였다. Nginx Helper의 로컬 삭제 방식은 캐시 경로를 설정 화면에서 입력받지 않고, wp-config.php의 상수로 읽는다. 확인해보니:

grep -i RT_WP_NGINX_HELPER_CACHE_PATH /var/www/ddlabx/wp-config.php
# (아무것도 안 나옴 = 상수 없음)

상수가 없었다. 즉 플러그인은 캐시가 어디 쌓이는지 모른 채 조용히 넘어가고 있었다. wp-config.php의 “stop editing” 줄 위에 한 줄을 넣었다.

define( 'RT_WP_NGINX_HELPER_CACHE_PATH', '/var/cache/nginx/fastcgi' );

이걸로 되겠지 싶었다. 여전히 HIT였다.

삽질 3. 로그를 켜니 비로소 원인이 보였다

추측을 멈추고 로그를 켜기로 했다. 그런데 로깅 체크박스가 회색으로 비활성이다. 이것도 상수가 필요했다.

define( 'NGINX_HELPER_LOG', true );

상수를 넣고 지원 탭에서 로깅을 켠 뒤 글을 한 번 수정하니, 로그가 원인을 그대로 보여줬다.

- https://ddlabx.com/nginx-fastcgi-cache-wordpress/
    is currently not cached
    ( checked for file: /var/cache/nginx/fastcgi/7/a3/e5a67c0d28... )

플러그인은 정확한 글 주소를 지우려 하고 있었다. 대상은 맞다. 그런데 “그 주소의 캐시 파일을 이 경로에서 찾았는데 없더라”고 한다. 즉 플러그인이 찾는 경로와 실제 저장된 경로가 다르다. 이 경로는 캐시 키를 MD5로 해시해서 만들어진다. 해시가 어긋난 것이다.

여기서 얻은 교훈 하나. Nginx Helper는 로그를 켜기 전엔 아무 말도 하지 않는다. “안 된다”만 반복하며 설정을 헤집는 대신, NGINX_HELPER_LOG를 켜고 로그를 읽는 것이 가장 빠른 길이었다. 진작 켰어야 했다.

삽질 4. 진짜 원인 — 캐시 키의 $scheme (답은 소스에 있었다)

실제 저장된 캐시 파일의 키를 직접 열어봤다.

F=$(sudo find /var/cache/nginx/fastcgi -type f | head -1)
sudo strings "$F" | grep -i "KEY:"
# KEY: httpsHEADddlabx.com/nginx-fastcgi-cache-wordpress/

실제 저장 경로와 플러그인이 찾는 경로를 나란히 놓으니 확실히 달랐다.

구분해시 경로
실제 저장/4/2c/0dc5d7020c4a9fbb196535d3ffeff2c4
플러그인이 찾음/7/a3/e5a67c0d28ab0e4d386f7da50e00aa37

같은 글인데 해시가 다르다. 손으로 MD5를 여러 조합 돌려봐도 플러그인 쪽 해시를 못 만들었다. 추측을 끝내려고 플러그인 소스를 직접 열었다. admin/class-purger.php에 해시를 만드는 줄이 있었다.

$hash = md5( $url_data['scheme'] . 'GET' . $url_data['host'] . $url_path );

그리고 바로 위 주석이 못을 박아뒀다. 기본 nginx 캐시 옵션(levels=1:2)과 fastcgi_cache_key "$scheme$request_method$host$request_uri"반드시 써야 한다고. 즉 플러그인은 scheme + ‘GET’ + host + path로 해시한다. 캐시 키에 $scheme가 반드시 들어 있어야 한다는 뜻이다.

여기서 뜨끔했다. 디버깅 중간에 “혹시 $scheme가 문제인가” 싶어 나는 그걸 빼버렸었다. 정반대였다. 플러그인은 오히려 $scheme를 요구하고 있었다. 안 해도 될 삽질을 하나 더 얹은 셈이다.

플러그인이 해시하는 문자열맞는 fastcgi_cache_key
scheme + GET + host + path“$scheme$request_method$host$request_uri”

키를 원래대로($scheme 포함) 되돌리고, 옛 해시로 만들어진 캐시를 전부 비웠다.

sudo nginx -t && sudo systemctl reload nginx
sudo rm -rf /var/cache/nginx/fastcgi/*

마지막 착시 — HEAD로 검증하면 영원히 HIT

다 고쳤는데도 curl -sI는 여전히 HIT를 뱉었다. 그런데 이상하게, 관리자에서 글을 수정하면 실제 사이트엔 바로 반영됐다. “되는데 안 된다”는 모순. 여기서 진짜 마지막 원인을 깨달았다.

플러그인 소스를 다시 보자. 해시에 'GET'이 고정돼 있다. 플러그인은 GET 요청으로 만들어진 캐시만 지운다. 그런데 내가 검증에 쓴 curl -sI-IHEAD 요청이다. HEAD는 GET과 다른 별도의 캐시 파일을 만든다. 정리하면 이렇다.

요청캐시 키 예시플러그인이 지우나?
실제 방문자 (GET)httpsGETddlabx.com/…지운다 → MISS
내 curl -sI (HEAD)httpsHEADddlabx.com/…안 지운다 → HIT 그대로

즉 실제 방문자 기준으론 이미 정상 작동하고 있었다. 글 수정이 바로 반영된 게 그 증거였다. 나는 방문자와 다른 방식(HEAD)으로 캐시를 들여다보며 혼자 “안 된다”고 착각한 것이다. 방문자와 똑같이 GET으로 확인하니:

curl -s -D - -o /dev/null https://ddlabx.com/nginx-fastcgi-cache-wordpress/ | grep -i x-fastcgi-cache
# 수정 후 → X-FastCGI-Cache: MISS   ✅

MISS. 처음부터 되고 있던 걸, 잘못된 잣대로 재고 있었다.

검증은 방문자와 같은 방식으로. 캐시 상태를 볼 때 curl -sI(HEAD)는 편하지만, 실제 트래픽은 전부 GET이다. 헤더만 보고 싶다면 curl -s -D - -o /dev/null로 GET 요청의 헤더를 봐야 한다. 이 차이 하나로 반나절을 더 헤맸다.

최종 체크리스트 — 자동 퍼지가 안 될 때

확인 항목정상값
캐시 제거 방법로컬 서버 캐시 파일 삭제 (PURGE/url ✕)
캐시 경로 상수RT_WP_NGINX_HELPER_CACHE_PATH = 실제 캐시 폴더
캐시 폴더 권한www-data 소유, 쓰기 가능
fastcgi_cache_key“$scheme$request_method$host$request_uri”
로그NGINX_HELPER_LOG = true 로 켜서 원인 확인
검증 방법GET (curl -s -D -), HEAD(-sI) 아님

돌아보면 진짜 버그는 딱 두 개였다. 플러그인이 캐시 경로를 몰랐던 것(상수 누락), 그리고 마지막의 HEAD/GET 착시. 나머지는 그 둘을 찾아가는 과정에서 내가 만든 곁가지였다. 특히 $scheme를 뺐다가 다시 넣은 건 안 해도 됐을 삽질이었다. 그래도 덕분에 플러그인이 캐시 키를 어떻게 계산하는지 소스로 확인했으니, 남는 건 있었다고 위안한다. 자체 서버를 굴린다는 건 이런 삽질을 내 몫으로 떠안는 일이고, 대신 원리를 손에 쥐게 된다.

다음 편 — 속도 최적화 ③
삽질은 여기서 끝. 다음은 예고대로 서버 안쪽이다. PHP 8.3의 OPcache, PHP-FPM 워커 수, MariaDB 버퍼를 1 vCPU·2GB에 맞게 손봐서, 캐시가 빗나가는 순간(MISS)의 속도까지 끌어올린다. 역시 실측으로 확인한다.

댓글 남기기