Configuring Nginx to Speed Up Your Ghost.io Blog

Adam Bard
Share

Ghost is a hot new blogging platform. It’s simpler than WordPress, and exclusively focused on blogging (rather than being a full CMS, as WordPress has become). Like WordPress, you can sign up and host your blog on their site (for a price), or you can download and host Ghost yourself. We’ll focus on the latter.

Why Ghost?

I recently set up a blog for my wife (who writes about our travels) on my virtual server. Said VPS is shared with quite a few other apps, so I wanted to keep things efficient. The most efficient way to serve a blog is to use a static site generator like Middleman or Jekyll, and serve your static resources with a web server like Nginx. That’s fine for me — my personal blog uses Middleman and I updated it with Git post-receive hooks — but my wife’s not so into the command line, so I thought Ghost might be a better fit.

Said server is also serving a bunch of other little sites and apps, so I needed to keep it lightweight.

Ghost is a very well-made and good-looking piece of software written in Node.js — but that doesn’t mean it’s perfect. Node is great for many things, but when it comes to serving up static files, that task can be handled more efficiently with Nginx. Nginx is a fast, lightweight web server that is gaining more and more popularity, and one of the things it’s best at is serving as a proxy in front of other apps.

Today, I’ll show you how I configured my server so that:

  • Ghost serves up the admin pages (the only dynamic pages)
  • Nginx serves static assets (images, JavaScript, CSS)
  • All non-admin pages are cached by Nginx, further reducing load

All this will let my server’s resources go a lot farther. Plus, since Ghost ends up doing almost no work, I can leave it configured to use SQLite 3 instead of something more suited for parallel connections, which is a nice bonus.

Before We Continue

The process of installing Nginx and Ghost is beyond the scope of this article. For Nginx, you might try Nginx’s docs; for Ghost, you can follow Ghost’s installation instructions.

Configure Ghost to run on port 2368. We’ll proxy port 80 (i.e. the default http port) to it with Nginx. We’ll also assume you’re running on example.com.

Configuring Nginx to Serve Static Assets

To start with, you should have this bare-bones Nginx configuration:

server{
    listen 80;
    server_name example.com www.example.com;
}

First, we’ll make sure to serve the blog, which is running on port 2368. To do this, simply add the following:

location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $http_host;
    proxy_pass http://127.0.0.1:2368;
}

Now, make sure Ghost is running, and restart Nginx. When you navigate to your home page at example.com, you should see a blank Ghost blog.

We want to serve images and assets with Nginx. Ghost hides these around in a few different places, so you’ll need to make a few aliases. Add the following (changing the path to your Ghost install, and to your theme, appropriately):

location /content/images {
    alias /path/to/ghost/content/images;
}

location /assets {
    alias /path/to/ghost/content/themes/<mytheme>/assets;
}

location /public {
    alias /path/to/ghost/core/built/public;
}

location /ghost/scripts {
    alias /path/to/ghost/core/built/scripts;
}

Setting Up Static Site-style Caching

In order to use up as few cycles as possible, we want to be really aggressive with caching. So, for all the non-admin pages, we’re going to configure Nginx to ignore cache-control headers entirely and just cache each page for an hour.

Take out the other proxy_pass block. We’ll need to split it into two. The first will proxy requests to the admin backend, which we must not cache. The second will be the rest, which will be cached with a vengeance. Here’s what that looks like:

location /ghost {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $http_host;
    proxy_pass http://127.0.0.1:2368;
}

location / {
    proxy_cache STATIC;
    proxy_cache_valid 200 60m;
    proxy_ignore_headers X-Accel-Expires Expires Cache-Control;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $http_host;
    proxy_pass http://127.0.0.1:2368;
}

Ignoring the headers is optional, but we don’t mind clearing out the cache ourselves every now and then if it really needs it.

Speaking of which, you’ll also want to configure Nginx’s cache path. Above, we’ve called our configuration STATIC, so we need to add this line to our Nginx configuration’s http block (I put mine in /etc/nginx/nginx.conf):

proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=STATIC:10m inactive=24h max_size=512m;

This tells Nginx to keep cache files at the path /data/nginx/cache, and sets the levels and various configurations. If you ever want to clear the cache, just execute rm -r /data/nginx/cache/* to remove all the cached files.

Everything All Together

Here’s the whole configuration, for reference:

server{
    listen 80;
    server_name example.com www.example.com;

    location /content/images {
        alias /path/to/ghost/content/images;
    }

    location /assets {
        alias /path/to/ghost/content/themes/<mytheme>/assets;
    }

    location /public {
        alias /path/to/ghost/core/built/public;
    }

    location /ghost/scripts {
        alias /path/to/ghost/core/built/scripts;
    }

    location /ghost {
        proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Host $http_host;
        proxy_pass http://127.0.0.1:2368;
    }

    location / {
        proxy_cache STATIC;
        proxy_cache_valid 200 60m;
        proxy_ignore_headers X-Accel-Expires Expires Cache-Control;
        proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Host $http_host;
        proxy_pass http://127.0.0.1:2368;
    }

}

And that’s really all there is to it. With this setup, you should be able to serve many thousands of visitors from even a small private server.