server setup with nginx, php5, fastcgi, varnish

21.02.2009

i've a very old laptop i don't use, and i decided to use it as a webserver/file server(well, i got the idea from this post on reddit). here's how my server looks like to human beings. on this post i'll explain to install and configure the following setup:

  • · nginx
  • · php5
  • · fastcgi
  • · eaccelerator
  • · varnish

reasons of this setup

· nginx

nginx is quite popular webserver, reverse proxy, and load balancer. according to netcraft's february 2009 survey nginx hosts 3,447,596 websites. it's very friendly to resources. the main disadvantage of nginx was the documentation. there wasn't much english documentation, but there's now a english wiki for nginx.

· php5

the most popular programming language on the web. very simple, and ease to use.

· fastcgi

fastcgi's main goal is to reduce overhead, and allowing a server to serve more request at once.

· eaccelerator

it's almost impossible to run a popular website without an opcode in php. i use eaccelerator but you may want to use xcache, or apc but i prefer eaccelerator. there are not much differences but eaccelerator performs a little bit better than these two accelarators.

· zend optimizer
you may want to use zend optimizer as well but you may not see much difference on a high traffic website.

· varnish

varnish became quite popular lately. it's a high speed http accelerator. response time difference between running a site with varnish, and without varnish is high.

the problem

php is slow, very slow. it's very ease to use, very simple programming language but has some very bad behaviors. i'm not going to explain the problems with the php's design. it's off the topic, this post is about getting high performance. i'd made a simple comparison with python, and ruby as you can easily see on the web but i didn't on purpose. why? the reason is simple; if you know something about programming languages you'd know comparison the languages that are not designed in the same concept is pointless, and inaccurate. it's inaccurate to compare a programming language that is weakly typed with a programming language that is strongly typed(why do you think ocaml performs better than the most of the programming languages? it even performs better than c in some situations.).

installing and configuring nginx

i decided to install ubuntu to my good old laptop which became my new server. to install nginx;
  $ sudo apt-get install nginx

and start nginx with;
  $ sudo /etc/init.d/nginx start

configuration

configuration is ease, first type $>sudo vim /etc/nginx/sites-available/default, we need to make few changes, additions.
  location / {
    root    /var/www;
    index   index.php;
  }
  
  location ~ \.php$ {
    fastcgi_pass    127.0.0.1:9000;
    fastcgi_index   index.php;
    fastcgi_param   SCRIPT_FILENAME   /var/www/htdocs$fastcgi_script_name;
  }
save the file and restart nginx; $> /etc/init.d/nginx restart, nginx needs to start when the server is rebooted(obviously), so we need to type this;
  $ sudo update-rc.d nginx defaults
we can move on to php5.

installing php5

we need to type this long command to shell;
  $ sudo apt-get install php5-cgi php5-curl php5-dev php5-gd php5-pear php5-imagick php5-imap php5-mcrypt php5-memcache
  php5-mhash php5-ming php5-mysql php5-pgsql php5-pspell php5-sqlite php5-tidy php5-xmlrpc php5-xsl
you can customize this command, if you want to install php5-snmp package, you need to type it. the reason i've included php5-dev package is we need phpize to install eaccelerator and phpize comes with php5-dev package. when the installation is finished open /etc/php5/cgi/php.ini file and change the value of cgi.fix_pathinfo;
  cgi.fix_pathinfo = 1 // default value is 0

houston, we have a problem

when you install nginx you'll notice there's no built-in fastcgi support, we need to manage our fastcgi processes ourselves. i go with lighttpd's spawn-fcgi to start our own php fastcgi processes. there are some other php-fcgi init scripts on the internet but i find spawn-fcgi binary more ease, but the disadvantage of this method is we need to install lighttpd.
  $ sudo apt-get install lighttpd
  $ sudo update-rc.d -f lighttpd -remove //we really don't want lighttpd to start at boot time

to start php fastcgi process, we need to type;
  $ sudo /usr/bin/spawn-fcgi -a 127.0.0.1 -p 9000 -u www-data -g www-data -f /usr/bin/php5-cgi -P /var/run/fastcgi-php.pid
remember we defined fastcgi port as 9000 in the config file of nginx. we can now move on to installation of eaccelerator.

installation of eaccelerator

if make is not installed, the first we need to do is to install it;
  $ sudo apt-get install make

download the latest release of eaccelerator and extract it, then get into directory and type;
  $ sudo phpize
  $ sudo ./configure --enable-eaccelerator=shared
  $ sudo make
  $ sudo make install

