ciplogic.com
Live with grace. Write superb software.

How do you migrate an existing Apache server, to a brand new nginx installation for several websites that use PHP? This is a simple tutorial into changing an Apache installation into a nginx one, without having to change your existing websites.

nginx is a server that scales far better compared to apache running on the same hardware. The tutorial is not super CentOS specific, but all the commands were run on a CentOS.

The Apache server that was migrated, namely this blog, has several virtual hosts, that are all running PHP, some of them Joomla websites. The plan is to take them as they are, and have them available externally the same way as before, using the same virtual host names, the same folder locations, with the same users assigned to them.

The reason is that if we screw up something in the process, we can just revert to our old proven Apache, by just restarting the Apache service and shutting down nginx. Also we can minimize the downtime, since if done right it should be in the end just shutting down apache and starting nginx, but if it doesn't work we can quickly go back to serving the files with Apache until we figure out what is going on.

While it is simple, it is a pretty long read, so grab your coffee, and hack away:

 1. Install nginx

This is as simple as running:

yum install nginx

Make sure the /etc/nginx/conf.d/default.conf has the paths pointing to /var/www/html, or whatever was the default site for your Apache configuration. (In my case it was /var/www/blog).

OK, next

2. Enable PHP processing

Unlike Apache, the PHP processing does not take place in the same process as nginx via modules, since nginx doesn't supports modules except at compile time. That's why having a service to do the PHP rendering is required.

Since I already have PHP installed for my apache installation, most of the packages are already installed, so I only needed the FastCGI integration to get it running under nginx.

yum install php55w-fpm

If you're doing a new installation you would need to fetch also cli and common packages, and whatever other libraries you would need (like gd or whatever else your web application requires).

Now let's add the PHP processing via php-fpm. They should look something like this:

location ~ \.php$ {
    root           /var/www/blog;
    try_files $uri =404;
    fastcgi_pass   127.0.0.1:9000;
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    include        fastcgi_params;
}

Don't just uncomment the configuration that is there, since it will be prone to attacks, and it won't work out of the box either.

Furthermore since Joomla uses a pretty messed up path segment processing to pass its arguments (e.g. /index.php/blog/96-sun-misc that Joomla uses vs /index.php?category=blog&article=96-sun-misc), you will need to add another rule into the root location of your server:

The line itself looks like:

try_files $uri $uri/ /index.php?$args;

And added in the root location will look in the end about like this:

location / {
    root   /var/www/blog;
    index  index.html index.htm index.php;
    try_files $uri $uri/ /index.php?$args;
}

 

Now because we're rewriting the URLs we also need to take special care for our Joomla sites that we don't execute php files from writable folders. Thus we need to add after the root location the following:

# deny running scripts inside writable directories
location ~* /(images|cache|media|logs|tmp)/.*\.(php|pl|py|jsp|asp|sh|cgi)$ {
    return 403;
    error_page 403 /403_error.html;
}

Basically php-fpm will do the rendering of the PHP pages, and nginx will just proxy them. For all the other resources nginx itself will serve them. Neat!

We're almost done:

3. Use the apache user

Since we want to reuse the same deployment layout, we will preserve the users and folders. Thus we need to tell nginx to run as the `apache` user.

Edit /etc/nginx/nginx.conf and change the user from nginx to apache.

Note that in theory we would also need to change the user in the php-fpm, but the default user that is used is already apache.

Set the right user for the work folder of nginx.

chown -R apache:apache /var/lib/nginx

4. Test the configuration

Then let's start our services:

service httpd stop
service nginx start
service php-fpm start

Then your old website should be appearing.

5. Optimizations

5.1 Compression

