Static Files Handling + Nginx-Helper

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.

Link: WordPress-Nginx tutorials

13 responses to “Static Files Handling + Nginx-Helper”

  1. 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!

      • 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.

  2. 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.

  3. 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!