and now we can edit the php.ini (/etc/php5/cgi/php.ini), add the following definitions after the [PHP] tag.
  ; eAccelerator configuration
  extension                       = "/usr/lib/php5/20060613+lfs/eaccelerator.so"
  eaccelerator.shm_size           = "256"
  eaccelerator.cache_dir          = "/var/cache/eaccelerator"
  eaccelerator.enable             = "1"
  eaccelerator.optimizer          = "1"
  eaccelerator.check_mtime        = "1"
  eaccelerator.debug              = "0"
  eaccelerator.filter             = ""
  eaccelerator.shm_max            = "0"
  eaccelerator.shm_ttl            = "0"
  eaccelerator.shm_prune_period   = "0"
  eaccelerator.shm_only           = "0"
  eaccelerator.compress           = "1"
  eaccelerator.compress_level     = "9"
  eaccelerator.allowed_admin_path = "/var/www/eaccelerator"

and we need to create /var/cache/eaccelerator directory with the needed permission.
  $ sudo mkdir /var/cache/eaccelerator
  $ sudo chmod 777 /var/cache/eaccelerator

here are the things you need to not forget;
  • · if you want to install zend optimizer, its definitions must come after the eaccelerator definitions.
  • · if you installed xdebug, you may receive internal server error. you need to uninstall either zend optimizer or xdebug.

it's time to restart nginx;
  $ sudo /etc/init.d/nginx restart

and now we need to be sure everything went ok. create a new php file on the htdocs root;
  $ vim info.php
and put these lines;
  <?php
  phpinfo();
  ?>
something similar to these images should appear;


before moving to next topic, let's make benchmark to see how php performs with eaccelerator, and without eaccelerator.

eaccelerator enabled
  $ ab -n 10000 -c 100 http://server/
  >> Requests per second: 232.57 [#/sec] (mean)

eaccelerator disabled
  $ ab -n 10000 -c 100 http://server/
  >> Requests per second: 148.71 [#/sec] (mean)

just for curiosity i've also tested xcache as well.
xcache enabled
  $ ab -n 10000 -c 100 http://server/
  >> Requests per second: 208.46 [#/sec] (mean)
the code that is used on this mini benchmark can be viewed here.

we can now move on to the most interesting item in this post which is; varnish.

installation and configuration of varnish

i'll talk about more how varnish works in another post. this post is about getting the things work right.

by default varnish daemon listens on port 6081, and uses the default.vcl file. management port is on port 6082, but it will be changed in the future, unix socket support will be added.

the command line is;
  $ varnishd -a :6081 -T localhost:6082 -f /etc/varnish/default.vcl -sfile, /tmp

varnish comes with vcl, a flexible configuration language. vcl syntax is similar to c, and perl. here are some basic things about vcl;
  • · comments are c, c++, shell style.
  • · strings are defined between quotes; "my_string"
  • · inline c code can be added. an example;
    C {
      prinf("hello world");
      }C
    
  • · backends are for server definitions.

now we can start to edit the config file($> sudo vim /etc/varnish/default.vcl). when you install varnish you'll notice there's no any difference. that's because if varnish detects a cookie it doesn't cache anything. first thing we need to do is to tell varnish to cache even if there's a cookie in the incoming request. so, we need to add cookie to hash;
  sub vcl_hash {
    set req.hash += req.http.cookie;
  }

and remove the commented lines in the config file. change the line 33 to;
  if (req.http.Authenticate) {
    pass;
  }

default configuration of varnish doesn't compress/decompress objects. it's good to compress the objects to reduce the bandwidth usage, and to speed up, and to serve more clients. there are two compression algorithms can be used which are deflate, and gzip. a good way of using these algorithms as specified in the faq section of varnish;
  if (req.http.Accept-Encoding) {
    if (req.url ~ "\.(jpg|png|gif|gz|tgz|bz2|tbz|mp3|ogg)$") {
      # no point in compressing these
      remove req.http.Accept-Encoding;
    } elsif (req.http.Accept-Encoding ~ "gzip") {
      set req.http.Accept-Encoding = "gzip";
    } elsif (req.http.Accept-Encoding ~ "deflate") {
      set req.http.Accept-Encoding = "deflate";
    } else {
      # unkown algorithm
      remove req.http.Accept-Encoding;
    }
  }

we may want to configure varnish's caching mechanism a little bit, you'll notice varnish caches too much.
  sub vcl_recv {
    if(req.http.Cache-Control ~ "no-cache") {
      pass;
    }
    # force lookup if the request is a no-cache request from the client
    if (req.http.Cache-Control ~ "no-cache") {
      purge_url(req.url);
    }
  }
  
  sub vcl_fetch {
    if(obj.http.Pragma ~ "no-cache" || obj.http.Cache-Control ~ "no-cache" || obj.http.Cache-Control ~ "private") {
      pass;
    }
    
    if(obj.http.Cache-Control ~ "max-age") {
      set obj.http.Cache-Control = regsub(obj.http.Cache-Control, "max-age=[0-9]+", "max-age=0");
    }
  }

let's run a mini benchmark how our server performs with varnish.
  $ ab -n 10000 -c 100 http://server/
  >> Requests per second: 552.31 [#/sec] (mean)
requests per second improved to 552.31 from 232.57.

this is the end for now, i hope you find this post useful, and informative.
blog comments powered by Disqus