In my website I had gzip compression for certain mime types (http://ciplogic.com/index.php/blog/98-creating-joomla-responsive-templates) so I had this into my httpd.conf:

AddType font/truetype .ttf
AddType font/opentype .otf
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE font/truetype
AddOutputFilterByType DEFLATE font/opentype

So in order to add them as well to nginx, the following changes are needed to be added into a new file named /etc/nginx/conf.d/gzip_compression.conf, and also having the new font mimetypes registered into the existing /etc/nginx/mime.types.

mime.types

Simply add the mime types, basically the AddType statements from the previous configuration, before the closing bracket into the /etc/nginx/mime.types file.

font/truetype                         ttf;
font/opentype                         otf;

Also make sure that the js files are mapped to the text/javascript mimetype, since by default in nginx they are mapped for some reason to application/x-javascript.

text/javascript                       js;

gzip_compression.conf

Enable the gzip compression, and register it for our mime types. Note that text/html is registered by default, and you will get a warning if you add it into the gzip_types.

gzip on;
gzip_proxied any;
# text/html is registered by default into the gzip_types, and you will get a warning adding it again here.
gzip_types application/json text/javascript text/css text/plain font/truetype font/opentype;
gzip_vary on;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";

Restart nginx for changes to take effect.

5.2 Use Local Sockets for php-fgm

If you noticed at step 2, in order to get our PHP running, the nginx server was talking via the loopback device to the php-fpm daemon on port 9000. Since we don't want to transport everything via TCP/IP for no good reason, we'll use local sockets that should be slightly faster in establishing the connections, and actual serialization of data.

Our local socket will be located at: /var/run/php-fpm/php-fpm.socket.

Thus in the /etc/php-fpm.d/www.conf file, we need to change the listen from:

listen = 127.0.0.1:9000

to

listen = /var/run/php-fpm/php-fpm.socket
listen.owner = apache
listen.group = apache
listen.mode = 0660

The reason for the other lines is that by default, because of reasons, php-fpm will create the socket with the owner root, despite the fact that the actual runner instances for this pool will run as apache (see step 3). And we want a socket with just enough rights that can be read by nginx.

Thus we will change in both /etc/nginx/conf.d/default.conf the fastcgi_pass to:

fastcgi_pass   unix:/var/run/php-fpm/php-fpm.socket;

6. Virtual Hosts

Now for each virtual host basically the same configuration takes place, but only with the location configuration sections for the root and the PHP processing. For example this is what I use for the codeeditor.ciplogic.com:

server {
    listen      80;
    server_name codeeditor.ciplogic.com;
 
    location / {
        root    /var/www/codeeditor;
        index   index.html index.html index.php;
        try_files $uri $uri/ /index.php?$args;
    }
 
    # deny running scripts inside writable directories
    location ~* /(images|cache|media|logs|tmp)/.*\.(php|pl|py|jsp|asp|sh|cgi)$ {
        return 403;
        error_page 403 /403_error.html;
    }
 
    location ~ \.php$ {
        root           /var/www/codeeditor;
        try_files $uri =405;
        fastcgi_pass   unix:/var/run/php-fpm/php-fpm.socket;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include     fastcgi_params;
    }
}

7. Make it permanent

Since all seems fine now, the last thing we need to do is to ensure that when rebooting the nginx with php-fpm will start up instead of apache. Thus we need to disable the old httpd service and enable both nginx and php-fpm.

chkconfig httpd off
chkconfig nginx on
chkconfig php-fpm on

Congratulations! You're done. 

Final Note

Performance wise the speed increase was relatively marginal, about 5% better for fetching the php files. Considering that I didn't changed the PHP version, nor upgraded Joomla, or did anything else, that alone sounds amazing, but not only it was faster, also the memory usage dropped in half, from 240MB under apache to 130MB using nginx + php-fpm.

Faster server, and smaller footprint? Well count me in.

References

  1. http://askubuntu.com/a/134676/443187
  2. http://serverfault.com/a/406169/187047
  3. https://www.howtoforge.com/installing-nginx-with-php5-and-mysql-support-on-debian-squeeze
  4. https://rtcamp.com/tutorials/nginx/enable-gzip/
  5. http://todsul.com/tech/install-and-configure-php-fpm-on-nginx/
  6. https://docs.joomla.org/Nginx 

 

Disqus Comments

comments powered by Disqus

Germanium

The one to rule them all. The browsers that is.

SharpKnight

SharpKnight is an Android chess game.

MagicGroup

MagicGroup is an eclipse plugin.