nginx and php on redhat

The Apache HTTP Server can use a lot of resources, especially on a well-trafficked site. For many admins nginx has been the answer. An alternative would be pairing nginx with php-fpm (PHP FastCGI Process Manager). More below.

While the jury is still out in my mind, an increasing number of well-respected sites have been switching from Apache to nginx as their web server. This makes sense because nginx’s design makes it much more efficient than Apache when it comes to serving up static content or proxying traffic to and from other web servers.

But nginx does not support serving up php pages natively. Doing that requires setting up a proxy to a service that can. In some cases this will be an Apache HTTP server, in others it will be a process manager like php-fpm.

Most php software was originally developed with Apache in mind and fundamentally relies on its many capabilities. For example WordPress Multisite’s heavy reliance on rewrite rules within its own .htaccess file. Nginx cannot process .htaccess files but instead requires that the rules that might be contained in one be reproduced in its own cryptic configuration language (someone has described it as sort of a cross between C and Perl). As a result, it’s likely that the easiest way to get things up and running is to use Apache, and maybe employ nginx as an external proxy/load-balancer. Having said that I’ve recently been experimenting with nginx + php-fpm for simple, non-multisite, installations of WordPress.

Think of php-fpm as an application server for php scripts. Instead of serving up jsp’s (Java Server Pages), it is used to publish php applications. Although you could point users directly to a php-fpm instance, the recommended way of doing things is to use a web server proxy frontend as a go-between. That’s where nginx comes in. Optimized as an HTTP proxy under high load, low resource, conditions, nginx is a good fit for the job.

But will the total resource usage really be any better than running php applications like WordPress on Apache, without the intervening FastCGI layer? That’s a good question, but before it can be answered I need to get nginx and php-fpm installed and configured.

The following material covers doing just that on Fedora 19, but can also be used as a guide for any Red Hat release.

1. The biggest difference between Fedora and Red Hat Enteprise Linux when it comes to nginx is that, like php itself, the best route is not to use the standard RHEL repositories (Fedora’s repository is up-to-date). Instead you’ll want to use a repository maintained by the nginx project itself to get the latest stable package. The link is here. The nginx.repo file, edited in accordance with the vendor’s instructions, will look like this:

[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/rhel/6/$basearch/
gpgcheck=0
enabled=1

2. Use yum to install both nginx and php-fpm, this will ensure that all dependencies are correctly resolved (and updates applied) during the installation process.

yum install nginx
yum install php-fpm

3. If you’re replacing Apache with nginx, one of the first things to do is to review your filesystem permissions. In all likelihood many folders and files under what was your DocumentRoot will be owned or at permissioned to allow access by the system “apache” user. While php-fpm already runs as that user, nginx does not. If you have any non-php content being served up you may have to change the owner from the apache to the nginx system user.

4. Edit /etc/nginx/nginx.conf to match your environment details:

user  nginx;
worker_processes  1;
error_log  /var/log/nginx/error.log;
pid        /run/nginx.pid;
events {
    worker_connections  1024;
}
http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    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;
    sendfile        on;
    keepalive_timeout  65;
    include /etc/nginx/conf.d/*.conf;
    index   index.php index.html index.htm;
    server {
        listen       80;
        server_name  mysite.example.com www2.example.com;
        root         /var/www/html;
        access_log  /var/log/nginx/mysite.access.log  main;
        location / {
        }
        error_page  404              /404.html;
        location = /40x.html {
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
        }
        location ~ \.php$ {
            root           /var/www/html;
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            include        fastcgi_params;
        }
    }
    server {
        listen       443;
        server_name  mysite.example.com www2.example.com;
        root         /var/www/html;
        ssl                  on;
        ssl_certificate      /etc/pki/tls/certs/example.com.crt;
        ssl_certificate_key  /etc/pki/tls/private/example.com.key;
        ssl_session_timeout  5m;
        ssl_protocols TLSv1;
        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers   on;
        access_log  /var/log/nginx/ssl_mysite.access.log  main;
        location / {
        }
        location ~ \.php$ {
            root           /var/www/html;
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
            include        fastcgi_params;
        }
    }
}

Two important points here: (1) make sure the filesystem paths set in various places reflect reality (in this example I’m using “/var/www/html” for the “root” locations, “/etc/pki/tls/certs” for the SSL cert location); (2) the fastcgi_param for SCRIPT_FILENAME in the example likewise reflects the reality my system lives in.

4. In my environment it wasn’t necessary (yet) to make any changes to the php-fpm conf files under /etc. Your mileage may vary as default configurations for many services differ widely among Linux distributions and within versions of those distributions.

5. Enable and start both services:

systemctl enable php-fpm
systemctl start php-fpm
systemctl enable nginx
systemctl start nginx

Check to see that all is well by using “systemctl status” against each service.

6. It’s not enough to test with a simple php script, be careful to exercise all the functions of whatever software you’re using. For example I found that my test script worked wonderfully, as did a basic install of WordPress — but that configuring WordPress to Multisite mode broke things badly. For the record, here’s my simple 3 line phpinfo.php script:

<html>
<?php phpinfo();?>
</html>

7. Doing “fancy stuff” like publishing content in folders off the DocumentRoot (like “/usr/share/phpMyAdmin” or “/var/www/cgi-bin”, you know, the kind of thing we used to call best practices) is not easy to do in nginx. What might take a single line in httpd.conf can often take many more in nginx.conf. Keep that in mind when re-architecting your infrastructure. That’s because at its core nginx is really not a complete replacement for Apache, and as currently designed, cannot be.

Further Reading:

How to Configure Single and Multiple WordPress Site Settings with Nginx (Debian/Ubuntu centric)

How to install WordPress Multisite on CentOS VPS with Nginx

Multi Site WordPress rewrite rules in Nginx

This entry was posted in System Administration on by .

About phil

My name is Phil Lembo. In my day job I’m an enterprise IT architect for a leading distribution and services company. The rest of my time I try to maintain a semi-normal family life in the suburbs of Raleigh, NC. E-mail me at philipATlembobrothersDOTcom. The opinions expressed here are entirely my own and not those of my employers, past, present or future (except where I quote others, who will need to accept responsibility for their own rants).