WP Super Cache + (optional) Domain-Mapping

Update: This article is updated for WordPress 3.5 multisite’s file-handling.

easyengine (ee) note: If you are using easyengine, you can accomplish everything in this article using following command:

ee site create example.com --wpsubdom --wpsc

In this article, we will setup WordPress Multisite using subdomains with Nginx with added support for WP Super Cache Plugin.

Nginx Config

Below is recommended Nginx configuration.

server {
        ##DM - uncomment following line for domain mapping  
        #listen 80 default_server;
    server_name example.com *.example.com ;
    ##DM - uncomment following line for domain mapping
    #server_name_in_redirect off;

    access_log   /var/log/nginx/example.com.access.log;
    error_log    /var/log/nginx/example.com.error.log;

    root /var/www/example.com/htdocs;
    index index.php;

    set $cache_uri $request_uri;

    # POST requests and urls with a query string should always go to PHP
    if ($request_method = POST) {
        set $cache_uri 'NULL';
    }   
    if ($query_string != "") {
        set $cache_uri 'NULL';
    }   

    # Don't cache uris containing the following segments
    if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-locations.php|sitemap(_index)?.xml|[a-z0-9_-]+-sitemap([0-9]+)?.xml)") {
        set $cache_uri 'NULL';
    }   

    # Don't use the cache for logged in users or recent commenters
    if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in") {
        set $cache_uri 'NULL';
    }

    # Use cached or actual file if they exists, otherwise pass request to WordPress
    location / {
        try_files /wp-content/cache/supercache/$http_host/$cache_uri/index.html $uri $uri/ /index.php ;
    }    

    location ~ \.php$ {
        try_files $uri =404;
        include fastcgi_params;
        fastcgi_pass 127.0.0.1:9000;
        #following line is needed by WP SUPER CACHE plugin
        fastcgi_param SERVER_NAME $http_host;
    }

    location ~* ^.+.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
        access_log off; log_not_found off; expires max;
    }

    location = /robots.txt { access_log off; log_not_found off; }
    location ~ /. { deny  all; access_log off; log_not_found off; }
    location ^~ /wp-content/cache/ { deny  all; access_log off; log_not_found off; }    
}

Domain Mapping

You need to uncomment few lines in above nginx-config to get domain-mapping working. Apart from above config changes, you can read this guide to setup/configure domain-mapping.

Must Read:

