I have a fully Nginx install now, with PHP-FPM. Coming from a world of Apache with Nginx proxying in front.

It seems Nginx has its own moody caching, very aggressive. There are a few layers of caching:

  1. Nginx's fastcgi cache itself. In my nginx.conf I have the following settings:

    fastcgi_cache_path        /var/run/nginx-cache  levels=1:2 keys_zone=WORDPRESS:100m inactive=60m;
    fastcgi_cache_key         "$scheme$request_method$host$request_uri";
    fastcgi_cache_use_stale   error timeout invalid_header http_500;
    fastcgi_ignore_headers    Cache-Control Expires Set-Cookie;
  2. THen there's php opcache. I've disabled it for now:

  3. I also have the following skip cache directives in the server blocks:

    set $skip_cache 0;
    # POST requests and urls with a query string should always go to PHP
    if ($request_method = POST) {
            set $skip_cache 1;
    if ($query_string != "") {
            set $skip_cache 1;
    #Don't cache the following URLs
    if ($request_uri ~* "/(wp-login.php|wp-admin|login.php|backend|admin)"){
        set $skip_cache 1;
    #Don't cache if there is a cookie called PHPSESSID
    if ($http_cookie ~* "PHPSESSID"){
        set $skip_cache 1;
    #Don't cache if there is a cookie called wordpress_logged_in_[hash]
    if ($http_cookie ~* "wordpress_logged_in_"){
        set $skip_cache 1;
    # Don't cache uris containing the following segments
    if ($request_uri ~* "cms/|/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") {
            set $skip_cache 1;
    # For the arc 
    if ($request_uri ~* "site/|/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") {
            set $skip_cache 1;
    # Don't use the cache for logged in users or recent commenters
    if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
            set $skip_cache 1;
  4. Then in the PHP block, I use that skip_cache stuff:

    location ~ \.php$ {
          try_files $uri $uri/ =404;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;
            fastcgi_cache_bypass                $skip_cache;
          fastcgi_no_cache                    $skip_cache;
            fastcgi_cache                       WORDPRESS;
            fastcgi_cache_valid  200 301 302    60m;

Despite all this, my admin screens are so aggressively cached that when I activate a plugin, it shows the older screen. When the plugin was deactivated. Only when I manually refresh the page do I see that the plugin is indeed activated.

What am I missing?

  • You should add the value of $skip_cache in your HTTP response headers by using add_header X-Skip-Cache $skip_cache, and then use browser developer tools to check all requests when pushing the activate button. This way you can see which request has $skip_cache set to zero, meaning it is returned from cache. Then you can update your cache rules. As a sidenote, you are not properly escaping literal dot . in your rules. You need to use \.php when you mean a filename with .php extension. Also, are you sure you restarted nginx after adding all the rules? – Tero Kilkanen Feb 14 '17 at 11:34

The way @MichaelHampton suggested is IMHO best (easiest, most efficient), but here's an alternative / additional approach that may be useful in some situations. It comes from my Wordpress / Nginx tutorial. Some themes mess with caching headers in a really bad way, so I basically want to rewrite all the headers differently for different areas of the website. I also remove the old Pragma header.

I define blocks for specific URLs or subdirectories so I can rate limit or control headers more precisely.

Note that "more_clear_headers" and "add_header" are part of the Headers More Nginx extension. If you look at the tutorial I linked to above, part two, it gives step by step instructions how to build Nginx with that module - it's pretty easy.

# Rate limiting for login pages
limit_req_zone $binary_remote_addr zone=login:1m rate=5r/m;

# Rate limit wp-login.php to help prevent brute force attacks
location = /blog/wp-login.php {
  # Next line applies the rate limit defined above
  limit_req zone=login burst=3;       
  fastcgi_keep_conn on;
  fastcgi_intercept_errors on;
  fastcgi_pass   php;
  include        fastcgi_params;
  fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
  more_clear_headers "Cache-Control";
  more_clear_headers Server; more_clear_headers "Pragma"; more_clear_headers "Expires";

  # No caching
  more_clear_headers "Cache-Control";
  add_header Cache-Control "private, max-age=0, no-cache, no-store";
  more_clear_headers "Expires";

  # Debugging aid - remove
  # add_header Z_LOCATION "WP-LOGIN"; add_header URI $uri;

# Wordpress admin caching headers are generally set correctly, for pages and resources. The only reason we define
# this block separately is to avoid messing with the headers in the main php block.
# This is probably unnecessary because of the skip_cache variable and may be removed
location ~* wp-admin {
  fastcgi_keep_conn on;
  fastcgi_intercept_errors on;
  fastcgi_pass   php;
  include        fastcgi_params;
  fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;

  # Debugging aid - remove
  # add_header Z_LOCATION "WP_ADMIN"; add_header URI $uri; add_header "Z_SKIP_CACHE" $skip_cache;

To add something else to add to Michael's answer, I also use this block. It disables caching for Wordpress Admin in a second way, and also disables caching for feeds, sitemap, xmlrpc, and a few others. Part of it's for Wordpress, I suspect part of it's for a custom written PHP website I run.

if ($request_uri ~* "/wp-admin/|/admin-*|/purge*|/xmlrpc.php|wp-.*.php|/feed/|sitemap(_index)?.xml|wp-cron") {
  set $fastcgi_nocache "true";
  • The directive more_clear_headers -- what do I need for that to work? My nginx is installed with yum, and doesn't seem to have this module. – PKHunter Feb 17 '17 at 17:13
  • headers more module. I've updated my answer to link to it, and instructions on how to build Nginx with that module. – Tim Feb 17 '17 at 18:47
  • Thank you. I'm unable to compile Nginx from source for a variety of reasons. But appreciate your comment, hope it helps someone. – PKHunter Feb 17 '17 at 19:31
  • Thank you for the add_header idea for debugging. I notice the following, which means the skip_cache is 1, but it's still sending the Cache-Control and expires stuff. What now? – PKHunter Jan 17 '18 at 17:09
  • @PKHunter suggest you ask a new question if you need help. I'd need much more detail than you can write in a comment to help you. – Tim Jan 17 '18 at 18:29

That bit of configuration looks familiar. I think it probably came from the WordPress wiki. In any event it's way overcomplicated, and mostly unnecessary.

I'm going to pull my config from a live WordPress site:

set $fastcgi_nocache "";
if ($http_cookie ~ (comment_author_.*|wordpress_logged_in.*|wp-postpass_.*)) {
    set $fastcgi_nocache "true";
fastcgi_ignore_headers Expires Cache-Control;
fastcgi_hide_header Pragma;
fastcgi_cache_bypass $fastcgi_nocache;
fastcgi_no_cache $fastcgi_nocache;

That's all. The only thing you really need to check is whether the user has a cookie indicating he's logged in, left a comment and wanted to be remembered, or entered a post password. Everything else either doesn't matter or doesn't apply to WordPress.

  • Thank you for the additional Pragma line, and the revised regex for the WP stuff. Very helpful. – PKHunter Feb 14 '17 at 18:23
  • Actually despite all this, the WP area is still heavily cached. – PKHunter Feb 17 '17 at 15:21
  • @PKHunter Are you sure you implemented it correctly? And more importantly, did you purge nginx's existing cache after making the changes? – Michael Hampton Feb 17 '17 at 15:22
  • Yes to all of the above. Also disabled opcache in PHP-FPM in case that was the issue. I clear nginx cache almost every time I make any change.. – PKHunter Feb 17 '17 at 15:46
  • @PKHunter Something else was wrong, then. This config doesn't cache anything site-wide for logged in users. Ever. Full stop. – Michael Hampton Feb 17 '17 at 15:49

I had the same issue for months... tried many different configurations for Nginx... nothing would work.


  • admin backend was cached so would not update when new page created or plugin installed.
  • switching between frontend and backend or even when clicking links would redirect for reauth.

Was just a simple issue: CLOUDFLARE

I had a Page Rule as follow:

*.mysite.com/* - Cache Level: Cache Everything

I deleted that rule, purged the cache and all is now back to normal.

  • Thanks. Where’s that rule from. From default nginx? Nothing in that rule you mention seems to overtly include cloud flare. Had you put in there and then had to delete it, or does nginx come with cloud flare stuff built in? – PKHunter Jan 21 at 9:52
  • This is not related to nginx but to Cloudflare. I put this rule in Cloudflare to boost the sites (I forgot about it) but that was too much caching so things went back to normal after I removed it from CF. If you do use CF maybe you should try bypassing it and see if you still have the same problem. If the problem goes away then you know this is CF that you have to fix. – Erus Jan 22 at 23:08
  • Something you must try: Use CHROME BROWSER and install an extension called: CLASSIC CACHE KILLER (you then need to activate it before you visite the website (Grey "OFF" flag must go green "ON" Then visit your site. If it works OK then your issue is CF caching or browser caching. – Erus Jan 22 at 23:13
  • If you want to try to visit your website by overriding CF you can just change your local /etc/hosts and point your URL directly to the server's IP (if you use Linux). If you use Windows you can do the same docs.rackspace.com/support/how-to/modify-your-hosts-file. MacOS will be like Linux I guess. Don't forget to delete the changes when you are done of course... :-) – Erus Jan 22 at 23:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.