I recently enabled SSL for a Rails 3 site that I’m working on. I thought it would be simple since the entire site will be served over SSL (no switching between SSL and non-SSL requests based on the page).
Since the entire site will be served over SSL, I configured nginx to do the SSL redirects for me:
1 2 3 4 5 |
|
Now whenever a non-SSL request comes in, nginx will redirect to the same path using SSL. My SSL server configuration is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
This is just the same configuration I previously had (before adding SSL) with the listen
directive changed to 443
and the ssl
directives added.
After making these changes and restarting nginx, everything seemed to be working correctly. Any requests to the non-SSL site were redirected with a 301
response code as expected.
After a day or so I noticed that URLs being generated by Rails were not using the https scheme. I originally noticed it with named routes like so:
1 2 3 4 5 6 7 8 9 10 |
|
Anywhere Rails was generating a full URL, the non-SSL version was being generated. It took me a bit to notice this since in most places I was using the _path
version of the named routes which generates just a relative path. So on a page that was served over SSL, those relative paths of course led to SSL pages. I should note that the site continued to “work” since the generated non-SSL URL was then redirected to the SSL version by nginx, but I definitely did not want to continue to generate extra requests (it also caused a problem with some URLs when accessed via jQuery Mobile).
It took quite a lot of searching to finally find the answer; most of the things written about Rails and SSL are in relation to either 1) having Rails do the SSL redirection using something like config.force_ssl = true
or 2) managing a site with some pages served over SSL and some not. The few references I found mentioned Rails using whatever scheme was in use by the current page when generating URLs (which intuitively made sense to me since it got the host name correct).
I finally ended up debugging in to the Rails source to see how it makes the decision on what scheme to use when generating URLs. After a few layers, it comes down to this code in Rack:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Rails uses the ssl?
method to determine what scheme to use in generated URLs. When I debugged my site running under SSL, none of the env variables were set, causing Rails to treat the request as if it came in over regular http.
The solution turns out to be simple (as they often are when you can track down the root cause). Add an extra header in the location
configuration in nginx:
1 2 3 4 5 6 7 |
|
After adding that single line and restarting nginx, Rails is generating URLs using https instead of http. In hindsight, it makes sense that Rails was not seeing the right scheme since the SSL is being terminated at nginx, but it never occurred to me that that’s what might be happening.