Using Zend Framework or Drupal with Nginx

Recently I've been playing a lot with Nginx as an alternative web server to Apache.  I also tend to write and manage a lot of PHP sites, some using Drupal, some Zend Framework.  For Drupal I found some nice config advice on the Nginx Wiki but rather than having a heap of definitions there that I then had to duplicate for PHP-only and Zend Framework sites, I decided to put together a few that make it really easy to manage multiple virtual hosts.

The files all start with basic PHP functionality:

# Simple include for PHP files
index index.php index.html;

location = /favicon.ico {
log_not_found off;
access_log off;
}

location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}

location ~ \..*/.*\.php$ {
return 403;
}

location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_intercept_errors on;
fastcgi_pass unix:/var/tmp/fastcgi.sock;
}

location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
expires max;
log_not_found off;
}

 

This I saved as /etc/nginx/conf.d/php.inc. This assumes you have a distro that stores Nginx configs in /etc/nginx, but this is where you'll find Nginx's configs on either CentOS or Ubuntu, the two distros I most use.

To use this in a virtual host (server) container, just include it.  This makes the server container really light:

server {
server_name mytest.web.localdomain;
root /web/mytest/trunk/site;
include conf.d/php.inc;
}

 

To handle a Zend Framework site I use the same idea, but since most of the work is already done in php.inc, the zf.inc is much simpler:

# Simple include for Zend Framework applications

location / {
try_files $uri @rewrite;
}

location @rewrite {
rewrite ^/(.*)$ /index.php;
}

include conf.d/php.inc;

 

Note the include in the bottom.  Basically it does everything required and includes the defaults from the php.inc file to ensure that everything works as required.  So the server container need only include the zf.inc file.  Similarly with Drupal sites, they just need to include a drupal.inc that looks like:

# Include file for drupal sites.

# This matters if you use drush
location = /backup {
deny all;
}

location / {
try_files $uri @rewrite;
}

location @rewrite {
rewrite ^/(.*)$ /index.php?q=$1;
}

location ~ ^/sites/.*/files/imagecache/ {
try_files $uri @rewrite;
}

include conf.d/php.inc;

 

Simple!  Now I can have very tidy server containers and know that I can support Drupal or Zend Framework sites without a problem.

But wait! Why do it this way, why not just use Apache or if you must use Nginx then proxy just the PHP requests to Apache?

There are a few answers to this.

Firstly nginx is very, very fast when compared to Apache for static file handling.  Think about your typical web page and the requests that are coming in.  Less than 10% of the requests are likely to be for the PHP code.  The rest is going to be for images, css files, maybe script files and other components that are static.  Handling 90% of that effort in a web server designed to be extremely fast for static content makes a heap of sense.

Proxying to Apache can make a lot of sense, if you are wedded to the Apache way (using .htaccess, and relying on SAPI mode for PHP), but if you aren't you can find that using PHP in FastCGI mode works equally well.  It does take a bit of understanding to make it all work though.

No .htaccess file

FastCGI/Nginx doesn't support .htaccess files.  There are a lot of reasons for this and you should check the Nginx wiki for more information on the technical details. So you need to think how to handle situations where you may have set PHP values in .htaccess for your app.

Luckily with PHP 5.3 and later there is a simple answer.  The User INI file.  This is a file usually called .user.ini which can be placed in the top-level directory of your web app and it will be parsed as an extra ini file (in PHP.ini format) before your PHP code is executed.  So if you were using auto-prepend and auto-append files, in .htaccess it would look like this:

php_value auto_prepend_file init.php
php_value auto_append_file final.php

The equivalent in .user.ini would look like this:

auto_prepend_file = init.php
auto_append_file = final.php

No php_value in Virtual Host containers

Because PHP is not embedded in Nginx, you cannot configure it the same way you can with Apache.  Still, using the .user.ini method above, this is not a problem.

It seems a bit convoluted, remind me why I wouldn't use Apache?

The big problem with using Apache is that when PHP uses memory, it increases the size of the Apache process.  If using the prefork MPM (and most *nix implementation do this), then you end up with the process retaining this memory size until it exits.  Get a few high-memory PHP pages being hit quickly and your entire web server can run out of RAM in very short order, especially if you use the default configs, or the default config tuning advice (which works fine for stuff that doesn't run PHP, but is in fact the very opposite of what you need for a stable PHP site). I might put together a post on tuning Apache for PHP some time soon.

The same problem exists using FastCGI, in that the PHP process sticks around and hence the memory is not released until it exits.  But the difference is that this is just for PHP.  Static files are handled by Nginx, so you only need to worry about that small percentage of your traffic that is in fact PHP.  So you need less processes hanging around to service these requests, so less overall memory.

If you proxy to Apache you get some of that benefit, because once again Nginx is handling the bulk of the work, but why put in a full web server when you already have one running?  Seems like overkill to me.

If you don't need the power of Apache, just run Nginx.  Even if you need PHP, it is all you'll need.

No feedback yet


Form is loading...