Search form

Nginx + PHP-FPM + PHP Cache + SSL + Varnish + Drupal

Note: The below instructions are for Varnish 3 on Ubuntu 12. If you want to run Drupal with Varnish 4 on Ubuntu 16, updated instructions are here.

So, you want to set up Nginx and Drupal with all the bells and whistles on Ubuntu Server 12.04. You want caching for both PHP and Varnish and you need it to work for both SSL and non-SSL. Here's a blueprint you might follow. This assumes you're starting from scratch with a bare install or image, with no web server, database or anything except SSH.

Note: Although the following text references APC, it is abandoned and you should consider the Zend OPcache instead. You can install APCu if you want a userland cache as well.

1) Install MySQL and php5-mysql

$ apt-get install mysql-server php5-mysql php5-suhosin

This also installs PHP-FPM.

Give yourself a good root database password. At this point you may also want to remove any test users and databases.

2) Install the latest Nginx

$ add-apt-repository ppa:nginx/stable

(If the server image you're using complains that add-apt-repository isn't installed, run apt-get install python-software-properties.)

$ apt-get update
$ apt-get install nginx
$ service nginx start

As of this writing, this gives you Nginx 1.5.10. If necessary, open TCP/80 and TCP/443 on your inbound firewall (or VPC security group, if you're using AWS) and visit your server's hostname or IP address in your browser. You should see the generic Nginx welcome page.

This method allows Nginx to be updated by the Ubuntu package manager, but remember that Nginx, unlike Apache, requires recompilation to add new modules. This might encourage you to install Nginx from source. However, you can add modules while keeping Nginx usable with apt-get by rebuilding its .deb packages.

3) Tell PHP-FPM to listen on a Unix socket instead of TCP

vi /etc/php5/fpm/pool.d/www.conf

Comment out the default listen directive and specify a Unix socket:

; listen = 127.0.0.1:9000
listen = /var/run/php5-fpm.sock

4) Install and configure APC

$ apt-get install php-apc php5-gd
$ vi /etc/php5/conf.d/apc.ini

At minimum I typically use these settings:

apc.shm_size = 128M
apc.mmap_file_mask = /apc.shm.XXXXXX
apc.ttl = 3600
apc.user_ttl = 7200
apc.gc_ttl = 3600

Restart php-fpm to apply your new settings.

5) Set up an SSL cert

(We'll just generate a self-signed cert for testing here. If you buy a cert, remember to point your Nginx server block to it instead.)

Create the server key:

$ openssl genrsa -des3 -out server.key 1024

Create the CSR:

$ openssl req -new -key server.key -out server.csr

Remove the key's password:

$ cp server.key server.key.org
$ openssl rsa -in server.key.org -out server.key

Sign it:

$ openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

Move your key and cert to an appropriate place, typically /etc/ssl.

6) Set up some Nginx server blocks for your Drupal site:

Non-SSL:

$ cp /etc/nginx/sites-available/default /etc/nginx/sites-available/drupal
$ vi /etc/nginx/sites-available/drupal
server {
        # omit "default" if this isn't the default site on your server
        # and if it is, remove "default" from the default server block.
        listen 80 default;                       

        root /path/to/drupal;
        index index.php;
        server_name myserver.com www.myserver.com;

        location / {
           try_files $uri @rewrite;   # need this for Drupal clean_urls
        }

        # define @rewrite
        location @rewrite {
           rewrite ^ /index.php last;
        }

        # tell nginx to pass php scripts to php-fpm
        location ~ \.php$ {
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                fastcgi_pass unix:/var/run/php5-fpm.sock;
                fastcgi_index index.php;
                include fastcgi_params;
        }
}

Remember to add your symbolic link to your config file in /etc/nginx/sites-enabled and restart Nginx. We'll worry about SSL after we get Drupal and Varnish installed. We'll modify this file a bit later on too, but for now let's get Drupal up and running.

7) Install Drupal in your "root" specified above using a default setup:

Refer to the official instructions if you need help. Enable clean_urls in the Drupal admin panel (or run drush vset clean_url 1 --yes if you've installed Drush) and visit your site to ensure they're working.

8) Install and configure Varnish:

Add the official Varnish repo:

$ curl http://repo.varnish-cache.org/debian/GPG-key.txt | sudo apt-key add -
$ echo "deb http://repo.varnish-cache.org/ubuntu/ precise varnish-3.0" | sudo tee -a /etc/apt/sources.list
$ sudo apt-get update
$ sudo apt-get install varnish

