Ultimate Guide to Page Speed: Advanced

The Portent Team Mar 3 2016

Preface: Let’s make a ruckus
Chapter 1: A Guide To This Guide
Chapter 2: Why Page Speed Matters To Digital Marketing
Chapter 3: What Impacts Page Speed?
Chapter 4: Novice – Image Compression And Such
Chapter 5: Intermediate – Server Compression And Geekery
Chapter 6: Advanced Chapter 6: Advanced – Varnish, Apache and nginx ← You are here
Chapter 7: Tools
Chapter 8: Glossary
Hidden Track: WordPress Optimization

In this post: We go down, down into the deepest, darkest heart of servers. And survive, sanity intact. If you want to really get your site screaming fast, this is where you need to go.

This chapter’s different. Instead of individual caching tips and tricks, we talk about entire technology suites and big-time server configuration changes. We’re doing some serious geekenization here. Configuration changes free up the pipe, accelerate server performance and speed up browsers. This is the big time.

Move the database

The easiest advanced upgrade you can make: Move your database to a separate server optimized for database software.

Database access and web page delivery both require lots of resources. They have to share those resources. A database may suck up CPU cycles that the web server needs during high traffic. The web server may eat up memory the database needs. Both can eat up disk space.

Putting the database on one server and the web server software on another gives each one its own, dedicated set of resources. You can also give the database access to a faster CPU (or, if you’re doing this in the cloud, access to more CPU oomph), which it needs.

Be sure the two servers have a nice, fat pipe between them. They’ll have to talk to each other. But that’s trivial compared to the gains you’ll see.

Our super-speedy technology stack

Our stack is pretty fancy. We built it because we were going after that last .4 seconds to shave off load times. When you’re down to this kind of hair-splitting, things get pretty customized.

But we’ve gotten up to our elbows in servers and tried a lot of stuff. This stack provides us the best results. It runs portent.com (and many others).

We used to use a pure NGINX stack. Since moving to SSL, we now use a combination of NGINX, Apache, Varnish and a load balancer, like this:

Load balancer >> NGINX >> Varnish >> Apache

The performance win is huge. We originally took portent.com from 2+ second load times to under 1 second. Since then, we’ve moved to SSL and added other technologies. That’s increased load times back to 2.5 seconds or so. But because of deferred javascript and pre-rendering, all pages fully load for the visitor in under 1 second.

What, no step by step?

We could write an entire extra book (or seven) about this technology stack and we’d still leave things out. Have a look at this introductory blog post we wrote here. But this isn’t a ‘one size fits all’ solution. You’ll need to tweak and change your toolset to fit your unique site and applications.

If you need specific instructions to do this stuff, please, don’t do it yourself. This isn’t a statement on your skills. It’s just smart: These are open-source technologies. They have their quirks. You’ll need all your problem-solving brainpower to deal with those. The technology needs to already make sense.

Web server: NGINX

Technically, NGINX is the ‘public’ side of our stack. It’s delivering the actual pages to users.

NGINX (“engine x”) is a lightweight HTTP server with modular architecture that serves static and index files, supporting accelerated reverse proxying with caching, simple load balancing, auto indexing, gzipping, FastCGI caching, and much more.

We like NGINX a lot and have used it for portent.com for over 2 years now. Combined with PHP5-FPM, it’s the foundation for the whole stack. FPM the FastCGI Process Manager. It’s an alternative to some of the PHP-specific accelerators.

It lets you spawn PHP processes at blazing speed and gracefully starts and stops PHP workers without losing queries.

Put NGINX and PHP5-FPM together and you’ve got one heck of a combination.


Apache is the workhorse. It runs WordPress and builds our site’s pages.

If you’re a web geek, you know Apache. It’s robust and super-popular, so our web partners know how to work with it. And, while it’s not as speedy as NGINX out of the box, you can tweak it to match NGINX performance.

Here are a few settings we use to speed things up:

AllowOverride None
AllowOverride is a directive that is usually set to “All” in standard Apache configurations. It tells Apache to scan and process .htaccess files in every directory and sub-directory on every Apache request, meant to be a convenience for .htaccess updates taking effect immediately. As you can imagine, this eats up a LOT of resources from the server.

Our recommendation is to set this directive to “None”, and instead include any .htaccess files required for your site.

