NGINX multi-site HTTPS

So I've discovered that NGINX supports multiple HTTPS virtual hosts using a single IP/SSL certificate (with SAN) - woohoo, hats off to Nginx!

As part of the Big Bad Blog project, I want to host distinct Ghost blogs for various family members. The Authoring and Administrative pages MUST be available only over HTTPS - no login leakage dontcha know!

So the steps we need to go through are:

  1. Obtain an SSL Certificate for the server
  • Not really interested in snake-oil, cos my Gramps gets worried if the barely visible little-lock-thingy aint painted lucid green.
  1. Get two HTTPS sites up and running on different sub-domains.

Then, once we have Ghost installed we want to:

  1. Create the Nginx snippets to make it easy to support multiple blogs
  2. Configre at least two running blogs.

Success!

You know we got there - you are reading one of the resulting blogs :)

Howdy Doody?

Here's how we diddy it:

Get SSL Certificates

I registered at http://www.startssl.com

I followed the instructions for generating a free SSL Certificate with 5 SAN names - its a cinch - I used openssl to generate the CSR (Certificate Signing Request) on my server - that way, the private key is kept private.

A handy command to examine the bundle content is openssl x509 -in bundle.crt -text.

My openssl insisted on password protecting the private key - which doesnt work that well for unattended daemon startup - so I had to export the private key using:

openssl rsa -in /etc/ssl/private/x.y.z.key -out /etc/ssl/private/x.y.z.nocrypt.key

The resulting certificate bundle has parts for NGINX installation - you have to install two files - the certificate and the private key - I chose to install these as /etc/nginx/ssl/1_x.y.z_bundle.crt and the unencrypted private key as /etc/nginx/ssl/1_x.y.z_bundle.key.

Configure NGINX

I want to enforce HTTPS for blog administration and post-creation - there is a Ghost setting you should use - see the page about installing Ghost.

I want to get NGINX to also enforce this too, as an additional layer - as mother says - you can never have too many layers.

I also want to use NGINX snippets to reduce config-duplication - yes indeed, less is more when it comes to config.

We create TWO snippets for inclusion in NGINX server blocks - one to deal with HTTP and redirect all /ghost/ URLS to HTTPS, and the other to do the inverse.

Note that each blog will run a handler listening on a different port on localhost. You need to choose a different $ghost_port for each blog.

/etc/nginx/snippets/blog_http.conf

# you must: set $ghost_port 2368; in your server block
listen 80;

location / {
  proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header   X-Forwarded-Proto $scheme;
  proxy_set_header   X-Real-IP $remote_addr;
  proxy_set_header   Host      $http_host;
  proxy_pass         http://127.0.0.1:$ghost_port;
}

location /ghost/ {
  return             301 https://$host$request_uri;
}

/etc/nginx/snippets/blog_https.conf

# you must: set $ghost_port 2368; in your server block
listen 443 ssl;
ssl_certificate /etc/nginx/ssl/1_xxx_bundle.crt;
ssl_certificate_key /etc/nginx/ssl/1_xxx_bundle.key;

location /ghost/ {
  proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header   X-Forwarded-Proto $scheme;
  proxy_set_header   X-Real-IP $remote_addr;
  proxy_set_header   Host      $http_host;
  proxy_pass         http://127.0.0.1:$ghost_port;
}

location / {
  return             301 http://$host$request_uri;
}

Once we have these snippets in place, we can create NGINX config files for each blog that we want. Note that the server_name setting (x.y.z in our sample config) must match a SAN in your SSL certificate.

/etc/nginx/sites-available/x.y.z

server {
  server_name x.y.z;
  root /var/www/x.y.z;
  set $ghost_port 2368;
  include snippets/blog_http.conf;
}

server {
  server_name x.y.z;
  root /var/www/x.y.z;
  set $ghost_port 2368;
  include snippets/blog_https.conf;
}

... and Ghost's your uncle.