Support » Networking WordPress » nginx php-fpm PHP APC WordPress multisite (subdirectory) WP Super Cache

  • Resolved bigsite

    (@bigsite)


    Hooray! I’ve got WordPress multisite (subdirectory) working with nginx 0.8.54 with the static gzip module, php-fpm (FastCGI Process Manager – PHP 5.3.5), PHP APC (opcode cache – 3.1.6 or 3.1.7), and WP Super Cache (latest dev). I also made my nginx/php-fpm configuration generic enough that most people should be able to copy and paste and have it work for them as well – WITHOUT security vulnerabilities or other major issues. I spent a good week on planning the configuration and then a few hours working out the bugs. I had significant help from people on the nginx IRC channel. Serious props to those good folks!

    If you don’t know what nginx is, it is an alternate web/proxy server to Apache that is gaining in popularity. If you don’t know what php-fpm is, it is a server whose sole purpose in life is to serve up PHP responses to FastCGI requests made by a web server. At any rate, I’ve broken up the nginx configuration into five distinct files and attempted to heavily comment them to make each option easier to understand. I also made a best-effort attempt to follow “best practices” for nginx configurations. This configuration works but could use some minor tweaks. First up is /etc/nginx/nginx.conf:

    # Generic startup file.
    user {user} {group};
    worker_processes  3;
    
    error_log  /var/log/nginx/error.log;
    pid        /var/run/nginx.pid;
    
    # Keeps the logs free of messages about not being able to bind().
    #daemon     off;
    
    events {
    	worker_connections  1024;
    }
    
    http {
    #	rewrite_log on;
    
    	include mime.types;
    	default_type       application/octet-stream;
    	access_log         /var/log/nginx/access.log;
    	sendfile           on;
    #	tcp_nopush         on;
    	keepalive_timeout  3;
    #	tcp_nodelay        on;
    #	gzip               on;
    	index              index.php index.html index.htm;
    
    	# Upstream to abstract backend connection(s) for PHP.
    	upstream php {
            	server unix:/tmp/php-fpm.sock;
    #       	server 127.0.0.1:9000;
    	}
    
    	include sites-enabled/*;
    }

    Now, you’ll observe that this is a bit different from most nginx.conf files. I opted to follow the Ubuntu/Debian method of declaring enabled sites for maximum flexibility – using ‘sites-available’ to store a config and then symlink to the config file from ‘sites-enabled’. Here’s my site configuration:

    # Redirect everything to the main site.
    server {
    	listen 80 default;
    	server_name *.mysite.com;
    	rewrite ^ http://mysite.com$request_uri permanent;
    }
    
    server {
    	server_name mysite.com;
    	root /var/www;
    
    	include global/restrictions.conf;
    	include global/wordpress-ms-subdir.conf;
    }

    If you look around the Internet, you can find various WordPress configs for nginx. Most of them drop stuff into the ‘server’ block, but I figure some people might want to reuse the same logic over and over. As long as the blog is in the root of the site, this is no problem. I created a ‘global’ subdirectory (/etc/nginx/global/) to add extra rules like WP stuff. Here’s ‘global/restrictions.conf’:

    # Global restrictions configuration file.
    # Designed to be included in any server {} block.</p>
    location = /favicon.ico {
    	log_not_found off;
    	access_log off;
    }
    
    location = /robots.txt {
    	allow all;
    	log_not_found off;
    	access_log off;
    }
    
    # Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac).
    location ~ /\. {
    	deny all;
    	access_log off;
    	log_not_found off;
    }

    And now for the basic WordPress rules in ‘global/wordpress-ms-subdir.conf’:

    # WordPress multisite subdirectory rules.
    # Designed to be included in any server {} block.
    
    # This order might seem weird - this is attempted to match last if rules below fail.
    # http://wiki.nginx.org/HttpCoreModule
    location / {
    	try_files $uri $uri/ /index.php?$args;
    }
    
    # Add trailing slash to */wp-admin requests.
    rewrite /wp-admin$ $scheme://$host$uri/ permanent;
    
    # Directives to send expires headers and turn off 404 error logging.
    location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
    	expires 24h;
    	log_not_found off;
    }
    
    # Pass uploaded files to wp-includes/ms-files.php.
    rewrite /files/$ /index.php last;
    
    # For multisite:  Use a caching plugin/script that creates symlinks to the correct subdirectory structure to get some performance gains.
    set $cachetest "$document_root/wp-content/cache/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/cache/ms-filemap/${host}${uri} break;
    }
    
    if ($uri !~ wp-content/plugins) {
    	rewrite /files/(.+)$ /wp-includes/ms-files.php?file=$1 last;
    }
    
    # Uncomment one of the lines below for the appropriate caching plugin (if used).
    # include global/wordpress-ms-subdir-wp-super-cache.conf;
    # include global/wordpress-ms-subdir-w3-total-cache.conf;
    
    # Rewrite multisite '.../wp-.*' and '.../*.php'.
    if (!-e $request_filename) {
    	rewrite ^/[_0-9a-zA-Z-]+(/wp-.*) $1 last;
    	rewrite ^/[_0-9a-zA-Z-]+(/.*\.php)$ $1 last;
    }
    
    # Pass all .php files onto a php-fpm/php-fcgi server.
    location ~ \.php$ {
    	# Zero-day exploit defense.
    	# http://forum.nginx.org/read.php?2,88845,page=3
    	# Won't work properly (404 error) if the file is not stored on this server, which is entirely possible with php-fpm/php-fcgi.
    	# Comment the 'try_files' line out if you set up php-fpm/php-fcgi on another machine.  And then cross your fingers that you won't get hacked.
    	try_files $uri =404;
    
    	fastcgi_split_path_info ^(.+\.php)(/.+)$;
    	include fastcgi_params;
    	fastcgi_index index.php;
    	fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    #	fastcgi_intercept_errors on;
    	fastcgi_pass php;
    }

    For WP Super Cache, the ‘global/wordpress-ms-subdir-wp-super-cache.conf’ file should look like this:

    # WP Super Cache rules.
    # Designed to be included from a 'wordpress-ms-...' configuration file.
    
    # Enable detection of the .gz extension for statically compressed content.
    # Comment out this line if static gzip support is not compiled into nginx.
    gzip_static on;
    
    set $supercacheuri "";
    set $supercachefile "$document_root/wp-content/cache/supercache/${http_host}${uri}index.html";
    if (-e $supercachefile) {
    	set $supercacheuri "/wp-content/cache/supercache/${http_host}${uri}index.html";
    }
    
    # If this is a POST request, pass the request onto WordPress.
    if ($request_method = POST) {
    	set $supercacheuri "";
    }
    
    # If there is a query string, serve the uncached version.
    if ($query_string) {
    	set $supercacheuri "";
    }
    
    # Logged in users and those who have posted a comment get the non-cached version.
    if ($http_cookie ~* comment_author_|wordpress_logged_in|wp-postpass_) {
    	set $supercacheuri "";
    }
    
    # Mobile browsers get the non-cached version.
    # Wastes CPU cycles if there isn't a mobile browser WP theme for the site.
    if ($http_x_wap_profile) {
    	set $supercacheuri "";
    }
    
    if ($http_profile) {
    	set $supercacheuri "";
    }
    
    if ($http_user_agent ~* (2.0\ MMP|240x320|400X240|AvantGo|BlackBerry|Blazer|Cellphone|Danger|DoCoMo|Elaine/3.0|EudoraWeb|Googlebot-Mobile|hiptop|IEMobile|KYOCERA/WX310K|LG/U990|MIDP-2.|MMEF20|MOT-V|NetFront|Newt|Nintendo\ Wii|Nitro|Nokia|Opera\ Mini|Palm|PlayStation\ Portable|portalmmm|Proxinet|ProxiNet|SHARP-TQ-GX10|SHG-i900|Small|SonyEricsson|Symbian\ OS|SymbianOS|TS21i-10|UP.Browser|UP.Link|webOS|Windows\ CE|WinWAP|YahooSeeker/M1A1-R2D2|iPhone|iPod|Android|BlackBerry9530|LG-TU915\ Obigo|LGE\ VX|webOS|Nokia5800)) {
    	set $supercacheuri "";
    }
    
    if ($http_user_agent ~* (w3c\ |w3c-|acs-|alav|alca|amoi|audi|avan|benq|bird|blac|blaz|brew|cell|cldc|cmd-|dang|doco|eric|hipt|htc_|inno|ipaq|ipod|jigs|kddi|keji|leno|lg-c|lg-d|lg-g|lge-|lg/u|maui|maxo|midp|mits|mmef|mobi|mot-|moto|mwbp|nec-|newt|noki|palm|pana|pant|phil|play|port|prox|qwap|sage|sams|sany|sch-|sec-|send|seri|sgh-|shar|sie-|siem|smal|smar|sony|sph-|symb|t-mo|teli|tim-|tosh|tsm-|upg1|upsi|vk-v|voda|wap-|wapa|wapi|wapp|wapr|webc|winw|winw|xda\ |xda-)) {
    	set $supercacheuri "";
    }
    
    # Stop processing if the supercache file is valid.
    if ($supercacheuri) {
    	rewrite ^ $supercacheuri break;
    }

    If you don’t have the static gzip module compiled in, be sure to comment out the correct line.

    If you are feeling adventuresome and want to squeak out a little tiny bit more performance, use a custom script like this and run it (and needs to be run after each new blog is created after that):

    <?php
    	require_once "wp-load.php";
    
    	@ini_set('display_errors', 'On');
    	nocache_headers();
    
    	$blogs = $wpdb->get_results($wpdb->prepare("SELECT blog_id, domain, path FROM " . $wpdb->blogs . " WHERE site_id = %d AND public = '1' AND archived = '0' AND mature = '0' AND spam = '0' AND deleted = '0' AND blog_id <> 1 AND last_updated <> '0000-00-00 00:00:00'", $wpdb->siteid));
    	if ($blogs)
    	{
    		// Generate new symbolic links for uploaded files for each blog.
    		foreach ($blogs as $blog)
    		{
    			$path = "/path/to/root/wp-content/cache/ms-filemap/" . $blog->domain;
    			if (!is_dir($path))  @mkdir($path, 0777, true);
    			$path .= $blog->path;
    			$path = substr($path, 0, -1);
    			if (!is_dir($path))  symlink("/path/to/root/wp-content/blogs.dir/" . $blog->blog_id . "/", $path);
    		}
    	}
    ?>

    The PHP script generates symbolic links from the names of the blogs to the numeric (ID) representation of each blog. Then, nginx doesn’t pass requests for uploaded files to php-fpm. If you don’t plan on doing this, comment out the relevant rules to save a few CPU cycles per request. Also, if you don’t have a mobile site WP theme, comment out the relevant WP Super Cache rules to save a lot of CPU cycles.

    A couple last notes: This whole setup assumes that the root of the site is the blog and that all files that will be referenced reside on the host. If you put the blog in a subdirectory such as /blog, then the rules will unfortunately have to be modified to handle that situation. Perhaps someone can take these rules and make it possible to, for instance, use a:

    set $wp_subdir “/blog”;

    Directive in the main ‘server’ block and have it automagically apply to the generic WP rules.

    I’m also considering splitting the WordPress config into ‘precache’ and ‘postcache’ pieces so that I can inject additional custom rules if I want to either override the cache or to just do custom WP stuff before the \.php$ line.

    I didn’t write a W3 Total Cache equivalent for the WP Super Cache. If someone does create one, put it in this thread or create a new thread and reference this thread.

    Also, these rules likely won’t work for multisite in subdomain mode.

    Finally, if you are in the market for nginx, you are probably running into scaling issues. You might want to look at various articles on scaling WordPress like:

    http://weblogtoolscollection.com/archives/2010/03/27/scaling-wordpress-part-1-using-mysql-replication-and-hyperdb/

    nginx and php-fpm only give a moderate boost in performance, so you might not like the results. PHP APC and WP Super Cache (or any of the other caching plugins) have much more impressive results.

    I’m kind of hoping the WP folks take note of this thread and integrate this approach into the WP core installer.

