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:
- Checklist For Perfect WordPress-Nginx Setup – It will help you verify if your caching will work after PHP/MySQL crash.
- Nginx Maps for Better Static File Handling in WordPress-Multisite – useful for multisite created before WordPress 3.5 released
- WordPress-Nginx tutorials
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 seeSupercache 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 ofserver_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). Usingdefine('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 useNginx 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.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:
Inside “wordpress-network-super-cache.conf” i have this block:
And inside “fastcgi.conf” i have:
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”).
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….
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 nginxmap{..}
.Thanks for giving this idea though. I will definitely dig deeper into this… 🙂
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.
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. 🙂
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/
There should be a space between
if
and(
always.So change all
if(
toif (
and problem will be solved.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 have127.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 whywp-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
.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