WordPress 3.0 Network with nginx and php-fpm backend on Ubuntu 11.04 (server)
Old habits die hard. I still love Firefox, and I’m still in love with apache2, but new times are coming. While I was hosting I needed a solution that could handle sites with different users, to log with different user rights and to secure the code from each other as hard as possible. I achieved this with unix users, 0700/0600 dir/files rights and with apache2-mpm-itk, and it did work like charm. Also, I had at least 2GB memory just for apache2, so it’s memory relish and it’s performance was acceptable for the security.
Things are about to change though, I’m moving my own sites to a small VPS with 512MB RAM and 1GB swap (just for sure). Why this low? Because it has to be enough. And to make this sure, I need switching to nginx + php-fpm, combined with APC from apache2 and xcache.
The host is Ubuntu 11.04 (server). Yes, it could be better, more stable, and 10.04, but I need up-to-date packages, I don’t like complying everything myself.
And the real twist: I need this to work with a WordPress 3.0 Network, with domain mapping plugin and all.
Installation
Add the nginx repository.
add-apt-repository ppa:nginx/stable
apt-get update
Install the packages we need.
Nginx, PHP, PHP-FPM, MySQL – the most needed ones
sudo apt-get install nginx-full php5-fpm php5-cli php5-dev php5-mysql php5-curl php5-gd php5-imagick php5-mcrypt php5-suhosin mysql-server
Suhosin is a security plugin, but it can conflict with lots of application. Be sure I doesn’t ruin yours.
APC
APC is avaliable via PECL, but a developement package is needed for it.
sudo apt-get install php-pear build-essential libpcre3-dev
sudo pecl install apc
Configuration
I remove the comments from the configurations files, so there are just the needs.
Nginx
/etc/nginx/nginx.conf
user www-data;
worker_processes 2;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
}
http {
server_names_hash_bucket_size 64;
sendfile on;
tcp_nopush on;
tcp_nodelay off;
client_max_body_size 64M;
types_hash_max_size 8192;
include /etc/nginx/mime.types;
default_type text/html;
log_format main ‘$remote_addr – $remote_user [$time_local] ‘
‘”$request” $status $body_bytes_sent “$http_referer” ‘
‘”$http_user_agent” “$http_x_forwarded_for”‘ ;
access_log /var/log/nginx/access.log main;
include /etc/nginx/gzip_params;
include /etc/nginx/sites-enabled/*;
}
/etc/nginx/fastcgi_params
fastcgi_connect_timeout 60;
fastcgi_send_timeout 180;
fastcgi_read_timeout 180;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;
fastcgi_intercept_errors on;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param REDIRECT_STATUS 200;
/etc/nginx/gzip_params
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_http_version 1.1;
gzip_comp_level 1;
gzip_proxied any;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/xml+rss;
/etc/nginx/sites-available/default
server {
listen 80;
server_name .domain.com;
access_log /var/log/nginx/domain.com.access.log;
error_log /var/log/nginx/domain.com.error.log;
location / {
root /var/www/;
index index.php index.html index.htm;
if ( $uri ~ \.(ico|gif|jpg|jpeg|png)$ ) {
expires 30d;
}
# WordPress multisite files rule
rewrite ^.*/files/(.*)$ /wp-includes/ms-files.php?file=$1 last;
# WordPress rewrite rules
try_files $uri $uri/ /index.php?q=$uri&$args;
}
location ~ \.php$ {
include /etc/nginx/fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www$fastcgi_script_name;
}
location ~ /\.ht {
deny all;
}
location /nginx_status {
stub_status on;
access_log off;
}
}
The real trick is done be the rewrite rules. After hours of searching and thinking about the perfect re-write of the rewrite rules, I’ve found these solutions on a Ruby (!) forum.
PHP
/etc/php5/fpm/pool.d/www.ini
[www]
listen = 127.0.0.1:9000
listen.allowed_clients = 127.0.0.1
listen.owner = www-data
listen.group = www-data
listen.mode = 0600
user = www-data
group = www-data
pm = dynamic
pm.max_children = 4
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 512
pm.status_path = /status
php_admin_value[open_basedir] = /var/www/
php_admin_value[upload_tmp_dir] = /var/www/tmp/
/etc/php5/fpm/main.conf
pid = /var/run/php5-fpm.pid
error_log = /var/log/php5-fpm.log
log_level = notice
include=/etc/php5/fpm/pool.d/*.conf
Test and conclusion
I did not expect any noticeable difference in performance or in user experience, but I was suprised. While the load and the memory usage lower with a significant value, the speed, the actual, user-based, visitor speed gained at least 300%. And also, there’s virtually no fragmentation in APC, while with apache2… well, it used to be terrible.
Apache2: I’m going to miss you. For 10+ years I have experience with apache and apache2, but there are more important aspects than habits in the world of web servers. The need for apache3 or a really good mpm-event is getting more and more urgent. Until then, I’m going to stick with nginx.
Updates
2011.10.27. Some of the config files have been updated for better performance and eliminated redundancy.