<Directory /var/www/portent.com>
    Options -Indexes +FollowSymLinks -MultiViews
    AllowOverride None

    Include /var/www/portent.com/.htaccess

Included files get cached by Apache, so any changes made to the .htaccess file would require the reloading of Apache for the updates to take effect.

Running Apache with mod_php has been criticized for slow performance, especially when compared to NGINX with FastCGI (PHP5-FPM). However, we (and others) have found that it is the product of poor configuration, directly related to AllowOverride. Once .htaccess discovery is turned off, Apache with mod_php routinely runs as fast or faster than FastCGI.

We certainly aren’t condemning FastCGI, but it is worth benchmarking your site. You may find that FastCGI gets better results, or perhaps mod_php is slightly better. We are confident in both setups as we have implemented each for portent.com, currently running Apache with mod_php.

With any PHP implementation geared towards performance, OpCache or APC should be utilized. PHP 5.5 introduced built-in OpCache. Before 5.5, APC or Xcache had to be implemented alongside PHP for the performance boosts they all provide: caching PHP code.

When a PHP script is processed for the first time, OpCache/APC will store the machine code in local memory, allowing for quick access the next time it is requested. We have seen OpCache/APC provide a tremendous performance boost in all of the sites we have implemented and highly recommend it. There are more complexities than just turning it on, so be sure to do research to find a solution for your implementation.


Varnish is a reverse proxy HTTP accelerator developed for dynamic, content-heavy web sites. Varnish caches pages in virtual memory, leaving the operating system to decide what gets written to disk or stored in RAM.

That’s a triple-win:

  1. Page requests don’t require database queries
  2. Page requests sometimes come from memory
  3. Page requests hit a server optimized to deliver cached content

The load balancer

Not much to see here. The load balancer handles SSL and passes requests to the best nodes.

Web Server Configurations

We can’t give you a step-by-step, but we can give some configuration examples. Here you go:

Leveraging browser caching with expires headers

Expires headers are a web server’s way of telling a visiting browser “This file won’t be changing for a while.” Used correctly, they reduce the number of HTTP requests required per visit, and that’s a huge performance gain. You reduce use of the pipe.

Here’s a simple example:

  1. You land on portent.com for the first time
  2. Your browser begins to request files from the site’s server: images, css, javascript, etc.
  3. During the request process, your browser acquires the main header logo for the site (ie. portent_logo.png)
  4. At the same time, the server tells your browser that the logo won’t change any time soon
  5. Therefore, your browser doesn’t reload the logo on future visits. Instead, it uses the version cached on your hard drive. That’s one less file to transfer

If the server does NOT have expires headers defined for PNG files, your browser requests and re-loads the file every time it loads a page. Ugh.

A typical page on our site has 100-200 separate files or resources. Without expires headers, visiting browsers have to load every file, every time they hit the site. Seems like a waste, doesn’t it?

Using expires headers, we reduce those requests 50%.

Warning: Be sure to understand what files you need to keep relatively fresh, and set their expires headers accordingly. Think about which files do change.

NGINX expires configurations
This configuration is super-aggressive, telling NGINX to set the expiration of a huge number of file types to 1 year (31536000 seconds) from first load date.

