+++ date = "2011-10-22" title = "Automatically switch between SSL and non-SSL with Nginx+Unicorn+Rails" tags = ["Rails", "rails3", "unicorn", "nginx", "ssl"] slug = "automatically-switch-between-ssl-and-non-ssl-with-nginx-unicorn-rails" +++ _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. ![image](https://ariejannet.s3.amazonaws.com/content/rabobank-ssl.jpg) 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][evssl]. 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 [evssl]: http://en.wikipedia.org/wiki/Extended_Validation_Certificate 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. ### Standard SSL 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][nu] guide, you'll have Nginx and Unicorn setup already. [nu]: http://ariejan.net/2011/09/14/lighting-fast-zero-downtime-deployments-with-git-capistrano-nginx-and-unicorn ### 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. ``` shell $ 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: ``` shell $ openssl req -new -key example.com.key -out example.com.csr Enter pass phrase for example.com.key: <> 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: ``` shell 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. ``` shell 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`. ``` shell cp example.com.key example.com.crt /etc/ssl ``` or if you use an intermediate certificate: ``` shell 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. ``` nginx 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: ``` ruby # 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). ``` ruby # 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][rse] gem. Checkout their [README on Github][readme] 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._ [rse]: http://rubygems.org/gems/rack-ssl-enforcer [readme]: https://github.com/tobmatth/rack-ssl-enforcer#readme ## 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.