Using NGINX as a Web Server for Drupal
Table of Contents
Nginx (engine-x) is a web server that is regarded to be faster than Apache and with a better performance on heavy load. The difference is summed up succinctly in a quote by Chris Lea on the Why Use Nginx? page: "Apache is like Microsoft Word, it has a million options but you only need six. Nginx does those six things, and it does five of them 50 times faster than Apache."
Technically speaking, Apache is a process-and-thread-driven application, while Nginx is event-driven. In practice this means that Nginx needs much less memory than Apache to do the work, and also can work faster. There are claims that Nginx, working in a server of 512MB RAM, can handle 10,000 (yes, ten thousands) concurrent requests without problem, while Apache with such a load would just commit harakiri (suicide). Besides, the configuration of Nginx, once you get used to it, is simpler and more intuitive than that of Apache.
It seemed like something that I should definitely give a try, since my web server already had performance problems and I cannot afford to pay for increasing its capacity. Here I describe the steps for installing and configuring Nginx to suit the needs of my web application (which is based on Drupal7, running on a 512MB RAM server at Rackspace).
1 Installing nginx and php5-fpm
In ubuntu server this is very easy:
sudo apt-get install nginx nginx-doc php5-fpm update-rc.d apache2 disable update-rc.d nginx enable service apache2 stop service nginx start
2 Configuring php5-fpm
The main config file (/etc/php5/fpm/php-fpm.conf
) did not need to
be changed at all.
On the pool configuration file (/etc/php5/fpm/pool.d/www.conf
) I made
only some small modifications:
- Listen to a unix socket, instead if a TCP socket:
;listen = 127.0.0.1:9000 listen = /var/run/php-fpm.sock
- Other modified options:
pm.max_requests = 5000 php_flag[display_errors] = on php_admin_value[memory_limit] = 128M php_admin_value[max_execution_time] = 90
I also made these modifications on /etc/php5/fpm/php.ini
:
cgi.fix_pathinfo=0 max_execution_time = 90 display_errors = On post_max_size = 16M upload_max_filesize = 16M default_socket_timeout = 90
Finally restarted the service php5-fpm:
service php5-fpm restart
3 Configuring nginx
On ubuntu, the configuration of Nginx is located at /etc/nginx/
.
- Create a configuration file for the website, based on the drupal
example configuration file:
cd /etc/nginx/sites-available/ cp /usr/share/doc/nginx-doc/examples/drupal.gz . gunzip drupal.gz mv drupal btranslator_dev cd /etc/nginx/sites-enabled/ ln -s ../sites-available/btranslator_dev .
- At
/etc/nginx/sites-enabled/btranslator_dev
modify server_name and root, and also add access_log and error_log:server_name dev.btr.fs.al l10n-dev.org.al; root /var/www/dev.btr.fs.al; access_log /var/log/nginx/btranslator_dev.access.log; error_log /var/log/nginx/btranslator_dev.error.log info;
- At
/etc/nginx/sites-enabled/btranslator_dev
, modify the name of the unix socket at the fastcgi_pass line:location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; include fastcgi_params; # Intercepting errors will cause PHP errors to appear in Nginx logs fastcgi_intercept_errors on; fastcgi_pass unix:/var/run/php-fpm.sock; }
- At
/etc/nginx/sites-enabled/btranslator_dev
, add the index line as well, at the root location:location / { index index.php; try_files $uri $uri/ @rewrite; }
- At
/etc/nginx/sites-enabled/btranslator_dev
, allow only localhost to access txt and log files:location ~* \.(txt|log)$ { allow 127.0.0.1; deny all; }
- At
/etc/nginx/nginx.conf
, decrease worker processes to 1 or 2:# worker_processes 4; worker_processes 2;
These modifications are all we need, and then we can reload or restart the nginx service:
service nginx restart
4 Configuration for phpMyAdmin
Add these lines inside the server section, at
/etc/nginx/sites-enabled/btranslator_dev
:
# Configuration for phpMyAdmin location /phpmyadmin { root /usr/share/; index index.php index.html index.htm; location ~ ^/phpmyadmin/(.+\.php)$ { try_files $uri =404; root /usr/share/; fastcgi_pass unix:/var/run/php-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include /etc/nginx/fastcgi_params; } location ~* ^/phpmyadmin/(.+\.(jpg|jpeg|gif|css|png|js|ico|html|xml|txt))$ { root /usr/share/; } } location /phpMyAdmin { rewrite ^/* /phpmyadmin last; }
Then reload the nginx service.
5 SSL (HTTPS) support
Add these lines at /etc/nginx/sites-enabled/btranslator_dev
:
server { listen 80; listen 443 ssl; ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem; ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key; . . . . . }
Since SSL connections have some overhead, to make them more
efficient, add these lines as well at /etc/nginx/nginx.conf
(in
order to increase session timeout and and use less expensive
encryption):
http { . . . . . #keepalive_timeout 65; keepalive_requests 50; keepalive_timeout 300; ## Global SSL options ssl_ciphers HIGH:!aNULL:!MD5:!kEDH; ssl_prefer_server_ciphers on; ssl_protocols TLSv1; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; . . . . . }
Then reload nginx.
6 Avoid any DOS attacks
In order to avoid any DOS attacks, add these lines at /etc/nginx/nginx.conf
http { . . . . . ## limit request frequency to 2 requests per second limit_req_zone $binary_remote_addr zone=one:10m rate=2r/s; limit_req zone=one burst=5; . . . . . }
7 Full configuration of the site
A full version of the file
/etc/nginx/sites-enabled/btranslator_dev
looks like this:
server { listen 80; listen 443 ssl; ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem; ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key; server_name dev.btr.fs.al l10n-dev.org.al; root /var/www-ssl/dev.btr.fs.al; access_log /var/log/nginx/btranslator_dev.access.log; error_log /var/log/nginx/btranslator_dev.error.log info; location = /favicon.ico { log_not_found off; access_log off; } location = /robots.txt { allow all; log_not_found off; access_log off; } # This matters if you use drush location = /backup { deny all; } # Very rarely should these ever be accessed outside of your lan location ~* \.(txt|log)$ { allow 127.0.0.1; deny all; } # This location block protects against a known attack. location ~ \..*/.*\.php$ { return 403; } # This is our primary location block. location / { index index.php; try_files $uri $uri/ @rewrite; expires max; } # This will rewrite our request from domain.com/node/1/ to domain.com/index.php?q=node/1 # This could be done in try_files without a rewrite however, the GlobalRedirect # module enforces no slash (/) at the end of URL's. This rewrite removes that # so no infinite redirect loop is reached. location @rewrite { rewrite ^/(.*)$ /index.php?q=$1; } # If a PHP file is served, this block will handle the request. This block # works on the assumption you are using php-cgi listening on /tmp/phpcgi.socket. # Please see the php example (usr/share/doc/nginx/exmaples/php) for more # information about setting up PHP. # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; include fastcgi_params; # Intercepting errors will cause PHP errors to appear in Nginx logs fastcgi_intercept_errors on; fastcgi_pass unix:/var/run/php-fpm.sock; } # The ImageCache module builds an image 'on the fly' which means that # if it doesn't exist, it needs to be created. Nginx is king of static # so there's no point in letting PHP decide if it needs to be servered # from an existing file. # If the image can't be served directly, it's assumed that it doesn't # exist and is passed off to PHP via our previous rewrite to let PHP # create and serve the image. # Notice that try_files does not have $uri/ in it. This is because an # image should never be a directory. So there's no point in wasting a # stat to serve it that way. location ~ ^/sites/.*/files/imagecache/ { try_files $uri @rewrite; } # As mentioned above, Nignx is king of static. If we're serving a static # file that ends with one of the following extensions, it is best to set # a very high expires time. This will generate fewer requests for the # file. These requests will be logged if found, but not if they don't # exist. location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ { expires max; log_not_found off; } # Configuration for phpMyAdmin location /phpmyadmin { root /usr/share/; index index.php index.html index.htm; location ~ ^/phpmyadmin/(.+\.php)$ { try_files $uri =404; root /usr/share/; fastcgi_pass unix:/var/run/php-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include /etc/nginx/fastcgi_params; } location ~* ^/phpmyadmin/(.+\.(jpg|jpeg|gif|css|png|js|ico|html|xml|txt))$ { root /usr/share/; } } location /phpMyAdmin { rewrite ^/* /phpmyadmin last; } }
8 Referencies:
- http://arstechnica.com/business/2011/11/a-faster-web-server-ripping-out-apache-for-nginx/
- http://blog.celogeek.com/201209/202/how-to-configure-nginx-php-fpm-drupal-7-0/
- http://insready.com/blog/build-nginx-php-fpm-apc-memcache-drupal-7-bare-bone-ubuntu-1004-or-debian-5-server
- http://groups.drupal.org/node/238983
- http://groups.drupal.org/nginx
- http://www.howtoforge.com/running-phpmyadmin-on-nginx-lemp-on-debian-squeeze-ubuntu-11.04
- http://nginx.org/en/docs/http/configuring_https_servers.html
- http://wiki.nginx.org/HttpSslModule
- http://wiki.nginx.org/HttpLimitReqModule
- http://matt.io/technobabble/hivemind_devops_alert:_nginx_does_not_suck_at_ssl/ur