From Jekyll to Ghost: Prelude

In case you're coming here from this tweet, this post is a draft. I'll have a full writeup soon.

I've migrated from Jekyll to Ghost to take advantage of Ghost's great management UI. I wasn't happy with its performance out of the box, though, so I've placed it behind nginx as a caching proxy to take care of a few things:

  • Serving static assets from nginx directly instead of through the node.js process
  • Caching responses from Ghost in nginx
  • Proxying media (e.g. my résumé) from S3

These are all relatively easy to accomplish and it effectively means most requests won't hit Ghost at all depending on your cache duration.

Static Assets

Nginx is great at serving static assets. As with just about any webapp, it's advisable to serve your static assets through something other than your application server for various reasons. In a production environment, I'd use a CDN, but this blog is so low-traffic that that's not necessary.

This can be configured in nginx through the following stanzas. One thing to keep in mind is that you need to hard-code your theme path, since Ghost serves the theme assets from the same URL regardless of the theme currently in use. I use Salt to manage my new systems, so it replaces the ghost_root variable with the path to the Ghost installation and ghost_theme with the current theme name (e.g. casper):

# Serve all of Ghost's static assets from nginx instead of via Express to
# keep traffic off the Ghost instance
location ~ ^/assets {
    root {{ ghost_root }}/content/themes/{{ ghost_theme }};
    access_log off;
    expires max;
location ~ ^/(shared/|built/) {
    root {{ ghost_root }}/core;
    access_log off;
    expires max;
location ~ ^/public {
    root {{ ghost_root }}/core/built;
    access_log off;
    expires max;
location ~ ^/(img/|css/|lib/|vendor/|fonts/|robots.txt|humans.txt) {
    root {{ ghost_root }}core/client/assets;
    access_log off;
    expires max;

Caching responses from Ghost

Omitting a lot of detail, caching Ghost itself is fairly simple. Using nginx's proxy_cache module, configure a cache and then enable its use in your Ghost location stanza:

server {

    location / {
        add_header X-Cached $upstream_cache_status;
        expires 5m;
        proxy_cache one;
        proxy_cache_key ghost$request_uri$scheme;
        proxy_cache_valid 200 5m;
        proxy_hide_header X-Powered-By;
        proxy_ignore_headers Cache-Control;
        proxy_pass http://ghost;


upstream ghost {
    server localhost:2368;

Caveat: cache invalidation

You'll need to invalidate the cache (which, in nginx land, means blowing away the entire thing unless you have the commercial package which allows PURGE requests) if you want to post or update before the cache expires. In the above example, the cache is valid for 5 minutes.

Proxying media from S3

To be continued...