2026年11月13日
2026年11月13日
Nginx Proxy Cache for WordPress: Skip Logged-In Users and Admin
This article is a localized edition of the Japanese original. Read the Japanese original
With the Nginx proxy cache (proxy_cache), Nginx stores the pages generated by the WordPress backend behind it (Apache or another application server) and serves the stored copy to the next visitors. Less PHP and database work is done, so a busy site puts less load on the server.
This article sets up the proxy cache for a layout where Nginx runs as a reverse proxy in front of WordPress on Apache listening on port 8080. The configuration was checked as of October 2026 on Nginx 1.30 (stable): it passes the nginx -t syntax check, and the cache returns HIT and BYPASS as described below.
If Nginx calls PHP-FPM directly (a setup that uses fastcgi_pass), use fastcgi_cache instead of the proxy cache. The idea is the same, but the directive names change to fastcgi_cache_path, fastcgi_cache_key and so on.
Layout and prerequisites
| Role | Listens on | What it does |
|---|---|---|
| Nginx | 80 (and 443 if needed) | Accepts visitor connections and serves the cached page if one exists; otherwise forwards the request to Apache |
| Apache + PHP (WordPress) | 127.0.0.1:8080 | Generates the WordPress pages |
The Nginx proxy module (ngx_http_proxy_module) is built in by default. If you use your distribution's package or a package from nginx.org, you do not need to rebuild Nginx. (The configure option --with-http_proxy_module mentioned in the old version of this article does not exist; there is only --without-http_proxy_module for leaving the module out.)
Step 1: Make Apache listen on 127.0.0.1:8080
On Debian and Ubuntu, change Listen in /etc/apache2/ports.conf.
# /etc/apache2/ports.conf
Listen 127.0.0.1:8080
Update the VirtualHost in your site configuration (for example /etc/apache2/sites-available/000-default.conf) to match.
<VirtualHost 127.0.0.1:8080>
ServerName example.com
DocumentRoot /var/www/html
</VirtualHost>
To keep visitors' IP addresses in the Apache access log, enable mod_remoteip and have it read the X-Forwarded-For header sent by Nginx.
# /etc/apache2/conf-available/remoteip.conf
RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 127.0.0.1
sudo a2enmod remoteip
sudo a2enconf remoteip
sudo apache2ctl -t && sudo systemctl restart apache2
Step 2: Configure the proxy cache in Nginx
Create an Nginx configuration file (for example /etc/nginx/conf.d/wp-proxy-cache.conf) with the following content. In both the Debian/Ubuntu packages and the nginx.org packages, /etc/nginx/conf.d/*.conf is included inside the http block, so you can put proxy_cache_path and map in this file.
# Where the cache lives and how big it can grow
proxy_cache_path /var/cache/nginx/wp levels=1:2 keys_zone=wpcache:10m
max_size=1g inactive=60m use_temp_path=off;
# Do not cache for logged-in users, visitors of password-protected posts, commenters, or shoppers with items in the cart
map $http_cookie $wp_skip_cookie {
default 0;
~*wordpress_logged_in_ 1;
~*wp-postpass_ 1;
~*comment_author_ 1;
~*woocommerce_items_in_cart 1;
}
# Do not cache the admin, login, WP-Cron, XML-RPC, REST API, or previews
map $request_uri $wp_skip_uri {
default 0;
~*^/wp-admin/ 1;
~*^/wp-login\.php 1;
~*^/wp-cron\.php 1;
~*^/xmlrpc\.php 1;
~*^/wp-json/ 1;
~*preview=true 1;
}
server {
listen 80;
server_name example.com;
set $skip_cache 0;
if ($request_method = POST) { set $skip_cache 1; }
if ($wp_skip_cookie) { set $skip_cache 1; }
if ($wp_skip_uri) { set $skip_cache 1; }
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache wpcache;
proxy_cache_key $scheme$request_method$host$request_uri;
proxy_cache_valid 200 301 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_bypass $skip_cache;
proxy_no_cache $skip_cache;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_lock on;
add_header X-Cache-Status $upstream_cache_status always;
}
}
What the main directives do
| Directive | Meaning |
|---|---|
proxy_cache_path |
The cache directory and its settings. keys_zone=wpcache:10m is the name and size of the shared memory zone that holds the cache keys (the official Nginx documentation says 1 MB holds about 8,000 keys), max_size is the limit on disk, inactive=60m removes cached items not used for 60 minutes, and use_temp_path=off writes files straight into the cache directory instead of going through a temporary path |
proxy_cache |
The cache to use (the name set in keys_zone) |
proxy_cache_key |
The key that identifies a cached item. The default is $scheme$proxy_host$request_uri; because several domains could get mixed up when they share the same proxy_pass target, this example includes $host |
proxy_cache_valid |
How long to keep responses per status code. Here 200, 301 and 302 are kept for 10 minutes and 404 for 1 minute |
proxy_cache_bypass |
When the value is neither empty nor 0, skip the cache and forward the request to Apache |
proxy_no_cache |
When the value is neither empty nor 0, do not store Apache's response in the cache |
proxy_cache_use_stale |
Serve an expired cached copy when Apache returns an error or does not respond |
proxy_cache_background_update |
Serve the expired copy while fetching a fresh page in the background (requires updating in proxy_cache_use_stale) |
proxy_cache_lock |
When several requests for the same page arrive at once, send only one of them to Apache |
Set both proxy_cache_bypass and proxy_no_cache. With proxy_cache_bypass alone, a page generated for a logged-in user can be stored in the cache and shown to other visitors.
By default, Nginx also does not store responses that carry a Set-Cookie header or Cache-Control: private, no-cache or no-store. Responses with cookies that WordPress sends, for example at login, are therefore not stored either. Only GET and HEAD requests are cached by default.
Step 3: Check the syntax and apply
Create the parent cache directory if it does not exist, then check the syntax and apply the change. Nginx creates /var/cache/nginx/wp at startup and gives ownership to the worker user.
sudo mkdir -p /var/cache/nginx
sudo nginx -t
sudo systemctl reload nginx
Make sure nginx -t prints syntax is ok and test is successful before you run reload.
Step 4: Check that the cache works
The X-Cache-Status header shows the cache status. Request the same URL twice: the first response is MISS, the second is HIT.
curl -s -o /dev/null -D - https://example.com/sample-page/ | grep -i x-cache-status
curl -s -o /dev/null -D - https://example.com/sample-page/ | grep -i x-cache-status
Requests with a logged-in cookie and requests to /wp-admin/ return BYPASS.
curl -s -o /dev/null -D - -H 'Cookie: wordpress_logged_in_test=1' https://example.com/sample-page/ | grep -i x-cache-status
The main values of $upstream_cache_status are:
| Value | Meaning |
|---|---|
MISS |
Not in the cache; fetched from Apache |
HIT |
Served from the cache |
BYPASS |
Matched the proxy_cache_bypass condition, so the cache was not used |
EXPIRED |
The cached copy had expired, so the page was fetched from Apache |
STALE |
An expired copy was served because of proxy_cache_use_stale |
UPDATING |
An expired copy was served while the entry was being updated |
If a request without cookies stays at MISS no matter how often you repeat it, your theme or a plugin may be sending Set-Cookie or Cache-Control: no-cache. Check Apache's response headers directly with curl -I http://127.0.0.1:8080/ -H 'Host: example.com'.
The X-Cache-Status header is for checking only. If you do not want to expose it, remove the add_header line when you are done.
Clearing the cache
After you update a post, the old page keeps being served until the cache period (10 minutes in this example) runs out. Open-source Nginx has no proxy_cache_purge directive for removing a specific URL from the cache (that is a feature of the commercial NGINX Plus).
To apply a change right away, delete the contents of the cache directory. The next request is a MISS, and the new page is stored.
sudo find /var/cache/nginx/wp -type f -delete
If many pages need to change immediately after updates, shorten the proxy_cache_valid time or add a third-party module that supports cache purging.
When Nginx terminates HTTPS
If Nginx accepts HTTPS and forwards requests to Apache over HTTP, WordPress may think the page is being served over HTTP and keep redirecting the admin screens. As described in the official WordPress documentation (Administration Over SSL), add the following to wp-config.php before the /* That's all, stop editing! */ line.
<?php
// When Nginx terminates HTTPS and passes requests to the backend over HTTP
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
$_SERVER['HTTPS'] = 'on';
}
(wp-config.php already starts with <?php, so add only the lines after it.) The Nginx configuration needs proxy_set_header X-Forwarded-Proto $scheme; as shown in Step 2.
Things to watch out for
- With caching in place, anything that runs on every page view (view counters, content that changes on each view, and so on) stops running for as long as a page stays cached. On membership sites and online stores, add every URL and cookie used in the login and checkout flows to the exclusion rules before you turn caching on
- Using this together with a WordPress caching plugin makes it hard to tell which cache served a page. Pick one, and confirm the behavior with
X-Cache-Status - Have Apache listen on port 8080 only on
127.0.0.1so it cannot be reached directly from outside - After every change, check the syntax with
nginx -tbefore youreload
Summary
- Nginx
proxy_cachestores pages from WordPress running on Apache (or similar) and serves them from Nginx - Exclude logged-in cookies, URLs such as the admin, login and REST API, and POST requests with both
proxy_cache_bypassandproxy_no_cache - Check
MISS,HITandBYPASSwith theX-Cache-Statusheader - With open-source Nginx, clear the cache by deleting the directory contents or keep the cache period short
- When Nginx terminates HTTPS, set
$_SERVER['HTTPS']inwp-config.phpbased onX-Forwarded-Proto
Related articles:
- How to set up a reverse proxy with Nginx
- How to test Nginx configuration files
- Running WordPress on Nginx
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)