WP Rocket 한글 URL: nginx가 캐시 파일을 찾지 못하는 원인과 njs 해결

공정위 광고 문구 Ver 3.1
공정위 광고 문구 Ver 3.2(모바일 전용)
테스트 환경 RHEL 계열AlmaLinux 9.8Nginx 1.28 + Apache 2.4WP Rocket 3.23.5.12026-10-17 확인

WP Rocket 한글 URL 글은 캐시 파일이 있어도 Nginx + Apache 구성에서 nginx가 그 파일을 찾지 못하고 요청을 Apache와 PHP로 넘기게 됩니다. WP Rocket이 캐시 폴더 이름을 만드는 코드와 nginx가 캐시 파일을 찾는 설정을 대조한 결과, 폴더 이름은 소문자 퍼센트 인코딩이고 브라우저가 한글 주소를 직접 인코딩할 때는 대문자라 경로가 어긋나는 것을 확인했으며, nginx에서 요청 주소의 인코딩을 소문자로 맞추는 방법을 정리했습니다.

원인은 여러 가지일 수 있습니다. 이 페이지에서는 WP Rocket 캐시 파일을 nginx가 찾지 못하는 한글 주소 문제를 다루며, 영문이 아닌 다른 언어 주소에도 도움이 될 수 있는 점을 참고하여 작성하였습니다.

영문 슬러그 글은 nginx가 캐시 파일을 바로 응답하지만, 한글 슬러그 글은 같은 캐시 파일이 있어도 PHP가 한 번 실행된 뒤에 그 파일을 응답하게 됩니다. 페이지를 새로 만들지는 않으므로 방문자 화면은 같고, 요청마다 PHP 워커를 사용하는 점이 다릅니다.

1️⃣ WP Rocket 한글 URL 캐시 폴더: 소문자 퍼센트 인코딩

캐시 폴더에 한글 이름의 폴더가 생길 것으로 생각하기 쉽지만, WP Rocket은 요청 주소를 그대로 폴더 이름으로 사용하면서 퍼센트 인코딩 부분만 소문자로 변환합니다. [wp-content/cache/wp-rocket/도메인/] 아래에 주소 경로대로 폴더가 생기고, 그 안에 [index-https.html]과 모바일 사본 [index-mobile-https.html], 압축본 [_gzip] 파일이 저장됩니다.

wp-content/cache/wp-rocket/example.com/
wp-rocket/영문 슬러그: 주소 그대로
index-https.html
index-https.html_gzip
index-mobile-https.html
index-mobile-https.html_gzip
%ec%9b%8c%eb%93%9c%ed%94%84%eb%a0%88%ec%8a%a4-%ec%ba%90%ec%8b%9c/한글 슬러그: 소문자 인코딩
index-https.html
index-mobile-https.html

소문자 변환은 캐시 파일 경로를 만드는 함수가 [%EC]처럼 퍼센트 뒤 두 자리를 찾아 바꾸는 한 줄로 처리되며, 3.3 판부터 이어진 동작입니다. 코드 주석에는 일부 호스팅에서 생기는 문제를 막기 위한 변환이라고 적혀 있고, 이 동작을 끄는 설정이나 필터는 없습니다.

// wp-rocket/inc/classes/Buffer/class-cache.php (3.23.5.1) — 캐시 파일 경로를 만드는 함수 안
$request_uri_path = preg_replace_callback( '/%[0-9A-F]{2}/', [ $this, 'reset_lowercase' ], $request_uri_path );

// 같은 파일 — 3.3 판부터
protected function reset_lowercase( $matches ) {
    return strtolower( $matches[0] );
}

워드프레스가 만드는 글 주소도 같은 소문자 인코딩입니다. 한글 슬러그는 데이터베이스에 [%ec%9b%8c]처럼 소문자로 저장되고, 글 목록과 관련 글 카드처럼 워드프레스가 함수로 만드는 링크는 이 값으로 나가므로 폴더 이름과 일치합니다. 고유주소와 슬러그가 정해지는 과정은 따로 정리되어 있습니다.

2️⃣ nginx 캐시 파일 찾기: 소문자 폴더와 대문자 요청 주소

