Securing your web application with an SSL certificate is the least your users will expect you to do. Once upon a time, HTTPS sites were rare and even then, only the “private” parts beyond a login dialog were encrypted. Those times died with Netscape and they aren’t coming back.

Here I’ll show you how to secure your Rails 3 application, running on Nginx, with a NameCheap SSL certificate. The requirements of this application are:

  • Must use HTTPS with a valid SSL certificate
  • All insecure HTTP links must redirect to their HTTPS versions
  • It must work across multiple subdomains (e.g. mycompany.yourdomain.com)
  • The marketing site running at www.yourdomain.com and yourdomain.com will not use HTTPS

We’ll be using Nginx on Ubuntu 12.04, Vagrant, Rails 3.2.11, rack-ssl-enforcer and NameCheap

In Development

Configuring Vagrant

The first step is to make things work locally in our development environment. I’m using Vagrant here which has made my choices slightly different but if you’re developing locally on Mac OS X, then this Railscast will be very useful to you. In my Vagrantfile I have

config.vm.forward_port 80, 8080                   # http

So, if I go to mycompany.lvh.me:8080 on my browser, I’ll be hitting the nginx instance running inside Vagrant (lvh.me is just a short wildcard domain name which points to 127.0.0.1 — essential for testing subdomains locally). We need to update Vagrant to forward the HTTPS traffic too.

config.vm.forward_port 80, 8080                   # http
config.vm.forward_port 443, 8081                  # https

Now, restart our Vagrant instance and the local 8081 port will forward to the 443 port on the virtual machine. Easy.

Configuring Nginx

Now we need to configure our Nginx server to listen for HTTPS traffic on port 443. My simplified nginx.conf file looks like this (I’ve remove the basic http server instance for clarity but it’s basically the same as the https server)

gzip on;
gzip_http_version 1.0;
gzip_proxied any;
gzip_min_length 500;
gzip_disable "MSIE [1-6]\.";
gzip_types text/plain text/html text/xml text/css text/comma-separated-values text/javascript application/x-javascript application/atom+xml;

# this can be any application server, not just Unicorn/Rainbows!
upstream app_server {
  # for UNIX domain socket setups:
  server unix:/tmp/unicorn.cognito.sock fail_timeout=0;
}
server {
  listen 443 default deferred ssl; # for Linux

  client_max_body_size 4G;
  server_name _;

  keepalive_timeout 5;

  root /vagrant/public;
  try_files $uri/index.html $uri.html $uri @app;

  ssl                  on;
  ssl_certificate      /vagrant/server.crt;
  ssl_certificate_key  /vagrant/server.key;

  ssl_session_timeout  5m;

  ssl_protocols  SSLv2 SSLv3 TLSv1;
  ssl_ciphers  HIGH:!aNULL:!MD5;
  ssl_prefer_server_ciphers   on;

  location @app {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://app_server;
  }

  # Rails error pages
  error_page 500 502 503 504 /500.html;
  location = /500.html {
    root /vagrant/public;
  }
}

}

The most important lines to consider here are:

ssl                  on;
ssl_certificate      /vagrant/server.crt;
ssl_certificate_key  /vagrant/server.key;

ssl_session_timeout  5m;

ssl_protocols  SSLv2 SSLv3 TLSv1;
ssl_ciphers  HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers   on;

which turns SSL on, points Nginx to the certificate and key files (more about that next) and configures the ssl options. We also need to set this line:

proxy_set_header X-Forwarded-Proto $scheme;

so that http->https redirects will work correctly.

Finally, test that your configuration is valid and restart your nginx server

sudo nginx -t
sudo service nginx restart

Creating a Self-Signed Certificate

You’ll have noticed the server.key and server.crt in nginx.conf. We need to create those

First, create a Certificate Signing Request:

openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr

Answer the questions with anything you like.

Next, use the .csr to generate a self-signed certificate:

openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

That should give you your server.key and server.crt file. Copy them to an appropriate location as specified in your nginx.conf

Configuring Rails

In the simplest case, you can configure Rails to redirect all links to https using this option:

config.force_ssl = true

This works fine in most cases but remember that our local http and https ports are 8080 and 8081 respectively. Rails doesn’t have the ability to specify custom HTTPS ports so I’ve used rack-ssl-enforcer to do the same job. Place this in development.rb:

# Redirect all HTTP links to use HTTPS
config.middleware.use Rack::SslEnforcer, :http_port => 8080,
                                         :https_port => 8081,
                                         :hsts => true

That hsts option refers to HTTP Strict Transport Security which is a method of using HTTP header to force the client to use a secure connection. hsts is enabled by default with Rails’ force_ssl option but not with rack-ssl-enforcer.

Just a word about HSTS: It instructs the browser to always use a secure connection when visiting URLs at your domain. I was initially worried that using HSTS on a subdomain like mycompany.yourdomain.com would also force HTTPS on the naked domain which is just running the marketing site. Thankfully it does not. It operates on the level of the full domain name

Now, restart your Rails application and visiting an insecure URL like http://mycompany.lvh.me:8080 should redirect you to https://mycompany.lvh.me:8081. The browser will display a scary warning about not trusting the certificate but that’s only because it’s self-signed.

In Production

Hopefully your development and production environments are similar enough that you can reuse the nginx.conf etc. In production.rb, there’s no need to specify the port for rack-ssl-enforcer though:

# Redirect all HTTP links to use HTTPS
config.middleware.use Rack::SslEnforcer, :hsts => true

Obtaining your SSL Certificate

I used NameCheap because they sell a good (bewildering) selection of certificates, at a decent price, and I was quite impressed with their support staff (prompt, friendly, efficient and knowledgable).

I bought the Comodo Essential SSL Wildcard for $99 which is a pretty good deal compared to other suppliers. Your own requirements may vary but this worked for me.

Before your start, make sure that you don’t have any privacy protection on your WHOIS domain information. This certificate is “domain validated” which means that they need to send an email to an address associated with the domain. Check with a WHOIS server or your domain registrar that a valid email address is publicly available and associated with your domain. Not doing this slowed up our purchase by a few days.

NameCheap uses a two-step purchasing process so you can buy the SSL cert with just the basic details and then activate it later using the information below.

On our server, we need to generate a Certificate Signing Request (just like we did with our self-signed key)

openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr

Fill in the details properly this time and use *.yourdomain.com as the Common Name value. It is very important to specify a wildcard domain here if you want it!

Activate your SSL certificate by selecting Apache + OpenSSL and pasting in the contents of your server.csr (not your development one!)

Now select an email address to send the confirmation to. You can choose from either a predefined address (admin/administrator/postmaster/webmaster@yourdomain.com) or the address found in the domain’s WHOIS information. You cannot send this confirmation to any arbitrary address!

Once you receive the email, paste the supplied code into the form at the url they give you and you should receive two emails shortly afterwards. One contains the “site seal” — a useless graphic designed only to promote the certificate authourity — and a zip file containing the certificates. Yes, multiple certificates.

Now, the tricky bit. You need to concatenate the certificate files together in the following order to form the certificate chain:

  1. Your certificate (STAR_yourdomain_com.crt)
  2. EssentialSSLCA_2.crt
  3. ComodoUTNSGCCA.crt
  4. UTNAddTrustSGCCA.crt
  5. AddTrustExternalCARoot.crt

The easiest way to do this on Linux is

unzip STAR_yourdomain_com.zip

cat STAR_yourdomain_com.crt EssentialSSLCA_2.crt ComodoUTNSGCCA.crt UTNAddTrustSGCCA.crt AddTrustExternalCARoot.crt > yourdomain.com.crt

Now, put the resulting certificate yourdomain.com.crt and the server.key used to generate the CSR into a safe place.

sudo mkdir /etc/nginx/certs
sudo cp server.key /etc/nginx/certs
sudo cp yourdomain.com.crt /etc/nginx/certs

In your production nginx.conf, use the paths /etc/nginx/certs/server.key and /etc/nginx/certs/yourdomain.com.crt for the key and certificate values respectively.

Restart your nginx server and you should be good to go!

Comodo has some basic documentation on installing their certs on Nginx

Bonus Points

Remember to add a reminder to your calendar to renew the certificate in a year! If the certificate expires then your customers will be presented with scary-looking warnings in their browser.

Extra bonus points if your write all this down because I guarantee you will not remember the process next year!