WordPress is one of the most popular content platforms on the Internet. It powers the majority of all freshly released websites and has a huge user-community. Running a WordPress site enables editors to easily and regularly publish content which might very well end up on Hacker News – so let us make sure the web server is up to its job!

This setup will easily let you serve hundreds of thousands of users per day, including brief peaks of up to 100 concurrent users on a ~15$/month machine with only 2 cores and 2GB of RAM.

Outrageous Assumptions

This guide assumes that you have a working knowledge of content management systems, web servers and their configurations. Additionally, you should be familiar with installing, starting and stopping services on a remote server via ssh. In short: if you know how to work a CLI, you’ll be fine.

Step 0/9: Use the Source, or: Enjoy the Repository

If you don’t need to adhere to a strict company security policy, use the Dotdeb repository for Debian to gain access to newer versions of NGINX and PHP. For Varnish, get the Varnish Debian repository. They are the easiest to use.

Step 1/9: The Little Engine that Could

Let’s start by firing up your favorite package management tool, potentially with Super Cow Powers, and install NGINX. The following configuration optimizes NGINX for WordPress. Put it into /etc/nginx/nginx.conf to make NGINX use both CPU cores, do proper Gzipping and more. Note: single-line configurations shown here may extend over multiple lines for readability – take care when copying.

