WordPress 3.4 and older multisite, by default, uses PHP’s readfile()
function to serve static files. This means every static file request will go through PHP. If you are uploading large files then most likely your PHP will crash and will throw an out of memory error. If you are in Apache world where PHP is run using mod_php (without X-Sendfile) then you will curse WordPress-Multisite!
WordPress 3.5 changed this but for old multisite network, rather than creating upgrade script, WordPress decided to maintain backward-compatibility. This article is still relevant for multisite networks created before WordPress 3.5. You will need this, even if you upgrade old-WordPress to latest version.
WPMU_ACCEL_REDIRECT and X-Accel-Redirect
WordPress-Multisite supports X-Accel-Redirect header which can improve performance, specially if you are serving large static files e.g. mp3.
Changes in wp-config.php
Add following line to wp-config.php
:
define('WPMU_ACCEL_REDIRECT', true);
This is only change you will need on WordPress side.
Changes in Nginx Config
Assuming you are following us throughout our WordPress-Nginx series and using conventions documented here…
Add following lines to mark /blogs.dir
directory as internal:
server{ ## inside server block #avoid php readfile() location ^~ /blogs.dir { internal; alias /var/www/example.com/htdocs/wp-content/blogs.dir ; access_log off; log_not_found off; expires max; } }
WordPress-Multisite /files
section with Subdirectories:
server{ ## inside server block location ~ ^(/[^/]+/)?files/(?<rt_file>.+) { try_files /wp-content/blogs.dir/$blogid/files/$rt_file /wp-includes/ms-files.php?file=$rt_file ; access_log off; log_not_found off; expires max; } }
If you have installed WordPress in a subdirectory, modify try_files
path accordingly.
WordPress-Multisite /files
section with Subdomains:
server{ ## inside server block #WPMU Files location ~ ^/files/(.*)$ { try_files /wp-includes/ms-files.php?file=$1 =404; access_log off; log_not_found off; expires max; } }
With above changes, rather than reading complete file, PHP determines only file-system path for request file-url. Then PHP created X-Accel-Redirection header with file-path value which Nginx intercepts, because of blogs.dir
directive. This way file-reading is performed by Nginx.
Problem…
Problem with this approach is, a static file request still reaches PHP. And that PHP file, wp-includes/ms-files.php
, loads WordPress in memory which loads MySQL in memory as well! Sound like quite a lot work for serving static-files.
Ideally, a static file should never put any load on PHP/MySQL at all. This is what we achieve next using Nginx’s map{..}
directive.
Nginx’s map{..} to Handle Static files in WordPress-Multisite
If you look inside wp-content/blogs.dir
folder on a WordPress-multisite setup, you will see for each site, wordpress create a directory to store that sites’ uploaded content. Names of these directory are number like 2, 3, 4, 9 etc. These number are basically site-ids for sites in the WordPress Multisite network.
If Nginx can find the correct numeric site-id for a site, it can serve requested file itself without putting any load on PHP & MySQL.
We use Nginx’s map {..}
section to hold site-names and site-ids pairs as you can see in following example:
map $http_host $blogid { default 0; example.com 1; site1.example.com 2; site1.com 2; }
Using it, Nginx can map a request for file:
http://site1.com/files/2012/09/somefile.png
to file-system path:
/var/www/example.com/htdocs/wp-content/blogs.dir/2/2012/09/somefile.png
Using Nginx-helper WordPress Plugin to generate Maps{..} automatically:
For small networks, you can create map manually. But for large network, you can use Nginx-Helper WordPress plugin to generate Nginx map{..}
automatically.
Once you install Nginx Helper, go to Network-Admin >> Settings >> Nginx
page. Enable Nginx Map feature. It will appear only if you are running WordPress Multisite.
You can either copy-paste map values from text-area directly into map{..} section in nginx config OR include absolute path to map.conf
file like below:
Nginx Config
For WordPress Multisite with subdomains:
map $http_host $blogid { default -999; include /var/www/example.com/htdocs/wp-content/plugins/nginx-helper/map.conf ; } server{ ## inside server block #WPMU Files location ~ ^/files/(.*)$ { try_files /wp-content/blogs.dir/$blogid/$uri /wp-includes/ms-files.php?file=$1 ; access_log off; log_not_found off; expires max; } }
For WordPress Multisite with subdirectories:
map $uri $blogname{ ~^(?<blogpath>/[^/]+/)files/(.*) $blogpath ; } map $blogname $blogid{ default -999; include /var/www/example.com/htdocs/wp-content/plugins/nginx-helper/map.conf ; } server{ ## inside server block location ~ ^(/[^/]+/)?files/(?<rt_file>.+) { try_files /wp-content/blogs.dir/$blogid/files/$rt_file /wp-includes/ms-files.php?file=$rt_file ; access_log off; log_not_found off; expires max; } }
Above nginx-config snippets already have fallback support for WPMU_ACCEL_REDIRECT and X-Accel-Redirect support. So if you are creating maps manually and you forgot to update it for newly created sites, static-file handing will still work.
In fact, technically, you need to always reload Nginx config after a new site created in WordPress-Multisite. But above fallback will ensure static file-handing will work even if you do not reload nginx config.
If you are running a very big network, like WordPress.com, you may put a cron-job to reload Nginx config every hour or so.
Try it! If you get stuck, feel free to use our free support-forum for help.
This is a massive help for our large 5000+ multisite installation. I never managed to get the X-ACCEL-REDIRECT to work with our installation though, it always gives us an error 500 for all files called, but the mapping functionality is brilliant.
Thanks!
@Ed
Glad to know it helped. 🙂
If you are installing fresh WordPress 3.5 you will not need X-ACCEL-REDIRECT, nginx-map and related nginx rules.
https://rtcamp.com/support/topic/nginx-subdomain-multi-site-w3-total-cache-wordpress3-5/
We’ve been going since the 2.x WPMU days unfortunately, has created a few issues with us over time. I don’t think we’re suffering too badly from the fact that X-ACCEL-REDIRECT isn’t working, but the Nginx Map is working well, sites do seem to load more quickly, whether that is a placebo or not is out for discussion.
Cheers,
Ed
If you are upgrading form old version of WordPress you will need above rules.
X-ACCEL-REDIRECT alone helps somewhat but nginx-maps works magically because it removes PHP calls for every static file access. On a large and/or busy size it can do wonder. I guess its already doing wonder for you 😉
Hmmm, it appear that even the mapping wasn’t working on our setup. The use of $http_host in the map would provide a port number of 8088 as we use Varnish on port 80. I changed it to $host and it seems to have stopped showing any wp-includes/ms-files.php references appearing in the PHP status output, I assume that means it’s now working and wasn’t before?
Also, regarding the define(‘WPMU_ACCEL_REDIRECT’, true); problem, would you have any idea why I get “rewrite or internal redirection cycle while internally redirecting to “/blogs.dir/7915/files/2012/04/bla.jpg” while reading response header from upstream” when I enable it along with or without your blogs.dir/ rewrite rules?
Cheers,
Ed
@Ed
Sorry but I cannot answer any question if you are using Varnish.
I will recommend removing Varnish. Nginx can handle static content pretty well itself. (source)
Varnish is useful when used in-front of Apache.
We use it as we have node, nginx and others running over multiple servers. Any idea about the WPMU_ACCEL_REDIRECT problem?
Ed
Do these directions also work for wordpress MU using subdirectories?
You can try following articles from this series:
Hi Rahul.
If I use a CDN (Cloudfront), do you still recommend NGINX Map???
It has any sense?
Regards.
Nginx map will benefit for CDN miss i.e. less than 1% of requests generally.
You can skip nginx map for now but add it later if you see spikes in server load.
This was also a very useful tutorial. The only snag is www to non www. The primary domain maps fine, the ‘non www’ mapped domains work fine too. But the mapped domains with ‘www’ still get directed to wp-signup. Still working on it!
You need to redirect www requests to www version.
Check – https://rtcamp.com/tutorials/nginx/www-non-www-redirection/ and more specifically this comment thread on that post