Automatically switch between SSL and non-SSL with Nginx+Unicorn+Rails
22 Oct, 2011
7 minute read

Scroll down for setup instructions. Or, read this bit about SSL in the real world first.

SSL or Secure Socket Layer is a nice way to secure sensitive parts of your Rails application. It achieves to goals.

Firstly is encrypts all traffic between you and the remote server. Consider the passwords and personal information you submit to websites. When unencrypted (using HTTP), all this data is sent over the internet for all to read. With SSL (HTTPS) enabled, all traffic is encrypted, making it very hard for a third-party to eavesdrop.

Secondly, a proper SSL connection can give you trust in that you’re communicating with the right people.

For example, Rabobank (a Dutch bank) uses SSL for its website. When you open their site, you’ll notice the green ‘Rabobank Nederland’ in the address bar.

This tells me I am communicating with Rabobank Nederland. This is why SSL certificates are so expensive - the SSL authority needs to verify the identify of Rabobank before they issue the certificate.

In the example above Rabobank uses an EV SSL Certificate. EV stands for Extended Validation. This means that the SSL authority has verified (among other things) that Rabobank is a legitimate business and that they are the legal owner of the domain rabobank.nl

The cost for such an EV SSL Certificate is $200 -$1000 per year. You probably don’t need it for your site.

SSL for you and me

When you are looking to secure the back-end of your site (where you login etc.), you only require the encryption part of SSL. There are two routes you can take

Self signed SSL

You are able to create a working SSL certificate yourself. This will give you encryption, but no identity validation. When you use a self-signed SSL certificate all browsers will warn you about this.

• Encryption
• No validation
• Warnings from your browser
• Free

For me, that’s a reason not to use self signed SSL for any other than development and testing purposes.

Most SSL authorities provide you with a Standard SSL product. These certificates only check if you own the domainname. They also offer encryption and work (without warnings) in your browser. You can get one of these for as little as $9 a year. • Encryption • Domain validation / trust • No warnings from your browser • Cheap ($10 - $20) Setting up SSL for your Rails application Setting up SSL is a web server thing. It does not involve your Rails appliation directly (but more on that in a moment). If you followed my nginx+unicorn guide, you’ll have Nginx and Unicorn setup already. Create your private SSL key (key) First you need to create a private key. Do this on you server. The following command will generate a 2048 bit key. When it asks you to set a passphrase, do so. $ openssl genrsa -des3 -out example.com.key 2048
Generating RSA private key, 2048 bit long modulus
..................................+++
.................................................................................+++
e is 65537 (0x10001)
Enter pass phrase for example.com.key:
Verifying - Enter pass phrase for example.com.key:

Creating a key and Certificate Sign Request (csr)

Okay, you now have your key. Next step, create the Certificate Sign Request. The sign request is the part you’ll send to the SSL authority to sign. You will need to provide some information here. Make sure you enter everything correctly.

• Country Name - Your 2 letter country code. I.e. NL, UK, BE
• State or Povince Name. I.e. Noord-Brabant, New York
• Locality Name - Your city. I.e. Eindhoven, London
• Organization Name - Your company or site name: Ariejan.net, Apple Inc.
• Organization Unit Name - The section of your company. You may leave this blank. I.e. Online Services, Finance
• Common Name - The domain this SSL certificate will be used for. If you want to run this on https://www.example.com, you enter www.example.com here. This is not a wildcard. example.com will not work on www.example.com and vice versa.
• Email Address, challenge password and optional company name should normally be left blank. Just hit enter.

Here’s the full version:

\$ openssl req -new -key example.com.key -out example.com.csr
Enter pass phrase for example.com.key: <<passphrase>>
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:NL
State or Province Name (full name) [Some-State]:Noord-Brabant
Locality Name (eg, city) []:Eindhoven
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Ariejan.net
Organizational Unit Name (eg, section) []:
Common Name (eg, YOUR name) []:example.com
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