Viewing 13 replies - 46 through 58 (of 58 total)
  • @dan Collis-Puro – I understand that and use that combination on personal servers without any issues. We definitely tried Apache + PHP + PHP APC first, but there were some really bizarre things going on with that combination on our specific setup. Turning off PHP APC resolved the issues, turning it back on resulted in the issues coming back. So, by process of elimination, PHP APC was definitely the cause. Switching to nginx and php-fpm is the only way we’ve found that allows us to run PHP APC without nearly as many issues on our specific setup. We still get the occasional hiccup but it isn’t nearly as bad as it was and we’re relatively happy at the moment.

    Hi, does anyone have an example config where WordPress is located in a subdirectory like /blog?
    I’ve been scratching my head all night trying to get it to work but I must be doing something wrong…

    This would be for a dedicated server with only one WordPress blog, right now I’m using these IF based rules, I simply call them from my main nginx.conf with a “location /blog/ {include blog.conf}” rule…

    http://forum.slicehost.com/comments.php?DiscussionID=2087

    I’m running WP Super Cache v0.9.9.9
    nginx Compatibility Plugin (PHP5) v0.2.5
    nginx v1.0.2 /w php-fpm & php-xcache

    Ok, I’ve done some more testing.
    The WP Super Cache files are actually sent properly when guests are visiting the site now.
    But if I try logging in I get a 404 error when trying to view a cached page.

    Here are links to Pastebins of my config (note the changes done because my WP is located under /blog)
    nginx.conf: http://pastebin.com/9yRbkNag
    wordpress-wp-super-cache.conf: http://pastebin.com/pvWrYi7Y

    Here below are two nginx debug logs, the first is the guest visiting a cached page, it works fine then. Cached content is served directly by nginx…

    The second log shows me when I’m logged into WP, I get a 404 visiting the same cached page, it can be seen that I’m getting redirected to a non-existant index.php file, but I don’t understand why this is not processed by the WP index.php instead of being sent directly… (I guess I must be missing some argument or something?)

    ——NOT LOGGED IN——
    ACCESS LOG:
    192.168.0.1 – – [11/Jun/2011:17:02:46 +0200] “GET /blog/224/slow-system-performance-when-copying-large-files-in-xp-x64-server-2003-x64/ HTTP/1.1” 200 8888 “-” “Mozilla/5.0 (Windows NT 5.2; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.14 Safari/535.1” “-“

    ERROR LOG DEBUG:
    2011/06/11 17:02:46 [notice] 5831#0: *877 "/blog/wp-admin$" does not match "/blog/224/slow-system-performance-when-copying-large-files-in-xp-x64-server-2003-x64/", client: 192.168.0.1, server: www.techspot.com, request: "GET /blog/224/slow-system-performance-when-copying-large-files-in-xp-x64-server-2003-x64/ HTTP/1.1", host: "www.techspot.com"
    2011/06/11 17:02:46 [notice] 5831#0: *877 "comment_author_|wordpress_logged_in|wp-postpass_" does not match "wp-settings-3=editor%3Dtinymce%26hidetb%3D1%26m0%3Do%26m1%3Do%26m2%3Dc%26m3%3Dc%26m4%3Dc%26m5%3Dc%26m6%3Dc%26m7%3Do%26urlbutton%3Durlfile; wp-settings-time-3=1307791463; wordpress_test_cookie=WP+Cookie+check; __qca=P0-140701392-1307791437086; __utmz=150184883.1307791437.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __csref=http%3A%2F%2Fwww.techspot.com%2Fblog%2F; PHPSESSID=amidaj1ibo8jrtudir3ro2op65; OAID=eea744cbebb14f9b0eafbd910c9ad95d; __utma=150184883.1830416333.1307791437.1307798802.1307804529.3; __utmc=150184883; __utmb=150184883.1.10.1307804529; _chartbeat2=2wtn5ciwhur68ycf; __cst=e8b136d46eff2fb9; __csv=40d5006bb94c8f3d|0; __csnv=866a21ee5c0ec749; __ctl=40d5006bb94c8f3d1", client: 192.168.0.1, server: www.techspot.com, request: "GET /blog/224/slow-system-performance-when-copying-large-files-in-xp-x64-server-2003-x64/ HTTP/1.1", host: "www.techspot.com"
    2011/06/11 17:02:46 [notice] 5831#0: *877 "^" matches "/blog/224/slow-system-performance-when-copying-large-files-in-xp-x64-server-2003-x64/", client: 192.168.0.1, server: www.techspot.com, request: "GET /blog/224/slow-system-performance-when-copying-large-files-in-xp-x64-server-2003-x64/ HTTP/1.1", host: "www.techspot.com"
    2011/06/11 17:02:46 [notice] 5831#0: *877 rewritten data: "/blog/wp-content/cache/supercache/www.techspot.com/blog/224/slow-system-performance-when-copying-large-files-in-xp-x64-server-2003-x64/index.html", args: "", client: 192.168.0.1, server: www.techspot.com, request: "GET /blog/224/slow-system-performance-when-copying-large-files-in-xp-x64-server-2003-x64/ HTTP/1.1", host: "www.techspot.com"
    --------LOGGED IN------
    ACCESS LOG:
    192.168.0.1 - - [11/Jun/2011:16:59:11 +0200] "GET /blog/224/slow-system-performance-when-copying-large-files-in-xp-x64-server-2003-x64/ HTTP/1.1" 404 4215 "-" "Mozilla/5.0 (Windows NT 5.2; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.14 Safari/535.1" "-"
    
    ERROR LOG DEBUG:
    2011/06/11 16:59:11 [notice] 5831#0: *858 "/blog/wp-admin$" does not match "/blog/224/slow-system-performance-when-copying-large-files-in-xp-x64-server-2003-x64/", client: 192.168.0.1, server: www.techspot.com, request: "GET /blog/224/slow-system-performance-when-copying-large-files-in-xp-x64-server-2003-x64/ HTTP/1.1", host: "www.techspot.com"
    2011/06/11 16:59:11 [notice] 5831#0: *858 "comment_author_|wordpress_logged_in|wp-postpass_" matches "wp-settings-3=editor%3Dtinymce%26hidetb%3D1%26m0%3Do%26m1%3Do%26m2%3Dc%26m3%3Dc%26m4%3Dc%26m5%3Dc%26m6%3Dc%26m7%3Do%26urlbutton%3Durlfile; wp-settings-time-3=1307791463; wordpress_test_cookie=WP+Cookie+check; wordpress_logged_in_8e18665c8a3d51ae0c46bce33a7db0f9=Per+Hansson%7C1307973397%7Cd976f9534bac73ff37d767ff5d505749; __qca=P0-140701392-1307791437086; __utmz=150184883.1307791437.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __csref=http%3A%2F%2Fwww.techspot.com%2Fblog%2F; PHPSESSID=amidaj1ibo8jrtudir3ro2op65; OAID=eea744cbebb14f9b0eafbd910c9ad95d; __utma=150184883.1830416333.1307791437.1307791437.1307798802.2; __utmc=150184883; _chartbeat2=2wtn5ciwhur68ycf; __cst=e8b136d46eff2fb9; __csv=40d5006bb94c8f3d|0; __csnv=cd31f2be9e5e17f9", client: 192.168.0.1, server: www.techspot.com, request: "GET /blog/224/slow-system-performance-when-copying-large-files-in-xp-x64-server-2003-x64/ HTTP/1.1", host: "www.techspot.com"
    2011/06/11 16:59:11 [error] 5831#0: *858 "/var/www/html/blog/224/slow-system-performance-when-copying-large-files-in-xp-x64-server-2003-x64/index.php" is not found (2: No such file or directory), client: 192.168.0.1, server: www.techspot.com, request: "GET /blog/224/slow-system-performance-when-copying-large-files-in-xp-x64-server-2003-x64/ HTTP/1.1", host: "www.techspot.com"

    @per Hansson – Your configuration is really screwy. Most obvious problem is that you can’t have ‘try_files’ and ‘if’/’rewrite’ in the same ‘location’ block. They are two separate modules that can’t be mixed. See the official ‘IfIsEvil’ Nginx documentation, which barely covers this rather critical topic.

    @bigsite

    Recently I upgrade WP Super Cache to development version. It has a debug mode in this version so I can see what WP Super Cache does. Then I just found this problem:

    If someone sets his own domain in Domain Mapping, Images uploaded by users won’t bypass PHP. Here’s the log:

    07:56:36 /files/2011/05/student-work-s2-02.jpg supercache dir: /srv/www/mysite.tld/public_html/wp-content/cache/supercache/customdomain.tld/files/2011/05/student-work-s2-02.jpg/
    07:56:36 /files/2011/05/student-work-s2-02.jpg No wp-cache file exists. Must generate a new one.

    Any good idea?

    UPDATE: #1. Later I went to /srv/www/mysite.tld/public_html/wp-content/cache/supercache/customdomain.tld/, actually no folder named files was created.

    #2. I’ve enabled Domain Mapping plugins in WP Super Cache Plugin tab.

    PHP is used to serve images unfortunately. On my site I added rewrite rules to bypass those and serve the files directly but I had to add one rule per site so it’s only useful if you’re running a network that doesn’t allow users to create new blogs.

    @donncha:

    I got it, I’m running a multisite instance for some of my friends. Could you share your rules? Thanks.

    They’re mod_rewrite rules, not Nginx ones but they look like this:

    RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
    RewriteRule ^files/(.+) wp-content/blogs.dir/X/files/$1 [L]

    Thanks Donncha, I converted it to nginx version:

    if ($http_host ~* domain.tld) {
      rewrite ^/files/(.+) /wp-content/blogs.dir/X/files/$1 break;
    }

    Add this before $cachetest in wp-ms-subdir.conf, it works but I think there should be a better and more effective method.

    anyone using a sitemap plugin for a multisite multidomain nginx config? None that I’ve tried created the sitemap…

    Thanks for any info.

    great guide bigsite thank you but i just wonder what is this for?

    # Upstream to abstract backend connection(s) for PHP.
    	upstream php {
            	server unix:/tmp/php-fpm.sock;
    #       	server 127.0.0.1:9000;

    i am using php-fpm but i have looked at /tmp/ folder and there is no php-fpm.sock in there.

    Greetings.

    Here is my version for the PHP script that generates symbolic links from the names of the blogs to the numeric (ID) representation of each blog.
    Just place this script in the WordPress installation root and run it each time you had a new site/blog to your multisite.

    <?php
    require_once "wp-load.php";
    
    @ini_set('display_errors', 'On');
    nocache_headers();
    
    $blogs = $wpdb->get_results($wpdb->prepare("SELECT blog_id, domain, path FROM " . $wpdb->blogs . " WHERE site_id = %d AND archived = '0' AND mature = '0' AND spam = '0' AND deleted = '0' AND blog_id <> 1 AND last_updated <> '0000-00-00 00:00:00'", $wpdb->siteid));
    if ($blogs)
    {
      // Generate new symbolic links for uploaded files for each blog.
      foreach ($blogs as $blog)
      {
        $path = ABSPATH."wp-content/cache/ms-filemap/" . $blog->domain;
        //if (!is_dir($path))  @mkdir($path, 0777, true);
        $path .= $blog->path;
        $path = substr($path, 0, -1);
        if (!is_dir($path))  symlink(ABSPATH."wp-content/blogs.dir/" . $blog->blog_id . "/", $path);
      }
    }
    ?>

    Warning: please don’t put “ms-filemap/” inside “cache/” directory. If you use WP Super Cache and press “Delete Cache on All Blogs”, all your uploaded images will be deleted. Instead, place “ms-filemap/” inside “wp-content/”.
    So, if you are using Bigsite’s excellent approach, wherever it says “wp-content/cache/ms-filemap/”, replace it with “wp-content/ms-filemap/” and you should be good (including the script in my previous post, right above).

    References:
    http://wordpress.org/support/topic/plugin-wp-super-cache-major-bug-network-deactivating-deletes-files

Viewing 13 replies - 46 through 58 (of 58 total)
  • The topic ‘nginx php-fpm PHP APC WordPress multisite (subdirectory) WP Super Cache’ is closed to new replies.