워드프레스가 만든 링크로 들어온 요청은 소문자 폴더와 일치해 nginx가 찾지만, 브라우저가 직접 인코딩한 주소는 그렇지 않습니다. 주소창에 한글 주소를 입력하거나 한글이 그대로 적힌 링크를 누르면 브라우저가 [%EC%9B%8C]처럼 대문자로 인코딩해 보내며, URL 표준인 RFC 3986도 대문자를 정규형으로 정하고 있습니다.

nginx가 WP Rocket 캐시 파일을 직접 응답하는 설정은 요청 주소로 파일 경로를 만들고 [try_files]로 파일이 있는지 확인한 뒤, 없으면 Apache로 넘깁니다. 경로에는 디코딩된 [$uri]가 아니라 요청 그대로의 [$request_uri]를 사용해야 퍼센트 인코딩 이름의 폴더와 맞으며, nginx는 이 값을 글자 그대로 비교합니다.

# nginx — WP Rocket 캐시 파일 직접 응답 (요약)
#   $rocket_bypass: 로그인·댓글 쿠키, GET·HEAD 이외, 쿼리스트링, 관리자 경로면 1
set $rocket_file 'https://uknew.co/core/cache/wp-rocket/example.com${request_uri}index$rocket_mobile-https.html';
if ($rocket_bypass = '1') { set $rocket_file ''; }

location / {
    try_files $rocket_file @apache;
    add_header X-Rocket-Nginx-Serving-File 'HIT' always;
}

그래서 대문자로 인코딩된 요청은 소문자 폴더를 찾지 못하고 [@apache]로 넘어갑니다. 편집기에서 한글 주소를 그대로 붙여 넣은 본문 링크도 브라우저가 대문자로 인코딩하므로, 사이트 안 링크라도 이 경우에는 같은 결과가 됩니다.

🧭 한글 주소 요청 흐름
WP Rocket설명 다이어그램
🔤 요청 주소
🌐 요청
/워드프레스-캐시/
/%ec%9b%8c…/워드프레스가 만든 링크
/%EC%9B%8C…/주소창 입력한글이 그대로 적힌 링크
요청→
🖥️ nginx
📁 캐시 폴더 /%ec%9b%8c…/
🔎 [try_files] 글자 그대로 비교
✅ 같은 글자 · 찾음
❌ 다른 글자 · 못 찾음
파일 응답@apache→
📄 응답
① nginx가 바로 응답X-Rocket-Nginx-Serving-File: HITnginx 안에서 끝남 · 첫 바이트 0.015~0.017초
② Apache → PHP → 같은 캐시 파일advanced-cache.phpcharset=UTF-8 · max-age=0PHP 워커 하나 할당 · 첫 바이트 0.024~0.028초
워드프레스가 함수로 만든 링크는 소문자로 인코딩되어 캐시 폴더 이름과 글자가 같으므로, nginx가 캐시 파일을 찾아 PHP를 거치지 않고 바로 응답합니다.
주소창에 입력한 한글 주소나 한글이 그대로 적힌 링크는 브라우저가 대문자로 인코딩하며, nginx는 소문자 폴더를 찾지 못해 요청을 [@apache]로 넘기게 됩니다. 이어서 PHP의 [advanced-cache.php]가 같은 캐시 파일을 찾아 응답합니다.

이 구성의 하루 접근 기록(2026년 10월 16일)에서 사람이 요청한 한글 글 주소는 대문자 인코딩 795건, 소문자 34건으로 96%가 대문자였으며, 전날도 767건과 27건으로 같은 꼴이었습니다. 대문자 요청의 유입은 리퍼러 없음 422건, 구글 176건, 네이버 138건, 사이트 안 링크 47건, 빙 4건이었습니다.

WP Rocket의 사전 로드(Preload)도 한글 주소를 대문자로 요청하며, 같은 날 6,026건이 전부 대문자였습니다. 캐시가 있으면 새로 만들지 않고 같은 파일을 받지만, 이 요청들도 nginx가 아니라 PHP 쪽으로 가게 됩니다.

3️⃣ PHP로 넘어간 뒤: advanced-cache.php가 같은 캐시 파일을 응답

