How to setup a public (and http version) health check while still using global force_ssl in Rails (for Elastic Load Balancer)

I recently ran into an issue where I wanted to enable SSL for an entire site. This is pretty straight-forward, setting force_ssl to true in the environment config, right?

Unfortunately, this blew up the site. Its on AWS behind an Elastic Load Balancer. After some digging I discovered that the SSL redirect pissed off ELB which wants a 200 return status but was now getting a 301. So it rightfully removed all instances from the pool and left the site unresponsive. The health checks aren’t very configurable either on ELB.

I needed to add an exception to the global SSL redirect for the status check. This turned out to be trickier than I expected. The force_ssl configuration option (not the ActionController filter) tells Rails to load up the Rack::SSL middleware. Middleware runs before it hits the controller so you can’t just skip it in the status controller like you can a filter.

Fortunately, it can take an :exclude parameter with a Proc which is called to make the decision.

Here was the final setup:

  • In config/environments/production.rb add:
config.force_ssl = true  
config.ssl_options = {  
  exclude: proc { |env| env['PATH_INFO'].start_with('/status')}
}
  • Add a controller, app/controllers/statuses_controller.rb (note plural), that does something, ideally all the way down to the database layer. Here’s an example that does a simple database read of the schema version.
class StatusesController < ApplicationController  
  skip_before_filter :signed_in_user # or whatever

  def show
    render text: ActiveRecord::Migrator.current_version
  end
end  
  • Add a route, in config/routes.rb, for the controller:

resource :status

That’s it. Let’s test it. Restart and curl the headers.

First, test that we get redirect to https properly.

$ curl -I localhost:3000
HTTP/1.1 301 Moved Permanently  
Location: https://localhost:3000/  

Check. Finally, make sure /status doesn’t get redirected, for SSL or authentication.

$ curl -v localhost:3000/status
< HTTP/1.1 200 OK  
20131003204948  

Perfect! ELB is now happy again. And hopefully you are to.


comments powered by Disqus
comments powered by Disqus