• 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 ^ https://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.
    # https://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.
    	# https://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:

    https://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 15 replies - 31 through 45 (of 58 total)
  • Thread Starter bigsite

    (@bigsite)

    Cleaned up the Codex with the updated set of configs and tried to make it more general-purpose. Removed the initial “broken” server {} block that was posted since a “try_files $uri =404;” line in a \.php$ location {} block is critical unless the person running nginx knows exactly what they are doing. Also, the correct practice is to use upstream {} blocks for setting up fastcgi servers. Having two configs on the same page was also a bit confusing.

    But, other than those issues, the Codex page looks pretty good to me. We just need W3 Total Cache rules and someone to test out the regular WP rules and someone else to see if subdirectory rules work with subdomain as-is. Looking forward to seeing nginx being used more widely now.

    Cool, thanks for cleaning it up, I just wanted to create the page, so someone with more experience could see it.

    I was wondering why with your configuration I can see the new sites localhost/site-1, localhost/site-2, but not the main one? (it was working before creating the network), it shows a blank page without throwing any error. Any clues where to look?

    Thread Starter bigsite

    (@bigsite)

    @pablox – A WSOD indicates an error probably happened somewhere and PHP stopped processing when it encountered the error. Check your server logs since PHP and other errors will show up there. I can only guess as to the cause at this point.

    Be aware that not all plugins are WP multisite/network-aware. WP 3.0 finally forced the issue onto plugin authors so the situation is improving slightly.

    I have an EC2 Ubuntu I am using for testing purposes of nginx and I am running WordPress Multisite w/subdomains and on top domain mapper plugin. The Codex and recommendation here work flawless in all aspect BUT Super Cache.

    As soon as I enable Super Cache after any posting or uploading of theme will cause the blog to throw 500 error. Worst inspecting error.log for nginx and php5-fpm logs show nothing reported so its like I am in the dark. Also whats really odd is other php scripts that relay on php5-fpm are working correctly like nothing happen. Restarting php5-fpm resolves the issue but its really points to some issue with Super Cache that I cant figure out.

    Now I suspect the issue might be just Super Cache issue with nginx but seeing as others in this thread have gotten it work maybe I am missing a step or setting. Any recommendation or idea where I should start looking? I had this problem before trying the nginx configurations here as I was using my own and was hoping maybe this one would resolve my super cache problems.

    Server info:
    Ubuntu Server 10.10 x64
    PHP 5.3.3-1ubuntu9.3 (fpm-fcgi) (built: Jan 12 2011 16:07:41)
    nginx version: nginx/0.8.54

    Update:

    So enabled wp_debug I got the following error:

    Fatal error: Internal Zend error – Missing class information for in /var/www/<domain.com>/public/wp-content/plugins/wp-super-cache/wp-cache-base.php on line 5

    Zuhaib

    Thread Starter bigsite

    (@bigsite)

    @zsiddique – Try reinstalling the WP Super Cache plugin. I always use the latest development version instead of whatever the official release is. If you have used another caching plugin in the past with your site, that can cause strange conflicts with WP Super Cache. I had weird issues with upgrading to WP Super Cache from WP Cache 2 until I completely deleted all files related to WP Cache 2.

    Also, you aren’t running the latest version of PHP.

    @bigsite – As this is a test site I nuked the whole installation (drop tables and files) and started fresh to troubleshoot the issue. The site is 100% stable without super cache. Enable Super Cache and after some traffic it would start to throw the Zend Error.

    Some googling on the Zend error point to possible PHP – APC issue. I have for now disabled APC to see if this resolves the issue. If you have some recommendation on things to check for APC that would be great.

    PHP 5.3.3 is the lastest that comes built with Ubuntu and I have not found any good Ubuntu PPA that provide a latest build. But 5.3.3 is not *that* out of date and I know some web host that have public PHP builds that are older. But I might try to upgrade PHP if their is a strong reason that it will resolve the issue.

    Thread Starter bigsite

    (@bigsite)

    @zsiddique – Build PHP from source. There are lots of examples around on how to build PHP from source for Ubuntu. If you use PHP APC, you want the latest PECL version of PHP APC and the latest version of PHP.

    Thread Starter bigsite

    (@bigsite)

    Looks like the latest development version of WP Super Cache now includes differentiation between http and https served content. I’ve updated the Codex to reflect this significant change.

    Supposedly there is some better mobile theme support (i.e. actual caching for mobile themes instead of the current cache bypass) in the development version of the WP Super Cache plugin as well but is kind of hacky and I’m not seeing any rewrite rule modifications yet.

    Thread Starter bigsite

    (@bigsite)

    We just updated to nginx 1.0.0 and it overwrote the SCRIPT_URI addition to the ‘fastcgi_params’ file. Maybe it wasn’t smart to modify that file. Dropped the change into the global/wordpress-ms-subdir.conf file and that resolved the issue (again).

    This looks much, much more complicated than the solution we’ve been running at blogs.law.harvard.edu for a long time now.

    The short version:

    Nginx sits in front of apache, caching based on simple regex patterns: instructions and my (very simple) plugin here.

    Apache runs wordpress essentially unmodified, except it binds to an internal IP or alternate port. It uses mod_rpaf to see client requests transparently, allowing you to apply ip access controls (LDAP is a biggie for us) at the apache / wordpress level. It knows nothing of the cache, really, except that it should tell the upstream nginx cache to not cache authenticated page requests. We use apc and the apc object cache on the backend wordpress apache with 256meg of shared apc RAM.

    That’s pretty much it. nginx handles caching and serving from the cache. There’s no rewriting trickery, nginx handles compression, cache reaping, flood protection and you can easily mix content from other apps via separate proxy rules.

    Apache is a great app server, easy to tweak, tons of features and when fronted with a lightweight caching proxy like nginx it’s a killer combo. Once you’ve got nginx caching for you, I just can’t see where factoring apache out of the equation is going to get you much. In our setup, apache pretty much just stitches dynamic PHP together when needed – it’s great at that.

    Ah well, different strokes. I’m a big fan of reducing moving parts, though.

    Big fan of nginx here too. Our server barely blips.

    Thread Starter bigsite

    (@bigsite)

    @dan Collis-Puro – Unfortunately it is not possible to do that here. Running Apache would require us to eliminate PHP APC. Also, a number of custom plugins would break. nginx works fine and, really, that part of the setup is a lot simpler than you might think – especially since the creation of the nginx configuration entry in the Codex based heavily on this thread.

    @bigsite: you must mean something other than APC, because APC works great with an apache backend and it’s a big part of our infrastructure.

    I stand by my “simpler is better” assertion. We host around 5 million page views a month on https://blogs.law.harvard.edu with one app server and one DB server because of our nginx proxy cache.

    That sounded like I said it with a sneer – sorry. No offense intended.

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