nginx가 캐시 파일을 못 찾았다고 캐시가 비활성화된 것은 아닙니다. Apache로 넘어간 요청은 PHP를 실행하고, 워드프레스가 로드되기 전에 WP Rocket의 [advanced-cache.php]가 먼저 실행됩니다. 이 파일은 요청 주소의 퍼센트 인코딩을 소문자로 바꾼 경로에서 캐시 파일을 찾아 그대로 응답하므로, 페이지를 새로 만들지는 않습니다.

차이는 그 요청에 PHP 워커 하나가 할당된다는 점입니다. 정적 파일 응답은 nginx 안에서 끝나지만 PHP 응답은 php-fpm 워커를 거치며, 응답 헤더도 PHP가 정한 값으로 나갑니다. PHP에 도달한 요청에 nginx 속도 제한을 걸어 둔 구성이라면 한글 주소 글 요청도 그 제한의 대상이 됩니다.

curl -sI --resolve example.com:443:127.0.0.1 -A 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0' 'https://example.com/%ec%9b%8c%eb%93%9c%ed%94%84%eb%a0%88%ec%8a%a4-%ec%ba%90%ec%8b%9c/' | grep -i 'HTTP/\|x-rocket-nginx-serving-file\|content-type\|cache-control\|content-length'
HTTP/2 200 
content-type: text/html
content-length: 773060
x-rocket-nginx-serving-file: HIT
curl -sI --resolve example.com:443:127.0.0.1 -A 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0' 'https://example.com/%EC%9B%8C%EB%93%9C%ED%94%84%EB%A0%88%EC%8A%A4-%EC%BA%90%EC%8B%9C/' | grep -i 'HTTP/\|x-rocket-nginx-serving-file\|content-type\|cache-control\|content-length'
HTTP/2 200 
content-type: text/html; charset=UTF-8
cache-control: max-age=0
x-content-type-options: nosniff

같은 한글 주소 글을 서버 안에서 소문자 주소와 대문자 주소로 각각 세 번씩 요청해 비교한 결과(2026년 10월 17일, 브라우저 UA), 소문자 요청에는 [X-Rocket-Nginx-Serving-File: HIT] 헤더와 [Content-Type: text/html]이 붙었고, 대문자 요청에는 그 헤더 없이 [Content-Type: text/html; charset=UTF-8]과 [Cache-Control: max-age=0]이 붙었습니다. 두 응답의 크기는 773,060바이트로 같아 같은 캐시 파일이며, 첫 바이트 시간은 소문자 0.015~0.017초, 대문자 0.024~0.028초로 대문자 쪽이 10ms 안팎 늦었습니다.

PHP까지 온 요청만 남는 Apache 접근 기록에서 세면, 같은 하루(2026년 10월 16일) 캐시 파일이 있는 한글 주소 글에 대한 요청 가운데 대문자 인코딩이 2,396건(200 응답 2,289건), 소문자가 11건이었습니다. 서버 자신의 사전 로드와 쿼리가 붙은 요청은 뺀 수이며, 캐시가 있는 글인데도 하루 2천 번 넘게 PHP 워커가 한 번씩 사용된 셈입니다.

4️⃣ 해결: nginx에서 요청 주소의 인코딩을 소문자로 변환

WP Rocket 쪽에서는 바꿀 수 없으므로 nginx가 비교하는 주소를 소문자로 맞추게 됩니다. nginx 기본 지시어에는 변수의 대소문자를 바꾸는 기능이 없어, njs 모듈의 [js_set]이나 perl 모듈의 [perl_set]으로 변환한 변수를 만들어 캐시 파일 경로에 사용합니다. 이 구성에는 njs 모듈의 [js_set]으로 아래와 같은 변환을 적용해 결과를 확인했으며, [perl_set] 설정은 적용하지 않고 nginx 공식 문서 기준으로 적었습니다.

이 설정은 nginx가 캐시 파일을 직접 응답하는 구성에만 필요합니다. Apache만 사용하거나 nginx가 모든 요청을 PHP로 넘기는 구성은 처음부터 [advanced-cache.php]가 처리하므로 해당하지 않으며, 영문 슬러그만 사용하는 사이트도 인코딩할 글자가 없어 영향이 없습니다.

# /etc/nginx/nginx.conf — 맨 위(http 블록 밖)
load_module modules/ngx_http_js_module.so;