# Aggressive caching for static files that rarely/never change
location ~* \.(asf|asx|wax|wmv|wmx|avi|bmp|class|divx|doc|docx|eot|exe|
wri|xla|xls|xlsx|xlt|xlw|zip)$ {
    expires 31536000s;
    add_header Pragma public;
    add_header Cache-Control "max-age=31536000, public";

This is a lot less aggressive. It tells NGINX to set the expiration data of CSS and javascript files to 24 hours (86400 seconds).

location ~* \.(css|js)$ {
    expires 86400s; 
    add_header Pragma public;
    add_header Cache-Control "max-age=86400, public";

Apache expires configurations
The same can be done with Apache.

<IfModule mod_mime.c>
    AddType text/css .css
    AddType application/x-javascript .js
    AddType text/x-component .htc
    AddType text/html .html .htm
    AddType text/richtext .rtf .rtx
    AddType image/svg+xml .svg .svgz
    AddType text/plain .txt
    AddType text/xsd .xsd
    AddType text/xsl .xsl
    AddType text/xml .xml
    AddType video/asf .asf .asx .wax .wmv .wmx
    AddType video/avi .avi
    AddType image/bmp .bmp
    AddType application/java .class
    AddType video/divx .divx
    AddType application/msword .doc .docx
    AddType application/vnd.ms-fontobject .eot
    AddType application/x-msdownload .exe
    AddType image/gif .gif
    AddType application/x-gzip .gz .gzip
    AddType image/x-icon .ico
    AddType image/jpeg .jpg .jpeg .jpe
    AddType application/vnd.ms-access .mdb
    AddType audio/midi .mid .midi
    AddType video/quicktime .mov .qt
    AddType audio/mpeg .mp3 .m4a
    AddType video/mp4 .mp4 .m4v
    AddType video/mpeg .mpeg .mpg .mpe
    AddType application/vnd.ms-project .mpp
    AddType application/x-font-otf .otf
    AddType application/vnd.oasis.opendocument.database .odb
    AddType application/vnd.oasis.opendocument.chart .odc
    AddType application/vnd.oasis.opendocument.formula .odf
    AddType application/vnd.oasis.opendocument.graphics .odg
    AddType application/vnd.oasis.opendocument.presentation .odp
    AddType application/vnd.oasis.opendocument.spreadsheet .ods
    AddType application/vnd.oasis.opendocument.text .odt
    AddType audio/ogg .ogg
    AddType application/pdf .pdf
    AddType image/png .png
    AddType application/vnd.ms-powerpoint .pot .pps .ppt .pptx
    AddType audio/x-realaudio .ra .ram
    AddType application/x-shockwave-flash .swf
    AddType application/x-tar .tar
    AddType image/tiff .tif .tiff
    AddType application/x-font-ttf .ttf .ttc
    AddType audio/wav .wav
    AddType audio/wma .wma
    AddType application/vnd.ms-write .wri
    AddType application/vnd.ms-excel .xla .xls .xlsx .xlt .xlw
    AddType application/zip .zip
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType text/css A86400
    ExpiresByType application/x-javascript A86400
    ExpiresByType text/x-component A31536000
    ExpiresByType text/html A3600
    ExpiresByType text/richtext A3600
    ExpiresByType image/svg+xml A3600
    ExpiresByType text/plain A3600
    ExpiresByType text/xsd A3600
    ExpiresByType text/xsl A3600
    ExpiresByType text/xml A3600
    ExpiresByType video/asf A31536000
    ExpiresByType video/avi A31536000
    ExpiresByType image/bmp A31536000
    ExpiresByType application/java A31536000
    ExpiresByType video/divx A31536000
    ExpiresByType application/msword A31536000
    ExpiresByType application/vnd.ms-fontobject A31536000
    ExpiresByType application/x-msdownload A31536000
    ExpiresByType image/gif A31536000
    ExpiresByType application/x-gzip A31536000
    ExpiresByType image/x-icon A31536000
    ExpiresByType image/jpeg A31536000
    ExpiresByType application/vnd.ms-access A31536000
    ExpiresByType audio/midi A31536000
    ExpiresByType video/quicktime A31536000
    ExpiresByType audio/mpeg A31536000
    ExpiresByType video/mp4 A31536000
    ExpiresByType video/mpeg A31536000
    ExpiresByType application/vnd.ms-project A31536000
    ExpiresByType application/x-font-otf A31536000
    ExpiresByType application/vnd.oasis.opendocument.database A31536000
    ExpiresByType application/vnd.oasis.opendocument.chart A31536000
    ExpiresByType application/vnd.oasis.opendocument.formula A31536000
    ExpiresByType application/vnd.oasis.opendocument.graphics A31536000
    ExpiresByType application/vnd.oasis.opendocument.presentation A31536000
    ExpiresByType application/vnd.oasis.opendocument.spreadsheet A31536000
    ExpiresByType application/vnd.oasis.opendocument.text A31536000
    ExpiresByType audio/ogg A31536000
    ExpiresByType application/pdf A31536000
    ExpiresByType image/png A31536000
    ExpiresByType application/vnd.ms-powerpoint A31536000
    ExpiresByType audio/x-realaudio A31536000
    ExpiresByType image/svg+xml A31536000
    ExpiresByType application/x-shockwave-flash A31536000
    ExpiresByType application/x-tar A31536000
    ExpiresByType image/tiff A31536000
    ExpiresByType application/x-font-ttf A31536000
    ExpiresByType audio/wav A31536000
    ExpiresByType audio/wma A31536000
    ExpiresByType application/vnd.ms-write A31536000
    ExpiresByType application/vnd.ms-excel A31536000
    ExpiresByType application/zip A31536000

We tried to find web servers that don’t support expires headers and couldn’t. But never say never, right?

Here’s a list of the ones we know will work. Expires headers can be set in IIS (Windows), Apache Tomcat (Java), etc. Many tools and content management systems will let you set expires headers, too. On WordPress, W3 Total Cache will help you set expires headers.

Enabling Keep-Alive

Keep-Alive is all about reducing server overhead.

In non-geek, the ‘Keep-Alive’ setting tells the server to maintain a connection between your web browser and the site server while you’re browsing. It’s like handing the phone to someone else when they want to talk to the caller, too, instead of hanging up and making them call back. This reduces round trips. The server won’t have to open as many new connections. That means less processor, memory and network overhead.

On HTTPS-only sites Keep-Alive is very, very important. TLS connections require multiple ‘handshakes.’ Keep-Alive means fewer new connections and many fewer handshakes.

As long as you’re using the core HTTP module (HTTPCoreModule) for NGINX, keep alive should be enabled by default. But if you see this directive, it’s disabled:


For shame.

Remove that directive to re-enable. Control how long connections are kept alive with the keepalive_timeout variable. This value is in seconds:
keepalive_timeout 10;

Apache should have Keep-Alive enabled by default, too. If it doesn’t, turn it on in the Apache configuration file (apache2.conf or httpd.conf).

KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5s

‘MaxKeepAliveRequests’ and ‘KeepAliveTimeout’ are additional variables that can be configured. ‘MaxKeepAliveRequests’ are the maximum number of requests allowed during a persistent connection. If you can, leave this number high. ‘KeepAliveTimeout’ is the number of seconds to wait for the next request from the persistent connection. Don’t set this too high—it could clobber your server by keeping connections open longer than necessary. Try 5-10 seconds, then tweak as necessary.

If you do not have access to the server’s Apache config file, try adding it to the .htaccess file:

<IfModule mod_headers.c>
    Header set Connection keep-alive

CDN and Caching Configurations

We talked about CDN basics in the Intermediate chapter. Time to dig deeper. These are all about the pipe:

Serving Static Content from a Cookieless Domain

When you’re really, really trying to squeeze every bit of performance out of your web site, it comes down to packets. And cookies. Browser cookies. Not chocolate-chip cookies.

Most cookies increase the size of even the tiniest file beyond a single packet. They also may require that the visiting browser upload more packets to the server, because servers read those cookies. Reduce the number of cookied files and you reduce file size plus the number of uploaded cookies.

But why do you need cookies? For dynamic sites, sure. And for web analytics. You don’t need to serve cookies (see what I did there?) for CSS files, or javascript, or all those little images and icons. Serve those from a cookieless domain and you improve reduce pipe use.

Compared to the other stuff in this chapter, this is an easy one. Set up a cookieless domain a couple of ways:

  1. Purchase a dedicated domain
  2. Setup a CNAME
  3. Turn off cookie delivery from that domain
  4. Serve static content from that domain

Many CDNs do this for you.


  1. Setup a dedicated subdomain
  2. Serve your static content from it. Again, use a CNAME

One note: Don’t set cookies at your top-level domain (yourdomain.com). If you do, then you’ll end up setting cookies on subdomains, as well. Set cookies on www.yourdomain.com instead. A few quick tips:

You can use the _setDomainName variable in your Google Analytics javascript:

['_setAccount', 'UA-xxxxxxx-1'],
['_setDomainName', 'www.example.com'],

For WordPress, add this PHP variable in your wp-config file:

define('COOKIE_DOMAIN', 'www.example.com');

One last bit

This chapter talked about the most complex, inch-by-inch performance improvements you can make. Do the Novice and Intermediate stuff, first.

And, of course, test before you launch.

Rest, weary friend. You’ve stared down the monster. But it’s dangerous to go alone. Onward to some favorite tools for the task at hand.

Chapter 7: Tools

New Call-to-action