user www-data;
worker_processes 2;
pid /var/run/nginx.pid;
events {
    worker_connections 768;
    multi_accept on;
    use epoll;
http {
    # Let NGINX get the real client IP for its access logs
    real_ip_header X-Forwarded-For;
    # Basic Settings
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 20;
    client_max_body_size 15m;
    client_body_timeout 60;
    client_header_timeout 60;
    client_body_buffer_size  1K;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 8k;
    send_timeout 60;
    reset_timedout_connection on;
    types_hash_max_size 2048;
    server_tokens off;
    # server_names_hash_bucket_size 64;
    # server_name_in_redirect off;
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    # Logging Settings
    # access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    # Log Format
    log_format main '$remote_addr - $remote_user [$time_local] '
    '"$request" $status $body_bytes_sent "$http_referer" '
    '"$http_user_agent" "$http_x_forwarded_for"';
    # Gzip Settings
    gzip on;
    gzip_static on;
    gzip_disable "msie6";
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_min_length 512;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_types text/css text/javascript text/xml text/plain text/x-component 
    application/javascript application/x-javascript application/json 
    application/xml  application/rss+xml font/truetype application/x-font-ttf 
    font/opentype application/vnd.ms-fontobject image/svg+xml;
    # Virtual Host Configs
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;

In case your site will make use of custom webfonts, NGINX may need some help to deliver them with the correct MIME type – otherwise it will default to application/octet-stream. Edit /etc/nginx/mime.types and check that the webfont types are set to the following:

    application/vnd.ms-fontobject           eot;
    application/x-font-ttf                  ttf;
    font/opentype                           ott;
    application/font-woff                   woff;

Step 2/9: Who Enters my Domain?

Now that you have done the basic setup for NGNIX, let’s make it play nicely with your domain: remove the symlink to default from /etc/nginx/sites-enabled/, create a new configuration file at /etc/nginx/sites-available/ named yourdomain.tld and symlink to it at /etc/nginx/sites-enabled/yourdomain.tld. NGINX will use the new configuration file which we will fill now. Put the following into /etc/nginx/sites-available/yourdomain.tld:

server {
    # Default server block blacklisting all unconfigured access
    listen [::]:8080 default_server;
    server_name _;
    return 444;
server {
    # Configure the domain that will run WordPress
    server_name yourdomain.tld;
    listen [::]:8080 deferred;
    port_in_redirect off;
    server_tokens off;
    autoindex off;
    client_max_body_size 15m;
    client_body_buffer_size 128k;
    # WordPress needs to be in the webroot of /var/www/ in this case
    root /var/www;
    index index.html index.htm index.php;
    try_files $uri $uri/ /index.php?q=$uri&$args;
    # Define default caching of 24h
    expires 86400s;
    add_header Pragma public;
    add_header Cache-Control "max-age=86400, public, must-revalidate, proxy-revalidate";
    # deliver a static 404
    error_page 404 /404.html;
    location  /404.html {
    # Deliver 404 instead of 403 "Forbidden"
    error_page 403 = 404;
    # Do not allow access to files giving away your WordPress version
    location ~ /(\.|wp-config.php|readme.html|licence.txt) {
        return 404;
    # Add trailing slash to */wp-admin requests.
    rewrite /wp-admin$ $scheme://$host$uri/ permanent;
    # Don't log robots.txt requests
    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    # Rewrite for versioned CSS+JS via filemtime
    location ~* ^.+\.(css|js)$ {
        rewrite ^(.+)\.(\d+)\.(css|js)$ $1.$3 last;
        expires 31536000s;
        access_log off;
        log_not_found off;
        add_header Pragma public;
        add_header Cache-Control "max-age=31536000, public";
    # Aggressive caching for static files
    # If you alter static files often, please use 
    # add_header Cache-Control "max-age=31536000, public, must-revalidate, proxy-revalidate";
    location ~* \.(asf|asx|wax|wmv|wmx|avi|bmp|class|divx|doc|docx|eot|exe|
    wri|xla|xls|xlsx|xlt|xlw|zip)$ {
        expires 31536000s;
        access_log off;
        log_not_found off;
        add_header Pragma public;
        add_header Cache-Control "max-age=31536000, public";
    # pass PHP scripts to Fastcgi listening on Unix socket
    # Do not process them if inside WP uploads directory
    # If using Multisite or a custom uploads directory,
    # please set the */uploads/* directory in the regex below
    location ~* (^(?!(?:(?!(php|inc)).)*/uploads/).*?(php)) {
        try_files $uri = 404;
        fastcgi_split_path_info ^(.+.php)(.*)$;
        fastcgi_pass unix:/var/run/php-fpm.socket;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_intercept_errors on;
        fastcgi_ignore_client_abort off;
        fastcgi_connect_timeout 60;
        fastcgi_send_timeout 180;
        fastcgi_read_timeout 180;
        fastcgi_buffer_size 128k;
        fastcgi_buffers 4 256k;
        fastcgi_busy_buffers_size 256k;
        fastcgi_temp_file_write_size 256k;
    # Deny access to hidden files
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
# Redirect all www. queries to non-www
# Change in case your site is to be available at "www.yourdomain.tld"
server {
    listen [::]:8080;
    server_name www.yourdomain.tld;
    rewrite ^ $scheme://yourdomain.tld$request_uri? permanent;

This configuration is for a single-site WordPress install at /var/www/ webroot with a media upload limit of 15MB. Read the comments within the configuration if you want to change things: nginx -t is your friend when altering configuration files.

Step 3/9: Let’s get the Elephant into the Room

Next, we’ll get PHP-FPM onboard. Install it any way you like, preferably PHP version 5.4. Just make sure you get APC as well. After successful installation, we’ll need to edit several configuration files. Let’s start by editing the well-known /etc/php5/fpm/php.ini. It’s a huge file, so instead of showing the entire file here, please go through the following configuration line by line and set them to the respective values in your php.ini:

short_open_tag = Off
ignore_user_abort = Off
post_max_size = 15M
upload_max_filesize = 15M
default_charset = "UTF-8"
allow_url_fopen = Off
default_socket_timeout = 30
mysql.allow_persistent = Off

At the very end of your php.ini, add the following block which will configure APC, the opcode cache:

apc.stat = "0"
apc.max_file_size = "1M"
apc.localcache = "1"
apc.localcache.size = "256"
apc.shm_segments = "1"
apc.ttl = "3600"
apc.user_ttl = "7200"
apc.gc_ttl = "3600"
apc.cache_by_default = "1"
apc.filters = ""
apc.write_lock = "1"
apc.num_files_hint= "512"
apc.shm_size = "256M"
apc.include_once_override = "0"
apc.canonicalize = "1"
;This should be used when you are finished with PHP file changes.
;As you must clear the APC cache to recompile already cached files.
;If you are still developing, set this to 1.

In your /etc/php5/fpm/php-fpm.conf, please set the following lines to their respective values:

pid = /var/run/php5-fpm.pid
error_log = /var/log/php5-fpm.log
emergency_restart_threshold = 5
emergency_restart_interval = 2
events.mechanism = epoll

Finally, in /etc/php5/fpm/pool.d/www.conf, do the same procedure again:

user = www-data
group = www-data
listen = /var/run/php-fpm.socket
listen.owner = www-data
listen.group = www-data
listen.mode = 0666
listen.allowed_clients =
pm = dynamic
pm.max_children = 50
pm.start_servers = 15
pm.min_spare_servers = 5
pm.max_spare_servers = 25
pm.process_idle_timeout = 60s
request_terminate_timeout = 30
security.limit_extensions = .php

and add the following to the end of it:

php_flag[display_errors] = off
php_admin_value[error_reporting] = 0
php_admin_value[error_log] = /var/log/php5-fpm.log
php_admin_flag[log_errors] = on
php_admin_value[memory_limit] = 128M

Kudos! You have just configured PHP to run on a high performance UNIX socket, spawn child processes to deal with requests and cache everything so that there’s less load on the system.

Step 4/9: Give Varnish a Polish

At this point, you already have a lean, mean webserving machine – albeit on port 8080. Now imagine ~95% of all your incoming traffic hitting an additional layer above which keeps static content in RAM. PHP won’t even have to process anything most of the time and your database will receive more load from editors adding new content than from queries caused by the frontend. That’s worth the extra mile, right? So let’s go install Varnish!

After installing, edit /etc/default/varnish to make it use the two cores, static content in RAM and proper timeouts:

DAEMON_OPTS="-a :80 \
    -T localhost:6082 \
    -f /etc/varnish/default.vcl \
    -u www-data -g www-data \
    -S /etc/varnish/secret \
    -p thread_pools=2 \
    -p thread_pool_min=25 \
    -p thread_pool_max=250 \
    -p thread_pool_add_delay=2 \
    -p session_linger=50 \
    -p sess_workspace=262144 \
    -p cli_timeout=40 \
    -s malloc,768m"

And for the very last step in this web server stack setup, put the following configuration into /etc/varnish/default – it’s well commented so you can see what each part does:

# We only have one backend to define: NGINX
backend default {
    .host = "";
    .port = "8080";
# Only allow purging from specific IPs      
acl purge {
sub vcl_recv {
    # Handle compression correctly. Different browsers send different
    # "Accept-Encoding" headers, even though they mostly support the same
    # compression mechanisms. By consolidating compression headers into
    # a consistent format, we reduce the cache size and get more hits.
    # @see: http:// varnish.projects.linpro.no/wiki/FAQ/Compression
    if (req.http.Accept-Encoding) {
        if (req.http.Accept-Encoding ~ "gzip") {
            # If the browser supports it, we'll use gzip.
            set req.http.Accept-Encoding = "gzip";
        else if (req.http.Accept-Encoding ~ "deflate") {
            # Next, try deflate if it is supported.
            set req.http.Accept-Encoding = "deflate";
        else {
            # Unknown algorithm. Remove it and send unencoded.
            unset req.http.Accept-Encoding;
    # Set client IP
    if (req.http.x-forwarded-for) {
        set req.http.X-Forwarded-For =
        req.http.X-Forwarded-For + ", " + client.ip;
    } else {
        set req.http.X-Forwarded-For = client.ip;
    # Check if we may purge (only localhost)
    if (req.request == "PURGE") {
        if (!client.ip ~ purge) {
            error 405 "Not allowed.";
    if (req.request != "GET" &&
        req.request != "HEAD" &&
        req.request != "PUT" &&
        req.request != "POST" &&
        req.request != "TRACE" &&
        req.request != "OPTIONS" &&
        req.request != "DELETE") {
            # /* Non-RFC2616 or CONNECT which is weird. */
            return (pipe);
    if (req.request != "GET" && req.request != "HEAD") {
        # /* We only deal with GET and HEAD by default */
        return (pass);
    # admin users always miss the cache
    if( req.url ~ "^/wp-(login|admin)" || 
        req.http.Cookie ~ "wordpress_logged_in_" ){
            return (pass);
    # Remove cookies set by Google Analytics (pattern: '__utmABC')
    if (req.http.Cookie) {
        set req.http.Cookie = regsuball(req.http.Cookie,
            "(^|; ) *__utm.=[^;]+;? *", "\1");
        if (req.http.Cookie == "") {
            remove req.http.Cookie;
    # always pass through POST requests and those with basic auth
    if (req.http.Authorization || req.request == "POST") {
        return (pass);
    # Do not cache these paths
    if (req.url ~ "^/wp-cron\.php$" ||
        req.url ~ "^/xmlrpc\.php$" ||
        req.url ~ "^/wp-admin/.*$" ||
        req.url ~ "^/wp-includes/.*$" ||
        req.url ~ "\?s=") {
            return (pass);
    # Define the default grace period to serve cached content
    set req.grace = 30s;
    # By ignoring any other cookies, it is now ok to get a page
    unset req.http.Cookie;
    return (lookup);
sub vcl_fetch {
    # remove some headers we never want to see
    unset beresp.http.Server;
    unset beresp.http.X-Powered-By;
    # only allow cookies to be set if we're in admin area
    if( beresp.http.Set-Cookie && req.url !~ "^/wp-(login|admin)" ){
        unset beresp.http.Set-Cookie;
    # don't cache response to posted requests or those with basic auth
    if ( req.request == "POST" || req.http.Authorization ) {
        return (hit_for_pass);
    # don't cache search results
    if( req.url ~ "\?s=" ){
        return (hit_for_pass);
    # only cache status ok
    if ( beresp.status != 200 ) {
        return (hit_for_pass);
    # If our backend returns 5xx status this will reset the grace time
    # set in vcl_recv so that cached content will be served and 
    # the unhealthy backend will not be hammered by requests
    if (beresp.status == 500) {
        set beresp.grace = 60s;
        return (restart);
    # GZip the cached content if possible
    if (beresp.http.content-type ~ "text") {
        set beresp.do_gzip = true;
    # if nothing abovce matched it is now ok to cache the response
    set beresp.ttl = 24h;
    return (deliver);
sub vcl_deliver {
    # remove some headers added by varnish
    unset resp.http.Via;
    unset resp.http.X-Varnish;
sub vcl_hit {
    # Set up invalidation of the cache so purging gets done properly
    if (req.request == "PURGE") {
        error 200 "Purged.";
    return (deliver);
sub vcl_miss {
    # Set up invalidation of the cache so purging gets done properly
    if (req.request == "PURGE") {
        error 200 "Purged.";
    return (fetch);
sub vcl_error {
    if (obj.status == 503) {
                # set obj.http.location = req.http.Location;
                set obj.status = 404;
        set obj.response = "Not Found";
                return (deliver);

This configuration will deliver high speed, gzipped content, caching aggressively while letting authenticated backend users view the live site uncached. It also protects the stack from unwelcome traffic while allowing cache purges from certain sources.

Step 5/9: Code Is Poetry

If you didn’t already have WordPress installed before, do it now. There’s plenty of good guidance on this already. Should you come across odd reloads of the same step during install, restart the three services NGINX, PHP-FPM and Varnish on your machine.

After successful installation of WordPress, we’ll need to make it talk to the high performance webserver stack we’ve just built for it. Let’s start by reading, understanding and installing the NGINX compatibility plugin for WordPress. Don’t forget to follow the instructions on the plugin site. Now, WordPress can deal with NGINX.

Next, let’s make WordPress work with PHP’s opcode cache APC: Read, understand and then install the Object-Cache plugin for WordPress.

The final step is to empower WordPress so it can purge the Varnish cache. Again, read, understand and only then install Pål-Kristian Hamre’s Varnish plugin for WordPress. Afterwards, go to its configuration page within the WordPress admin-interface and set the following parameters:

Varnish Administration IP Address:
Varnish Administration Port: 80
Varnish Secret: (get it at /etc/varnish/secret)
Check: "Also purge all page navigation" and "Also purge all comment navigation"
Varnish Version: 3

And that’s all concerning the high performance webserver stack, folks! Your WordPress will now handle a great deal of traffic while keeping TTFB to a minimum. The times of high CPU load and disk I/O troubles are now past.

The Performance Golden Rule

Steve Souders’ Performance Golden Rule clearly states that most performance gains can be achieved by optimizing the frontend side of things instead of scaling the backend to be able to deal with a huge amount of HTTP request. Or, as Patrick Meenan puts it, one should always question “the need to make the requests in the first place”. So let’s not stop at our high performance webserver, but continue with a high performance frontend!

Step 6/9: Bust that Cache Wide Open!

If you have read the configuration files for NGINX, you will have noticed some lines about a cache-buster for CSS and JS files. What it does is altering the link to your theme’s unified stylesheet and JS to something like style.1352707822.css, in which the number is the filemtime. So whenever you alter your files, their apparent filename within the site will change and clients will download the newest version of those files while you don’t need to alter any paths manually. Simply place the following into your theme’s functions.php:

 * Automated cache-buster function via filemtime
function autoVer($url){
  $name = explode('.', $url);
  $lastext = array_pop($name);
    filemtime($_SERVER['DOCUMENT_ROOT'] . parse_url($url, PHP_URL_PATH)), 
  echo implode('.', $name) ;

Now use the autoVer function when including CSS and JS, e.g. in your functions.php (see wp_register_style) or header.php. Examples:

# Add in your theme's functions.php:
  get_bloginfo('stylesheet_directory') . autoVer('style.css'), 
# Add in your theme's header.php:

Step 7/9: Avoid the wicked @import for WordPress child themes

WordPress child themes rely on @import in their CSS file to get the styles from their parent theme. Sadly, @import is a wicked load-blocker. Here’s a simple trick to have two stylesheets included via link in the website’s header if it’s a child theme because stylesheets called via link can download in parallel. Remove the @import from your child-theme’s CSS file and place the following PHP snippet into your header.php when linking your stylesheets:

     * Circumvent @import CSS for WordPress child themes
     * If we're in a child theme, build links for both parent and child CSS
     * This way, we can remove the @import from the child theme's style.css
     * CSS loaded via link can load simultaneously, while @import blocks loading
     * See: http://www.stevesouders.com/blog/2009/04/09/dont-use-import/
    if(is_child_theme()) {
        echo '<link rel="stylesheet" href="';
        echo '" />'."\n\t\t";
        echo '<link rel="stylesheet" href="';
        echo '" />'."\n";
    } else {
        echo '<link rel="stylesheet" href="';
        echo '" />'."\n";

Additionally, you should use @media print within your unified stylesheet because, as Stoyan Stefanov has pointed out, browsers will always download all stylesheets no matter the medium.

Step 8/9: Unification Day

After combining your CSS into a unified stylesheet and making it load in a non-blocking manner, you should think about minifying it. There are plenty of decent minification tools around.

Also, you can enable loading JS libraries from Google and other CDNs there. No matter which libraries you use, unify and compress you local JS with e.g. Google Closure Compiler. If you’re using Google Analytics, check out this excellent GA snippet by Mathias Bynens which you can easily integrate into your unified JS. If you’re not using jQuery on the frontend, deregister its default inclusion via your theme’s “functions.php”.

The last thing to be minified is the website source code outputted by WordPress. There are several minification plugins either as standalone or integrated into WP Super Cache or WP Total Cache plugins. I recommend “WP HTML Compression”, especially when used together with the Roots Theme

Step 9/9: Famous Last Words

In case the imperfections of the code outputted by WordPress keep you up at night, your performance optimizations don’t have to end here. Check out the Roots Theme, which finally brings decent relative URL structures, clean navigation menus and much more to WordPress. It’s not as easy as installing a common theme or plugin, but worth it.

If you ever dreamed of using Nicole Sullivan’s excellent OOCSS high performance CSS selectors with WordPress, check out the PHP DOMDocument parser to filter the output of WordPress and set classes for all relevant DOM objects in your source so you can adhere to the OOCSS code standards. While parsing the entire website output during creation by WordPress may seem excessive, consider that it finally gives you total control over syntax and defined classes while editors can use the familiar WordPress interface to manage content. And with such a mighty & multi-layered caching system in place, the frontend won’t slow down at all.

So Long, and Thanks for All the Time

If you’ve reached the point that you’re worrying about the performance implications of CSS selectors and the execution time of PHP’s DOMDocument, it is time to thank you. You have very likely managed to reduce your loading time of your WordPress site by more than 3 seconds. This means that with ~10.000 visitors per day, you’ve saved them about 8 hours of time collectively. That’s amazing and more than enough time to sit back and enjoy some of the season’s spirit – you’ve earned it!


Tobias Baldauf (@tbaldauf) is a freelance web-performance consultant with a passion for full stack web development. He created vital interfaces for European train-operations, tools for the world’s largest peering exchange, gives talks on web performance and organizes the Cologne Web Performance meetup.

93 Responses to “Using NGINX, PHP-FPM+APC and Varnish to make WordPress Websites fly”

  1. Die Adventskalender am 7. Dezember « F-LOG-GE

    [...] verweist auf einen Link, der vor allem für JavaScript-Profis interessant sein dürfte. Beim Performancekalender gab es gestern einen Artikel über Optimierungen für [...]

  2. Joseph Scott

    I’m curious, does Varnish perform significantly faster than Nginx at serving cached or static content? If the files are cached to disk (or are static) then the operating system will be caching them as well.

  3. Pothi Kalimuthu

    Thanks for the great article, Tobias!

    @Joseph For static content, here is a direct quote from Per Buer, CEO of Varnish Software….

    Varnish is a really crappy file server and probably won’t become a very good one. :-) And Nginx is really, really good at picking up files from the file system and serving them.

    For cached content that is stored in memory, I haven’t tested on what works best.

    When some clients insist on using WP Super Cache or W3 Total Cache for some reason, I’d go with disk based cache and let it served via Nginx.

    Personally, I prefer Nginx/php-fpm => Varnish => Nginx/php-fpm stack where Nginx listens on port 80 serving static files, Varnish caches (only) the dynamic content, and all the WP backend operations are done by Nginx/php-fpm without going through Varnish.

  4. Today’s Readings | Aaron T. Grogg

    [...] been there so long, I just hate the idea of moving…)? Well I’ve come across two articles that caught my attention, now I just need the time and energy to try [...]

  5. WordPress: die CSS-Datei des Child-Themes performance-freundlicher einbinden | WordPress & Webwork

    [...] habe ich das Code-Beispiel auf dieser Website auf die ich wiederum durch den Artikel von Jens aufmerksam geworden [...]

  6. Using NGINX, PHP-FPM+APC and Varnish to make WordPress Websites fly | VietHiP

    [...] (via calendar.perfplanet.com) Share this:TwitterFacebookLike this:LikeBe the first to like this. December 8, 2012 by VietHiP Categories: Nginx, Web server | Leave a comment [...]

  7. Ben Word

    Great article! Instead of editing your header template for the child theme stylesheet, you could use conditionals within a function that hooks into wp_enqueue_scripts. Here’s an example from Roots Theme:


  8. editor

    Thanks, Ben! People had already told me to use wp_enqueue & I thought about how to change my code to reflect this. Great to see that your Roots theme has implemented this already!

  9. Thomas

    Glimpsed over the article and looks quite good at first sight, will re-read it later when i have more time.

    One question though: Do those nginx optimisations help with other software as well like Typo3 or Magento installations?

  10. Tobias

    @Thomas: The config for /etc/nginx/nginx.conf is quite independent of CMS etc.

    The stuff at /etc/nginx/sites-available/yourdomain.tld is partially WordPress-specific: the 404, the location blocks for readme.html etc., the directory settings, the exclusion of the uploads-directory for PHP execution etc. … You will have to go through these and change them to suit your setup. I’m sure there a plenty of good guides for Typo3 on NGINX out there.

    Remember that you’ll also need to change the config for Varnish as well as make Typo3 able to work with APC and purge the caches, esp. of Varnish.

  11. Jozef

    Great post and instructions, really appreciated, thanks! You might want to replace all & with only & to make it direct copy paste.

  12. Jozef

    & amp ;

  13. The WordPress Weekend Roundup - WP Daily

    [...] 2. Optimizing NGINX, PHP-FPM+APC and Varnish [...]

  14. Darren

    Great article! I’ve started working with WP/Nginx quite a bit lately, so this is all very interesting to me. I looked closely at your example server{} config, and I noticed a couple of items that either appear to be bugs, or I just don’t understand them. So I’ll mention them here in my best “constructive criticism” voice, and maybe you could clarify whether these directives were intentional (and purposeful) or simply an oversight.

    For example, I don’t understand why this is necessary:

    # Add trailing slash to */wp-admin requests.
    rewrite /wp-admin$ $scheme://$host$uri/ permanent;

    I don’t have that directive in my config, but I haven’t had any issues. Seems to me that requests for /wp-admin would already be caught by this:

    try_files $uri $uri/ /index.php?q=$uri&$args;

    Is this redundant, or am I missing something?

    I was also wondering about this part:

    # deliver a static 404
    error_page 404 /404.html;
    location /404.html {

    # Deliver 404 instead of 403 "Forbidden"
    error_page 403 = 404;

    First, does this imply that we should create a static 404 page and save it in the web root? And if so, why doesn’t WordPress generate an error page (like it does on a standard Apache setup)?

    Second, what is the reasoning behind error_page 403 = 404?

    I haven’t seen this syntax before, so I tried this in my configuration, and the result was that the request (for a forbidden URL) was 302 redirected to /404, which doesn’t exist, so I got a 404.

    Shouldn’t it be one of these?

    # Return 404 status and serve 404.html error page
    error_page 403 =404 /404.html

    # Return 403 status and serve 404.html error page
    error_page 403 /404.html

    If your intent is simply to reuse the static 404.html error page for both types of errors (i.e., my second example above), then that makes sense. But if your intention is to actually alter the status code (i.e., your article’s version or my first example above), then what is the reasoning?

    One last thing I’ll mention…

    I’m guessing the following should say license.txt:

    # Do not allow access to files giving away your WordPress version
    location ~ /(\.|wp-config.php|readme.html|licence.txt) {
    return 404;


  15. Jason McCreary

    The article would benefit from a diagram. Something to help visualize the flow from Varnish -> nginx -> PHP on various requests. Otherwise, great tutorial.

  16. Per Buer


    @Joseph Scott:

    Varnish can potentially serve content faster than Nginx because it has stored it in memory and it would not require any syscalls to access that content.

    However, the difference (if any) would be minuscule and not notable. As you point out the Linux page cache would make sure that the content is kept in memory and then the cost of a couple of syscalls won’t do much.

    Nginx is blistering fast and a well written product (I’m a big fan) and we (Varnish Software) don’t recommend people setting up Varnish in front of it when only serving static content.

  17. David Barkley

    My bird dogs sent me a couple of properties in Oklahoma and Memphis.  I must have occasion to evaluate them tomorrow.  It’s first-class to have friends!

  18. Hakan Altun

    thanks for clear explanation,

    Is the second step intended for a single WP site on a host, or am I supposed to do it for each WP site on a single host?

  19. Performance Calendar » Using NGINX, PHP-FPM+APC and Varnish to make WordPress Websites fly | Links di Davide Salerno

    [...] Performance Calendar » Using NGINX, PHP-FPM+APC and Varnish to make WordPress Websites fly. [...]

  20. Mikkie

    Was this intended for Varnish 2.x or 3.x? Because that vcl config is spitting out tons of errors on Varnish 3

  21. Mikkie

    Also, i doubt the port was set correctly here ;)

    Varnish Administration IP Address:
    Varnish Administration Port: 80

  22. JR

    You need to replace the ampersand html code with actual ampersands (two of them).

    It should be && instead of the text that is written.

    You should probably do the correction.

  23. Dan Grossman

    I would highly recommend connecting to the PHP-FPM pool over TCP rather than a socket. You might lose 1-2% of potential throughput, but past a few hundred connections a second, the socket is going to start dropping connections left and right… I’m talking 10-20% of requests getting a bad gateway error with nginx reporting “resource unavailable” on the socket. Alternatively, you’d have to make multiple pools listening on different sockets and have nginx load balance across them. That’s been my experience so far.

  24. Lee

    awesome post.. i have an old machine in the back i want to experiment with now.
    The Accept-Encoding ~ “gzip” is built into the code there, its good for speed and seo!

  25. Pierre-Jean

    Nice article ! I was particulary interested by autoVer function, but unfortunatly I didn’t manage to make it work. I just have “style..css” appearing :)

  26. Hello World! | zach.mn

    [...] I am still learning about all of the different caching options and whatnot, but I recently found this article on optimizing WordPress to the max using nginx/php-fpm+apc and Varnish.  I haven’t gone so [...]

  27. MrFurious

    For Pierre-Jean and any others who have trouble with the autoVer function, I got it to work by replacing $_SERVER['DOCUMENT_ROOT'] with get_template_directory() and using return in the last line instead of echo (if you use the wp_register_style variant in your theme’s functions.php). All good stuff here! Thanks and cheers.

  28. Scalability comparison of Wordpress with NGINX/PHP-FCM and Apache on an ec2-micro instance.Adventures in HttpContext | Adventures in HttpContext

    [...] straightforward nginx/php guide on Tod Sul. I also relied on an nginx tuning guide from Dakini and this nginx/wordpress tuning guide from perfplanet. They both have excellent [...]

  29. Kendrick

    But it’s always wise to check in with a doctor before you start using a supplement regularly. Skiing and snowboarding are strenuous sports which demand a lot of endurance, so your usual set of ten to fifteen reps really isn”. Smoking can affect your health and fitness since it affects all the bodily functions and damages vocal cords and skin.

    Feel free to surf to my web blog: Kendrick

  30. Paul Thomson

    Hey Tobias,

    Great post! Thanks for taking the time to write it, has helped fine tune my server quite nicely.

    I was wondering how best to go about introducing SSL to bypass Varnish? I’m got a WordPress site setup and trying to set up for secure login but I think varnish is getting in the way, ideally I would like NGINX to handle all requests on port 443.

    Any help would be greatly appreciated…

    Paul :-)

  31. Marc


    Thanks for the article, great help. However, I am getting error

    nginx: [emerg] invalid number of arguments in “location” directive in /etc/nginx/sites-enabled/example.com:70

    Is there a fix for this?

  32. Jeromy Postert

    I as well as my friends happened to be going through the great ideas on your web blog and so quickly developed a terrible suspicion I had not thanked the web blog owner for those techniques. These young men became joyful to read through all of them and already have simply been taking pleasure in those things. Many thanks for truly being quite considerate and then for obtaining variety of essential information millions of individuals are really needing to be informed on. Our own sincere regret for not saying thanks to you earlier.

  33. Sam

    Definitely a very helpful article. One question:

    In the sites-available configuration for nginx, you say “If using Multisite or a custom uploads directory, please set the */uploads/* directory in the regex below”

    Can you give an example of how the location directive would be different for multisite?


  34. Souza


    “”"”nginx: [emerg] invalid number of arguments in “location” directive in /etc/nginx/sites-enabled/example.com:70

    Is there a fix for this?”"”"”

    Just join the lines! :) Do this instead of using news lines:

    location ~* \.(asf|asx|wax|wmv|wmx|avi|bmp|class|divx|doc|docx|eot|exe|gif|gz|gzip|ico|jpg|jpeg|jpe|mdb|mid|midi|mov|qt|mp3|m4a|mp4|m4v|mpeg|mpg|mpe|mpp|odb|odc|odf|odg|odp|ods|odt|ogg|ogv|otf|pdf|png|pot|pps|ppt|pptx|ra|ram|svg|svgz|swf|tar|t?gz|tif|tiff|ttf|wav|webm|wma|woff|wri|xla|xls|xlsx|xlt|xlw|zip)$ {

  35. Speeding up a WordPress site part 2 | Chris's blog

    [...] this combination. The one I’ve found is the best guide out there for a good start is this: Using Nginx, PHP-FPM, APC and Varnish. I a couple of small problems using that guide on a freshly installed CentOS box. I installed [...]

  36. get information golden retriever stuff

    Your means of telling the whole thing in this paragraph
    is actually good, every one be able to without difficulty know it, Thanks a lot.

  37. dating tips blog

    I love your wp web template, where did you download it through?

    Your web site does not show up correctly on my apple iphone – you might want
    to try and repair that
    Audio began playing anytime I opened up this internet site, so
    I believe one of your commercials caused my browser to resize,
    you may well want to put that on your blacklist.

    I just added this website to my feed reader, excellent stuff.

    Can not get enough!
    This is a excellent web site, could you be involved in doing an interview regarding just how you developed it?

    If so e-mail me!
    Thank you, I’ve recently been seeking for info about this topic for ages and yours is the best I have discovered so far.

  38. To Boldly Monitor What No One Has Monitored Before | tobias.is

    [...] the Performance Advent Calendar, I described how to setup a high performance webserver. As a follow-up, let’s look into monitoring this [...]

  39. vcl

    Error on default.vcl:

    root@s1:~# sudo /etc/init.d/varnish start
    * Starting HTTP accelerator varnishd [fail]
    Message from VCC-compiler:
    Expected ‘)’ got ‘&’
    (program line 73), at
    (‘input’ Line 52 Pos 30)

  40. Jan Koch

    This post finally enabled me to find huge errors in my configuration!
    I head reoccurring 502 errors caused by PHP-FPM and now there gone, by adapting your configuration!


  41. Jan Koch

    This post finally enabled me to find huge errors in my configuration!
    I had reoccurring 502 errors caused by PHP-FPM and now there gone, by adapting your configuration!


  42. Siegfried

    Wow! thats what i call great tutorial!
    would it be possible on shared server?
    best regards

  43. Jomz


    What is your website that can serve hundreds to thousands of users a day for only $15/month with only 2 cores and 2GB of RAM? I want to save money also on my web hosting.

    Please email me if you can’t share your website here. Or please let me know if perfplanet.com is what you mean in your article here.


  44. vimax

    Hello my friend! I wish to say that this post is awesome, nice written and include
    almost all important infos. I would like to look extra
    posts like this .

  45. test

    Hi there, I desire to subscribe for this weblog to get hottest updates, therefore where
    can i do it please help out.

  46. Were To Buy Revatio

    . Erectile dysfunction (ED or impotence) occurs
    when a man is not capable of maintaining an erection or
    achieving of the penis during sexual intercourse.

    Penile erection normally occurs when a man is sexually aroused, perhaps by what he sees, hears,
    thinks or even smells. In this state, blood is diverted from the brain to sponge-like tissues lining
    the penis, leading to an increase in length, size and firmness of the penis.

    This is known as an erection.

    For more info about Were To Buy Revatio click this link Were To Buy Revatio to gather more info.

  47. c class ip

    “I like this website very much so much great information.”

  48. Odessa Devilla

    “hey there, this might be little offtopic, but i am hosting my site on hostgator and they’ll suspend my hosting in 4days, so i would like to ask you which hosting do you use or suggest?”

  49. Nginx Consultant

    Interesting read. I am a huge fan of Nginx. Will definitely spin this up on a VPS I just put into action. I have 40 + domains with around 2.3M pageviews a month. Configuring numerous sites like this will be a huge undertaking. Maybe one a day for a month or two. HA!

  50. Dharmaputra

    Very nice article.
    There were few copy-paste issues already mentioned above.

    After I applied the suggestion above, I cannot access my wp-admin, does anybody know why?

    I think something to do with no caching for wp-login and wp-admin, thank you for you suggestion.

    peace and smile,

  51. Misho

    Great tutorial, now my wordpress working 2x faster. But you have a litle mistakes.
    Varnish config && need only &&
    fastcgi_pass unix:/var/run/php-fpm.socket;
    fastcgi_pass unix:/var/run/php5-fpm.sock;

    Please correct thate mistakes. For us beginners it is difficult to understand.


  52. Chanell Keech

    Wonderful blog! Do you have any suggestions for aspiring writers? I’m hoping to start my own website soon but I’m a little lost on everything. Would you advise starting with a free platform like WordPress or go for a paid option? There are so many choices out there that I’m completely confused .. Any ideas? Kudos!

  53. Santiago

    Thanks for your incredible work.
    May I ask what would be better (RAM/PERFORMANCE) to host some WP sites:
    a) LEMP (PHP-FPM)
    b) Nginx as a Front End Proxy for Apache
    I did google it but I have not found any answer.

  54. CRx Consult

    Thanks for this tutorial! I have just finished installing similar setup for magento ecommerce sites and soon all my wp sites will be upgraded also.

  55. ダウンジャケット 品質

    I always used to study piece of writing in news
    papers but now as I am a user of web thus from now
    I am using net for articles, thanks to web.

    my website … ダウンジャケット 品質

  56. NGINX PHP-FPM 5.5 Zend OPCache Varnish 3.0.4 and WordPress | Serene Code

    […] is an excellent guide on how to install NGINX, PHP-FPM+APC and Varnish here […]

  57. corin

    First of all: Great post! Thank you so much!
    I had a few small problems:
    -the /etc/nginx/sites-available/yourdomain.tld has unnecessary newlines (the “location ~* \.(asf|asx|…”) which have to be removed (but you even warned about that in your post)
    -in /etc/default/varnish: “-p sess_workspace=262144 \”, at least on arch linux, has to be: “-p shm_workspace=262144 \”
    -in /etc/varnish/default there are multiple occurences of “&&”, this seems to be some kind of encoding error; “&&” would be right.
    greets, corin

  58. youtube clip iherb

    Every weekend i used to pay a quick visit this web
    site, as i want enjoyment, since this this web page conations really pleasant
    funny information too.

  59. WordPress on VPS with Nginx and PHP APC | TechSlides

    […] Personally, I can do with less mail and prefer mysql command line. Next, I am going to look into Varnish and […]

  60. YOLO

    Excellent, what a weblog it is! This weblog provides useful information to us,
    keep it up.

  61. Ed Mandell

    i dont understand why you’d need object caching if youve already got page caching?

    surely if everything is cached at the page level it doesnt need to be cached again at the object level or am i missing something

  62. Matthew Marable

    Thanks for the awesome tutorial, works like a charm!

  63. kasino

    Way cool! Some extremely valid points! I appreciate you
    penning this article and the rest of the website is also really good.

  64. Yazeed Al Oyoun

    Someone mentioned this already but it’s still in the article.

    try_files $uri $uri/ /index.php?q=$uri&$args;

    should be

    try_files $uri $uri/ /index.php?q=$uri&&$args;

  65. jig saw reviews

    It’s an amazing piece of writing designed for all the web visitors; they will obtain advantage from
    it I am sure.

    Feel free to surf to my blog :: jig saw reviews

  66. best circular saw

    Thank you for the auspicious writeup. It in reality used to be
    a leisure account it. Glance complicated to far brought agreeable from you!
    By the way, how can we be in contact?

    Take a look at my webpage; best circular saw

  67. shyness

    We must make sure we don’t use technology as being a
    crutch in order to avoid physical social interaction.
    Managing feelings of shyness can be an ongoing process.
    However, whether it turns out that you might be feeling
    lonely and isolated when you don’t have enough close friendships and
    relationships in your lifetime, you will need to decide what steps you’ll want to take to improve your situation.

  68. กำจัดปลวกอุดร

    My developer is trying to convince me to move to .net from PHP.
    I have always disliked the idea because of the expenses.

    But he’s tryiong none the less. I’ve been using WordPress on a number of websites for
    about a year and am worried about switching to another platform.

    I have heard excellent things about blogengine.net.

    Is there a way I can import all my wordpress posts into it?
    Any help would be really appreciated!

  69. stainless steel cookware reviews

    Fantastic blog! Do you have any helpful hints for aspiring writers?
    I’m planning to start my own website soon but I’m a little
    lost on everything. Would you recommend starting with a free platform like WordPress or go for a
    paid option? There are so many options out there that I’m completely overwhelmed ..
    Any ideas? Kudos!

    my blog; stainless steel cookware reviews

  70. best popcorn popper

    My family every time say that I am wasting my time here at web, except I know I am getting know-how all the time by reading thes nice posts.

    Here is my web-site :: best popcorn popper

  71. cordless vacuum reviews

    Thanks for finally writing about > Performance Calendar Using NGINX, PHP-FPM+APC and Varnish to make WordPress Websites
    fly < Loved it!

    Also visit my web site – cordless vacuum

  72. best car amplifier

    I’m impressed, I must say. Rarely do I encounter a blog that’s both
    educative and entertaining, and without a doubt, you’ve hit the nail on the
    head. The problem is something that too few people are speaking intelligently about.
    I am very happy I came across this in my search for something relating to this.

    Here is my page – best car amplifier

  73. best bike lights

    It’s a shame you don’t have a donate button! I’d certainly donate to this superb blog!

    I guess for now i’ll settle for book-marking and adding your RSS feed to my Google account.
    I look forward to fresh updates and will talk about this blog with my Facebook group.
    Chat soon!

    Also visit my web blog best bike lights

  74. best mouthwash

    Fantastic goods from you, man. I’ve understand your stuff previous to and you are just extremely magnificent.
    I really like what you have acquired here, certainly like what you’re stating and
    the way in which you say it. You make it entertaining and you still care for to keep it smart.

    I can’t wait to read much more from you. This is really
    a wonderful site.

    My website best mouthwash

  75. pellet stove reviews

    Hi there it’s me, I am also visiting this web page regularly, this web site is truly pleasant and the viewers are really
    sharing good thoughts.

    Feel free to visit my webpage pellet stove reviews

  76. security systems reviews

    Thank you for the good writeup. It in truth was once a
    amusement account it. Glance advanced to more added agreeable from you!
    By the way, how can we keep in touch?

    Have a look at my weblog security systems reviews

  77. best wine refrigerator

    This is very interesting, You’re a very skilled blogger.
    I’ve joined your feed and look forward to seeking more of your magnificent post.
    Also, I’ve shared your website in my social

    My webpage … best wine refrigerator

  78. best herb grinder reviews

    Hello, its good paragraph regarding media print, we all know media
    is a fantastic source of data.

  79. Renata

    Thank you for this article – it was very helpful and informative.

  80. best upright freezer

    Hello, just wanted to tell you, I enjoyed this article. It was helpful.
    Keep on posting!

    Here is my blog post – best upright freezer

  81. http://petersonjwip.areavoices.com/2014/01/14/exactly-what-transforms-a-day-off

    Very good information. Lucky me I found your website by chance (stumbleupon).
    I’ve book marked it for later!

  82. xplocial review

    Hello, Neat post. There’s a problem along with your web site
    in internet explorer, would check this? IE nonetheless
    is the market chief and a big element of people will miss your magnificent writing because
    of this problem.

  83. Pro Binary Signal

    Howdy, i read your blog from time to time and i own a similar
    one and i was just wondering if you get a lot of spam feedback?
    If so how do you prevent it, any plugin or anything you
    can advise? I get so much lately it’s driving me crazy so any assistance is very much appreciated.

  84. Bernie

    Amazing! This blog looks exactly like my old one! It’s on a entirely different subject but it
    has pretty much the same layout and design. Outstanding choice of colors!

  85. Nginx & Varnish connection error - WordPress BuddyPress Tweaks

    […] /etc/varnish/default.vcl file is copied from the tutorial. All & has been corrected to &. It is a fresh VPS. No firewall. Any clue to resolve it? […]

  86. best online trading platform

    Direction following might backbone on the trade approach.

    Due towards the lack of volume, their day trading software won.
    Everyone knows that a stock’s price pops whenever it gets

  87. free gps app For iphone

    Not only is the operating system highly customisable and expandable
    thanks to downloadable apps, but the processor makes for
    super fast loading times and allows demanding applications to run with ease.
    You can find constellations, planets, stars, or a see your first UFO:).
    It has three main parts, pages for your documents, keynote for your presentations, and numbers
    for charts and graphs, making it an ideal app to handle day-to-day business document editing and processing with much ease and convenience.

    Here is my web-site :: free gps app For iphone

  88. game of war Fire age no survey

    Military icons from the Military Icon Set can be viewed online at.
    They can act as stress busters as they keep you concentrated and you will never get time to think about
    other things. I will limit this blog to the 3 most important topics to master to become a winning player at PLO8.

    Review my blog … game of war Fire age no survey

  89. asphalt download free

    These all aggregates are mixed in a correct proportion and temperature.
    Using a special sealing coat on the asphalt will help to create a
    barrier that stops the gas or other petroleum products from reacting with
    the asphalt and weakening it. Metal roofing
    is also lightweight, easy to maintain, and aesthetically appealing,
    just like asphalt shingles.

    My webpage; asphalt download free

  90. best iphone games

    iPhone made the most out of this significant upturn in the prolific phase of
    Smart Phone and made its presence count and cashed the season with the insightful launch of its application portfolios segregated in varied dynamics of promising value leads.
    The app features more than 57 different tools, including
    the following: Calculating circuit values for resistor, capacitor,
    inductor, NE555, filter circuits and more Number base
    converter (hex, decimal, binary etc. Experience: This is the first and foremost thing one must look into while opting offshore development company for customized iPhone apps service.

    my web site best iphone games

  91. game camera reviews

    I think this is among the most important info for me.
    And i’m glad reading your article. But wanna remark on some general things, The web
    site style is ideal, the articles is really great :
    D. Good job, cheers

    Here is my web site … game camera reviews

  92. Nginx & Varnish connection error • PHP Help Coding Programming

    […] /etc/varnish/default.vcl file is copied from the tutorial. All & has been corrected to […]

Leave a Reply

You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>
And here's a tool to convert HTML entities