# http 블록 안
js_import rocket from /etc/nginx/njs/rocket.js;
js_set $rocket_uri rocket.lowerEscapes;

모듈을 읽어 들이고, 변환 함수를 담은 파일을 등록한 뒤, 그 함수의 결과를 [$rocket_uri] 변수로 받는 세 줄입니다. 변수는 처음 참조되는 순간 계산되므로 다른 설정 순서를 바꿀 필요는 없습니다.

// /etc/nginx/njs/rocket.js — 퍼센트 인코딩 두 자리만 소문자로 (WP Rocket 과 같은 규칙)
function lowerEscapes(r) {
    return r.variables.request_uri.replace(/%[0-9A-F]{2}/g, function (m) {
        return m.toLowerCase();
    });
}
export default { lowerEscapes };

함수는 요청 주소에서 퍼센트 뒤 두 자리가 대문자인 곳만 찾아 소문자로 바꾸고, 그 밖의 글자는 그대로 둡니다. 이미 소문자로 인코딩된 요청은 바뀌는 곳이 없어 결과가 같습니다.

# server 블록 — 캐시 파일 경로의 $request_uri 를 $rocket_uri 로 바꾸는 한 줄
set $rocket_file 'https://uknew.co/core/cache/wp-rocket/example.com${rocket_uri}index$rocket_mobile-https.html';

변환 대상은 퍼센트 뒤 두 자리뿐입니다. 주소 전체를 소문자로 바꾸면 대문자가 들어간 영문 주소가 다른 경로가 되므로, WP Rocket과 같은 규칙으로 퍼센트 인코딩 부분만 바꿉니다. perl 모듈을 사용하는 서버라면 같은 변수를 [perl_set]으로 만들 수 있습니다.

# perl 모듈을 쓰는 서버 — 같은 변수를 perl_set 으로
load_module modules/ngx_http_perl_module.so;

perl_set $rocket_uri 'sub { my $r = shift; my $u = $r->variable(q(request_uri)); $u =~ s/%[0-9A-F]{2}/lc($&)/ge; return $u; }';

[perl_set]은 요청마다 이 서브루틴을 실행해 같은 치환을 적용합니다. 모듈 패키지는 같은 판의 nginx와 짝으로 배포됩니다. 공식 저장소에 설치된 nginx 판에 맞는 모듈이 없으면 nginx 업데이트가 함께 필요하며, 적용 전에 [nginx -t]로 설정을 검사한 뒤 reload합니다.

nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

설정 검사를 통과하고 reload한 뒤에는 대문자로 인코딩된 한글 주소 요청에도 [X-Rocket-Nginx-Serving-File: HIT]가 붙었으며, 모바일 요청에는 모바일용 캐시 파일이 나갔습니다.

소문자 주소와 영문 슬러그 요청은 그대로 nginx가 응답했고, 쿼리나 로그인 쿠키가 붙은 요청과 POST 요청은 전과 같이 PHP로 넘어갔습니다. 없는 한글 주소의 404 응답도 그대로였습니다.

5️⃣ 적용 전후 확인: 응답 헤더와 접근 기록

확인은 같은 글을 대문자 주소와 소문자 주소로 각각 요청해 응답 헤더를 비교하는 방법으로 합니다. nginx가 직접 응답하면 설정에 넣어 둔 표시 헤더가 붙고, PHP가 응답하면 그 표시 헤더가 붙지 않습니다.

curl -sI 'https://example.com/%EC%9B%8C%EB%93%9C%ED%94%84%EB%A0%88%EC%8A%A4-%EC%BA%90%EC%8B%9C/' | \
  grep -i 'x-rocket-nginx-serving-file\|cache-control\|content-type'
content-type: text/html
x-rocket-nginx-serving-file: HIT
curl -sI 'https://example.com/%ec%9b%8c%eb%93%9c%ed%94%84%eb%a0%88%ec%8a%a4-%ec%ba%90%ec%8b%9c/' | \
  grep -i 'x-rocket-nginx-serving-file\|cache-control\|content-type'
content-type: text/html
x-rocket-nginx-serving-file: HIT