This gives you Varnish 3.0.5 as of this writing.

Configure Varnish:

vi /etc/default/varnish

Make sure "start=yes" is specified, and change DAEMON_OPTS to make Varnish listen on port 80:

DAEMON_OPTS="-a :80 \
             -T localhost:6082 \
             -f /etc/varnish/default.vcl \
             -S /etc/varnish/secret \
             -s malloc,256m"

Set up your Drupal-specific .vcl file at /etc/varnish/default.vcl. I start with this:

acl upstream_proxy {
    "127.0.0.1";
}

backend default {
    .host = "127.0.0.1";
    .port = "8080";
}

sub vcl_recv {
    # Set the X-Forwarded-For header so the backend can see the original
    # IP address. If one is already set by an upstream proxy, we'll just re-use that.
    if (client.ip ~ upstream_proxy && req.http.X-Forwarded-For) {
       set req.http.X-Forwarded-For = req.http.X-Forwarded-For;
    } else {
       set req.http.X-Forwarded-For = regsub(client.ip, ":.*", "");
    }
    ## Remove has_js and Google Analytics cookies.
    set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(__[a-z]+|has_js)=[^;]*", "");
    ## Remove a .;. prefix, if present.
    set req.http.Cookie = regsub(req.http.Cookie, "^;\s*", "");
    ## Remove empty cookies.
    if (req.http.Cookie ~ "^\s*$") {
       unset req.http.Cookie;
    }
    # Site still uses some static files out of /files, cache them
    if (req.url ~ "^/files/site.*") {
       unset req.http.Cookie;
    }
    # enable caching of theme files
    if (req.url ~ "^/sites/www.site.*") {
       unset req.http.Cookie;
    }
    # Drupal js/css doesn.t need cookies, cache them
    if (req.url ~ "^/modules/.*\.(js|css)\?") {
       unset req.http.Cookie;
    }
    ## Pass cron jobs and server-status
    if (req.url ~ "cron.php") {
       return (pass);
    }
    if (req.url ~ ".*/server-status$") {
       return (pass);
    }
}

sub vcl_hash {
   if (req.http.Cookie) {
      hash_data(req.http.Cookie);
   }
}

8) Tell Nginx to listen on 8080 instead of 80, and set it up for SSL:

Edit your /etc/nginx/sites-available/drupal file from above, and change the listen directive:

server {
        listen 127.0.0.1:8080 default; # tells Nginx to listen for traffic passed by Varnish
        [...]
}

Next, create a file called /etc/nginx/sites-available/drupal-ssl:

server {
        listen 443 ssl default;
        server_name myserver.com www.myserver.com;

        ssl_certificate /etc/ssl/certs/mycert.crt;
        ssl_certificate_key /etc/ssl/private/mykey.key;

        location / {
            # Pass the request on to Varnish.
            proxy_pass  http://127.0.0.1;

            # Pass some headers to the downstream server, so it can identify the host.
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

            # Tell any web apps like Drupal that the session is HTTPS.
            proxy_set_header X-Forwarded-Proto https;

            proxy_redirect     off;
        }
}

What we've done here is to tell Nginx to listen on 443 for SSL traffic, then decrypt it and pass it to Varnish on 80 for caching. Varnish of course doesn't handle SSL, so if you want your SSL traffic to be cached you need a proxy or load balancer like Nginx or Pound in front of Varnish. Effectively we've created an Nginx->Varnish->Nginx sandwich.

9) Get Drupal set up for SSL and Varnish:

Install the Varnish module, then configure it through the Drupal admin panel. Ensure "3.x" is selected as the Varnish version and copy in the contents of /etc/varnish/secret as the "Control Key".

Then edit the settings.php for your Drupal site. Add a couple of statements:

// Tell Drupal to use SSL based on headers sent by Nginx and Varnish
if(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'){
    $_SERVER['HTTPS']='on';
}

// If you want your site to be SSL only, you can use a redirect like this
//if(!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != 'on'){
//    header('Location: '. 'https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
//}

10) Finally, restart everything:

$ service nginx stop
$ service varnish restart 
$ service nginx start

To ensure Varnish is working, you can run varnishstat on the command line while you click around your site. To ensure APC is working, unzip the apc.php.gz file and put it in a web-accessible directory. You might also check out the Atlassian Varnish configuration for some additional ideas on caching Drupal sites.

Categories: