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:
- 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.
- Get two HTTPS sites up and running on different sub-domains.
Then, once we have Ghost installed we want to:
- Create the Nginx snippets to make it easy to support multiple blogs
- 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.