WordPress 3.0 Network with nginx and php-fpm backend on Ubuntu 11.04 (server)

Smaller machine with better response time and a lot smaller memory footprint? Why not?


  1. Installation
    1. Nginx, PHP, PHP-FPM, MySQL – the most needed ones
    2. APC
  2. Configuration
    1. Nginx
      1. /etc/nginx/nginx.conf
      2. /etc/nginx/fastcgi_params
      3. /etc/nginx/gzip_params
      4. /etc/nginx/sites-available/default
    2. PHP
      1. /etc/php5/fpm/pool.d/www.ini
      2. /etc/php5/fpm/main.conf
    3. Test and conclusion
    4. Updates

    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.