Great, you now have two files:

• example.com.key - your private key. Keep it secret, keep it safe!
• example.com.csr - sign request

Get your certificate (crt)

Now, go to your selected SSL authority, order your Standard SSL Certificate and upload the contents of example.com.csr when requested.

After doing some validations, which may require you to click some links in emails, you’re certificate should be ready for download. Save this file as example.com.crt.

Intermediate certificates

Some SSL authorities work with so called intermediate certificates. This requires you to include an intermediate certificate with your own certificate. If your SSL provider requires this, save the intermediate certificate as intermediate.crt.

For usage with nginx, you must place both your own and the intermediate certificates in a single file. This is easy:

cat example.com.crt intermediate.crt > sslchain.crt

Remove the passphrase from your key

Now, this is not recommended, but many people do this. The reason is that when your private key has passphrase, your server requires that passphrase everytime your (re)start it. This could mean that your server cannot boot up without manual interaction from your part.

cp example.com.key example.com.key.orig
openssl rsa -in example.com.key.orig -out example.com.key

You now have example.com.key.orig, which is your original private key with the passphrase. And you have example.com.key, which is the same private key, but without the passphrase.

Setup nginx for SSL

Finally, you can setup Nginx for SSL. Normally I add both a SSL and non-SSL configuration. Setup is very easy.

First of all, become root and copy your keys and certificate to /etc/ssl.

cp example.com.key example.com.crt /etc/ssl

or if you use an intermediate certificate:

cp example.com.key sslchain.crt /etc/ssl

Next, you take your non-SSL server configuration and duplicate it. Then you add the following lines.

listen 443; # Instead of Listen 80

ssl on;
ssl_certificate /etc/ssl/sslchain.crt; # or /etc/ssl/example.com.crt
ssl_certificate_key /etc/ssl/example.com.key;

location / {
# Add this to the location directive.
proxy_set_header   X-Forwarded-Proto https;
}

Most of this is pretty straight forward. The proxy_set_header directive is needed to let your Rails application know if the request came in over SSL or not. Normally this shouldn’t matter, but you’ll need it for the next part of this guide.

Save, restart nginx and your SSL connection should be available.

Automatically switch between SSL and non-SSL with Rails

To take this site as an example, I don’t want to run the front-end through SSL. First of all, you can’t submit any data to my server. Second, I include several external resources (Disqus, Twitter, AdSense), that will give you warnings about using “insecure” content on an encrypted page.

What I do want is to encrypt traffic to the backend, where I log in and write posts like these.

I need to make sure that your browser knows exactly when and when not to switch to SSL. This is where the rack-ssl-enforcer gem comes in.

First, update your Gemfile:

# Gemfile
gem 'rack-ssl-enforcer'

After you’ve run bundle install, update config/application.rb (or config/environments/production.rb is you only want to configure this for your production environment).

# config/application.rb or config/environments/production.rb
config.middleware.use Rack::SslEnforcer,
:redirect_to => 'https://example.com',    # For when behind a proxy, like nginx
:only => [/^\/admin\//, /^\/authors\//],  # Force SSL on everything behind /admin and /authors
:strict => true                           # Force no-SSL for everything else

With the following statement, you achieve the following:

• When you access any URL with a path that starts with /admin or /authors, you’ll be redirected to the SSL site.
• Because we’re behind a proxy and want to do proper redirects, we specify the correct SSL domain.
• We set strict to true. If you access something that is not /admin or /authors, you will be redirected to the non-SSL version of that page.

There’s a lot more possible with the rack-ssl-enforcer gem. Checkout their README on Github for details.

Note: if you find yourself getting into an infinite redirect loop, make sure have the proxy_set_header directive set correctly in your Nginx configuration.

Wrapping up

You now know how you can setup an SSL certificate with Nginx and how you can make your Rails application automatically switch between SSL and non-SSL whenever you want to.