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
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.
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)
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.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.crt file. Copy them to an appropriate location as specified in your nginx.conf
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
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.
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 (email@example.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:
- Your certificate (STAR_yourdomain_com.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/yourdomain.com.crt for the key and certificate values respectively.
Restart your nginx server and you should be good to go!
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!