14 responses to “WP Super Cache + (optional) Domain-Mapping”

  1. Great series the “WordPress Nginx Tutorials”. Well done and an interesting approach.

    But i have 3 questions.

    1) Why will “/index.php?$args” break WP Super Cache?
    I have a different setup for “Nginx + WordPress Multisite Subdomains + Domain Mapping Plugin + WP Super Cache”, similar to http://codex.wordpress.org/Nginx, and passing the $query_string to index.php doesn’t break WP Super Cache. It works all the same.

    2) Why is “fastcgi_param SERVER_NAME $http_host” essential for WP Super Cache?
    I don’t use it and it works all the same with the default setting in fastcgi.conf: “fastcgi_param SERVER_NAME $server_name”

    3) What is this block for “location ^~ /blogs.dir { … }”
    The request “/blogs.dir” shouldn’t exist, right? Why have a location for it? It should return 404.

    Maybe i’m wrong and you are right, but please explain. Thank you.

    Salutations.

    Gonçalo Peres

    • Thanks Gonçalo. I will answer your questions 1-by-1 🙂 #1. Why will /index.php?$args break WP Super Cache? If you enable debugging logs for WP Super Cache, you will see Supercache caching disabled. Only using wp-cache. Non empty GET request. in error messages. ?$args will force WP Supercache to dropback to legacy mode in which it does low-performance caching. Ideally, you should be able to see a cached page, even if PHP crashes. Using /index.php?$args will throw bad-gateway error as PHP is used for caching (PHP-involvement degrades caching performance) I have published a checklist for perfect nginx setup. It has a simple test to verify if WP Super cache running to its full potential. #2. Why is “fastcgi_param SERVER_NAME $http_host” essential for WP Super Cache? WP Super Cache uses PHP’s $_SERVER['SERVER_NAME'] instead of $_SERVER['HTTP_HOST'] In domain mapping, by default $_SERVER['SERVER_NAME'] will always have value of server_name directive which is generally “_” or main domain. Thus WP Super Cache will break but not for logged in users. Also, if you have same theme across main domains and NO same article on different domains you may not notice it. Also it may not be noticed if caching is already running in low-performance mode because of issue /index.php?$args (refer to #1) Next version will have this fixed, as confirmed by Donncha (WP Super cache developer). Once it gets released, I will update all posts. #3. What is this block for location ^~ /blogs.dir {..} This block is “internal” for nginx, as you can see form line “internal” in it. WordPress-Multisite uses PHP to “find” and “read” static files (e.g. images uploaded from posts). Using define('WPMU_ACCEL_REDIRECT', true); and ^~ /blogs.dir {..}, we can reduce load on PHP significantly. With this PHP “find” file and hand-over their location to nginx “internally”. And then nginx does reading. A better idea is to use Nginx Map{}. If we use it Nginx can “find” and “read” file itself reducing load on PHP completely. Static files can be served even if PHP stopped! You can read more about Nginx Map here. If you have any more questions, feel free to ask.

  2. Hello Rahul and thank you for replying.

    I’m using a test configuration (WordPress 3.4 .2 Multisite enabled) with no plugins, except for:
    WP Super Cache 1.1
    MU Domain Mapping 0.5.4.2
    Theme: Twenty Eleven

    1) I’ve made some tests with “/index.php?$query_string” at the end of the try_files, to confirm what you claim. WP Super Cache does work as expected.
    Maybe the message you see in debug mode (Supercache caching disabled. Only using wp-cache. Non empty GET request) is caused by MU Domain Mapping. In Network Administration go to Options, Domain Mapping and make sure “Remote Login” is not checked. If it is checked, it loads a javascript call of type GET with multiple args in the header of all your multisites. Meaning that in debug mode, when you call any page or post, you will see that second request with GET parameters set, thus throwing the WP Super Cache error message.
    Even if it’s not that, WP Super Cache in debug mode doesn’t complain whatsoever with “/index.php?$query_string” in the end of try_files.
    I can stop PHP-FPM and still get all the pages that exist on cache.

    2) I never get this issue. My Nginx server blocks are configured with only the correct domain, which is the same setting in Domain Mapping. And like i said before, the “fastcgi_param SERVER_NAME $server_name” set in my fastcgi.conf included file works fine.

    My configuration per multisite is like this:

    ### example.com ###
    server {
    	server_name www.example.com;
    	rewrite ^ http://example.com$request_uri? permanent;
    }
    
    server {
    	server_name		example.com;
    	root				/var/www/wordpress-network;	
    	include 			wordpress-network-super-cache.conf;
    }

    Inside “wordpress-network-super-cache.conf” i have this block:

    # Pass all .php files onto a php-fpm/php-fcgi server.
    location ~ \.php$ {
    	try_files $uri =404;
    	include fastcgi.conf;
    }

    And inside “fastcgi.conf” i have:

    fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
    fastcgi_param  QUERY_STRING       $query_string;
    fastcgi_param  REQUEST_METHOD     $request_method;
    fastcgi_param  CONTENT_TYPE       $content_type;
    fastcgi_param  CONTENT_LENGTH     $content_length;
    
    fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
    fastcgi_param  REQUEST_URI        $request_uri;
    fastcgi_param  DOCUMENT_URI       $document_uri;
    fastcgi_param  DOCUMENT_ROOT      $document_root;
    fastcgi_param  SERVER_PROTOCOL    $server_protocol;
    fastcgi_param  HTTPS              $https if_not_empty;
    
    fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
    fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;
    
    fastcgi_param  REMOTE_ADDR        $remote_addr;
    fastcgi_param  REMOTE_PORT        $remote_port;
    fastcgi_param  SERVER_ADDR        $server_addr;
    fastcgi_param  SERVER_PORT        $server_port;
    fastcgi_param  SERVER_NAME        $server_name;
    
    fastcgi_pass unix:/tmp/php-fpm.sock;

    3) I know WordPress-Multisite uses PHP to “find” and “read” static files.
    I use the approach suggest in http://codex.wordpress.org/Nginx, with a “ms-filemap” directory inside “wp-content” and a symbolic link for each blog dir (ex: “ln -s ../blogs.dir/2 example.com”).

    # For multisite:  Use a caching plugin that creates symlinks to the correct subdirectory structure to get some performance gains.
    set $cachetest "$document_root/wp-content/ms-filemap/${host}${uri}";
    if ($uri ~ /$) {
    	set $cachetest "";
    }
    if (-f $cachetest) {
    	# Rewrites the URI and stops rewrite processing so it doesn't start over and attempt to pass it to the next rule.
    	rewrite ^ /wp-content/ms-filemap/${host}${uri} break;
    }
    

    The “Nginx Map{} “does look a very good idea too.
    The reason i asked this 3rd question is because I though you were using “Nginx Map{}”, but now I see the configuration is commented!

    Nice talking to you 🙂

    Greetings from Portugal

    Gonçalo Peres

    • #1. index.php?args issue
      Yes, we use “Remote Login” option but that doesn’t break W3 Total Cache or other caching plugins.
      Only WP Super Cache is broken.
      Its broken for many people as you can see: https://www.google.com/search?q=Supercache+caching+disabled.+Only+using+wp-cache

      #2. fastcgi_param issue
      Line: fastcgi_param SERVER_NAME $server_name;
      is different than: fastcgi_param SERVER_NAME $http_host;

      May be again difference is our results related to different ways in we use domain mapping.
      But WP Super Cache developer himself agreed that he should relay on HTTP_HOST value as you can see from his reply here:
      http://wordpress.org/support/topic/plugin-wp-super-cache-warning-wpsupercache-11-and-nginx-broken-on-multisite?replies=16#post-2913202

      I guess you are using development version of WP Super Cache or missing a test case may be.
      Its a documented issue with WP Super Cache plugin as you can see from above link!

      #3. Nginx Map issue
      We are using Nginx Map{..} on this site. Above configuration is not actual configuration of this site. We don’t use WP Super Cache or W3 Total Cache anymore.
      We are currently using a variant of this: http://rtcamp.com/tutorials/wordpress-multisite-subdomains-domain-mapping-nginx-fastcgi-cache-purge/

      All my config will have some lines commented with instructions.
      In above config please check instructions here – http://rtcamp.com/tutorials/nginx-maps-wordpress-multisite-static-files-handling/
      They will guide for Nginx map{} usage.

      Map section values will change for every setup so I cannot put all values here which will work for everyone.

      I am aware of symbolic method as well but Nginx Map is better way to deal with it.
      IMHO why use workaround/hacks for something which Nginx can handle itself.
      On a network with 1000’s of sites, blogs.dir folder will be cluttered with symbolic links. Plus if a site is mapped with multiple domains, you may need to create multiple symlinks which will increase clutter further.

      If you use Nginx helper plugin, it will create and update a map file automatically every time you add/remove a site in Multisite network. In next version we will be adding some more features to that plugin to automate few more thing for wordpress-nginx setup.

    • On a side-note, for part #3, if you wish to continue with symbolic-links atleast switch to try_files

      You will be able to wrap up following and few more config lines in a single-line….

      # For multisite:  Use a caching plugin that creates symlinks to the correct subdirectory structure to get some performance gains.
      set $cachetest "$document_root/wp-content/ms-filemap/${host}${uri}";
      if ($uri ~ /$) {
      	set $cachetest "";
      }
      if (-f $cachetest) {
      	# Rewrites the URI and stops rewrite processing so it doesn't start over and attempt to pass it to the next rule.
      	rewrite ^ /wp-content/ms-filemap/${host}${uri} break;
      }
  3. Hello Raul.

    #3. Nginx Map issue
    Yes I agree with you: the “map” directive does look better, along with “Nginx helper plugin” and the “try_files” approach.
    I’ll switch to it in the near future.

    For quite some time i’ve also been thinking on switching to the built-in support for “fastcgi_cache”. It’s definitely a step further
    Have you found any disadvantages compared to WP Super Cache, that “Nginx helper plugin” doesn’t cover?

    Ok, I see that you guys developed “Nginx helper plugin”. Great 🙂
    Does it have an option to purge all cache files for a specific multisite ?
    Is that option available to individual site administrators?
    (I’m asking because i could not figure it out from the plugin page)

    Thank you Rahul and best regards 🙂

    Gonçalo Peres

    • Q. Have you found any disadvantages compared to WP Super Cache, that “Nginx helper plugin” doesn’t cover?
      From a user perspective fastcgi_cache needs more work.

      For example, if I want to purge complete cache, I need to do “rm -rf” in folder nginx using for caching.

      WP Super Cache & W3 Total Cache are both easy to use. W3 Total Cache also offers other features like Object-Cache, Database-cache, minify, CDN. Which are all good.

      Nginx’s fastcgi_cache is more raw in that sense. I *think* nginx must be burning less CPU cycles while creating a full-page cached copy as compared to any WordPress plugin. Just a intuitive guess, I do not have any data to support this thought.

      Personally, I am not driven by benchmarks. That is why while trying different config I didn’t bother to run any benchmark.

      Q. Regarding Nginx-Helper, option to purge all cache files for a specific multisite, and more ??
      Nginx-Helper supports purging when a page/post is edited. Even when a comment is approved on a post/page.

      We are definitely planning to add a “Purge ALL” option in near future. For that, I first plan to contact developer of https://github.com/FRiCKLE/ngx_cache_purge
      If he can implement “purge all” in his module it will be safer.

      Purging cache for a multi-site is tricky. Nginx stores cached content using hashes. WP Super Cache/W3 Total Cache plugin uses domain-names & URLs.

      In WP Super Cache, you can just delete everything for an individual site at a path like /wp-content/cache/supercache/site1.example.com/

      In Nginx, it will be easy to “purge all” cached content. But for a specific site, as of now there is no way to track what pages are cached.

      I will see if we can maintain a list of cached-pages using Nginx-Helper plugin or some other mechanism.

      Another idea would be to ask nginx to use separate cache-dir per domains (mapped to $http_host value). This may be done efficiently using nginx map{..}.
      Thanks for giving this idea though. I will definitely dig deeper into this… 🙂

  4. Fastcgi_cache

    Yep, it would be great if the “fastcgi_cache_path” directive existed under the “server” block context, and not only inside “http” context. That way it would be possible to deal with each multisite cached pages. Until then, WP Super Cache is still my option.
    Yes, probably WP Super Cache uses more machine resources to build a static page, compared to fastcgi_cache. But in “real world” you won’t even notice it. To 99% of you visitors you will be serving static content, and whether it is served by WP Super Cache generated pages, or fastcgi_cache, again you probably won’t even notice it.

    MU Domain Mapping

    Why are you using “Remote login” option? Have you noticed the javascript call that appears on every post? That means tha for every post visited, another get request with a query string set is made to your server as well. That completly ruins the caching purpose.

    Greetings and once again thank you for your great posts.

  5. Fastcgi_cache

    I never faced any issues with load since I moved from Apache to Nginx. 🙂

    This site is hosted on a dedicated server with 32-CPU cores and 32-GB RAM. We hardly use 10% of server resources most of the time. But, personally, I like to make things as fast as they can be!

    That being said, my primary job is server administration and most of the times I have a remote shell open to this server on my machine. So its easy for me to live with shortcomings of fastcgi_cache solution.

    I will NOT recommend fastcgi_cache to anyone who is not comfortable with linux shell prompt. Apart from cache cleanup, there are few more things that you need to handle via shell.

    As an example, we recently added woocommerce store here and for it to work nicely, I had to add some extras in nginx config to NOT cache shopping cart areas. A plugin like woocommerce may be handling with kind of exceptions with WordPress Caching plugins automatically! In other cases, you can use plugin settings area to make changes safely.

    Remote Login

    We need “Remote Login” for seamless login on mapped domains.

    For example. if I log into rtcamp.com (this site) and go to devilsworkshop.org (another site), I will get auto-logged-in there. We have around 20 sites so this option come handy when moving from a site to another site.

    If I disable domain-mapping, I will need to log-in on each domain again.

    Thanks

    Thanks for your time and valuable feedback. 🙂

    I will be coming up with more tutorials soon. This time focus will be more on theory rather than off-the-shelf config. 🙂

  6. Hi,
    I keep getting the unknown directive error whenever I test my nginx configuration (identical to post). Even if I remove the previous directive, the next one will yell at me. Are all these directives invalid am I just not seeing something?
    I have pasted them here http://paste.ubuntu.com/5575732/

      • Thanks, that did the trick. But I noticed that none of the sub domains are accessible, they all seem to be redirected to xxx.idu-lms.site/index.html, wp-admin is not accessible either. And I have 127.0.0.1 idu-lms.site *.idu-lm.site in my /etc/hosts as well.

        • /etc/hosts doesn’t support wildcards, so you cannot have something like *.idu-lm.site in it.
          For that you can have a DNS server running locally. e.g. bind

          I am not sure you are seeing /index.html and why wp-admin is not accessible.
          On line if($request_uri ~* "(/wp-admin... check if there is any space is missing. wp-admin issue could be related to seeing /index.html.

  7. It seem you use ubuntu + Nginx + FastCG.
    I use CentOS + Nginx (port 80) + Apache (port 8080 as proxy)
    Ready set WildCard DNS for domain. http://any_subdomain.depthe.com OK.
    This is /etc/httpd/conf/httpd.conf
    NameVirtualHost *:8080

    ServerAdmin [email protected]
    ServerName depthe.com
    ServerAlias *.depthe.com
    DocumentRoot /var/www/domains/depthe.com
    ErrorLog logs/depthe.com-error_log
    CustomLog logs/depthe.com-access_log common

    And this is /etc/nginx/conf.d/domain.com.conf
    server {
    listen 80;
    server_name depthe.com *.depthe.com;
    access_log /var/log/nginx/depthe.com.access.log ;
    error_log /var/log/nginx/depthe.com.error.log ;
    location ~ .(js|css|jpg|jpeg|gif)$ {
    root /var/www/domains/depthe.com;
    set $cache_uri $request_uri;
    }
    location / {
    proxy_pass http://130.255.190.119:8080/ ;
    include /etc/nginx/conf.d/proxy.conf;
    }
    }

    When I browse depthe. com/test-subdomain it shows content of “Test subdomain” post
    But http://test-subdomain.depthe.com it shows home page content looking like http://depthe.com

    Please help me edit to make it works correctly.
    I like depthe. com/test-subdomain and http://test-subdomain.depthe.com are same content