접근 기록에서는 요청 줄의 인코딩 대소문자로 두 경우를 나눠 셀 수 있습니다. 적용 뒤에는 대문자 요청 수가 그대로여도 PHP까지 가는 요청이 줄어야 합니다. 이 수는 사람 방문자만 가린 것이 아니라 서버 자신의 사전 로드와 봇, 한글 이름의 목록 주소까지 포함한 전체 요청 수입니다.

grep -c 'GET /%E[0-9A-F]' /var/log/nginx/access.log
5989
grep -c 'GET /%e[0-9a-f]' /var/log/nginx/access.log
521

적용 뒤에는 같은 글의 대문자 주소 요청도 소문자 주소와 같은 헤더로 응답했습니다. 첫 바이트 시간은 대문자 주소가 적용 전 0.024~0.028초에서 적용 뒤 0.015~0.018초로 줄어, 소문자 주소의 0.016초 안팎과 같은 범위가 되었습니다.

📊njs 적용 전후 첫 바이트서버 안 요청 · 초
적용 전적용 뒤세 번 잰 범위
대문자 주소 첫 바이트
적용 전에는 Apache와 PHP를 거쳐 같은 캐시 파일을 응답했고, 적용 뒤에는 nginx가 캐시 파일을 바로 응답합니다.
00.0100.0200.030초
적용 전0.024~0.028초PHP 응답
적용 뒤0.015~0.018초nginx 응답
소문자 주소 첫 바이트
적용 전에도 nginx가 캐시 파일을 바로 응답하던 요청입니다.
00.0100.0200.030초
적용 전0.015~0.017초nginx 응답
적용 뒤0.016초 안팎nginx 응답
숫자는 2026년 10월 17일 같은 한글 주소 글을 서버 안에서 소문자 주소와 대문자 주소로 각각 세 번씩 요청해 잰 첫 바이트 시간입니다.

🔢 FAQ & 추천 콘텐츠

가능하지만 권하지 않습니다. 이미 발행된 글의 주소를 바꾸면 옛 주소마다 301 리디렉션이 필요하고, 한국어 검색어가 들어간 주소라는 장점도 잃게 됩니다. 이 글의 방법은 주소를 그대로 두고 서버 쪽 비교 규칙만 맞춥니다.

영향이 없습니다. WP Rocket은 주소로 캐시를 지울 때 경로의 한글을 다시 소문자 퍼센트 인코딩으로 변환해 폴더를 찾으므로, 대문자로 적힌 주소를 넘겨도 같은 폴더가 지워집니다. 파일 정리 함수 [rocket_clean_files] 기준입니다.

소문자 변환은 3.3 판부터 이어진 동작이며 이 글은 3.23.5.1 기준입니다. 업데이트 뒤에는 캐시 폴더 이름이 여전히 소문자인지와 nginx 표시 헤더가 붙는지만 확인하면 됩니다.

🧭 WP Rocket·Perfmatters 관련 글 글 13 · 도구 3
🗂️ What is WP?
📋 WP Rocket 설정 가이드 📄 Perfmatters 가이드 📄 WP Rocket With Perfmatters 📄 WP Rocket MCP Server 📄 WP Rocket 한글 URL 📄 WP Rocket 다국어 캐시
📄 WP Rocket 속도 테스트 📝 캐시·최적화 플러그인 📝 속도 개선 사례
📋 캐시: 정의와 모든 분야 📄 웹사이트 캐시 & 서버 내부 캐시
📋 BEST 호스팅 TOP 7 📄 빠른 호스팅 2026
🛠️ 워드프레스 감지 🛠️ 후이즈 도메인 조회 🛠️ 내 아이피 확인

ℹ️ 제휴 안내
본 사이트의 콘텐츠에는 제휴 링크가 포함되어 있습니다. 방문자가 이 링크를 통해 상품 또는 서비스를 구매하면 본 사이트는 판매처로부터 수수료를 지급받습니다. 이 과정에서 구매자가 지불하는 금액(이벤트 할인 시 금액이 내려갑니다↓)은 오르지 않습니다. 게시된 가격·할인·재고 정보는 작성 시점 기준이며 실제와 다를 수 있으므로, 구매 전 판매처에서 최종 확인하시기 바랍니다. 상품 선정과 평가는 자체 기준에 따라 작성되며, 수수료 지급 여부가 소개 순서나 평가 내용에 영향을 주지 않습니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

Prove your humanity: 5   +   9   =