Enable HTTPS on Your Heroku-hosted Rails App Using Sabayon

Eric Marchese
8 min readMar 2, 2017

--

Update: On March 21, 2017, Heroku integrated Let’s Encrypt certificate management into their platform, making the use of Sabayon unnecessary.

An exciting thing happened this past May on Heroku. They started offering free SSL for custom domains on paid (“Hobby” level or above) Heroku dynos as a beta. It has since moved out of the beta phase and is a standard offering for all paid dynos now. Before, this cost an additional $20 a month, so like all semi-costly add-ons, most people didn’t go for it. Undoubtedly inspired by the enthusiastic growth of LetsEncrypt.org, Heroku has joined the chorus of voices saying that another web is possible and it must be encrypted! SSL should come standard with any custom domain name setup and “https://” should become as ubiquitous as “.com” but unfortunately this isn’t yet the web we’re living in.

LetsEncrypt is leading the charge against costly and bureaucratic SSL certificate authorities by providing free and infinite SSL certificates to the masses. It turns out generating and using SSL certificates was easy to do all along, but everyone holding the keys to the castle were charging for admission and deciding who could and couldn’t be issued a certificate. If we want something to be easy, accessible, and ubiquitous, this is not the way to go about it. Some hosting companies have begun integrating LetsEncrypt-powered SSL directly into their system, making it incredibly easy to start using with one click. Using it with Heroku will take a few more clicks and keystrokes. But with Sabayon, it is a mostly painless process that once complete, you can forget about, as it will continue handling any certificate renewals automatically.

In this tutorial I will focus on using Sabayon with Rails apps, but it can be used with any web app and the README includes the example code necessary to implement this with apps written in Ruby, Go, Javascript, PHP, Python, and Elixir. Sabayon itself is written in Go, so there is nothing Ruby-specific about it, Ruby is just the ecosystem I live in and have the most experience with. The only requirements for using Sabayon is that you have a custom domain, a web app hosted on Heroku, and the will to secure your app!

Create an OAuth Authorization Token

You’ll need an authorization token from Heroku to allow the Sabayon app you’re about to deploy to access the Heroku-hosted app you want to add SSL to. Open up the Terminal and proceed with these commands.

First install heroku-cli-oauth if it isn’t already installed.

heroku plugins:install heroku-cli-oauth

Now create a new authorization token.

heroku authorizations:create -d "<description-you-want>"

If you already have heroku-cli-oauth installed and have already created an authorization token that you want to use, you can display it with this command.

heroku auth:token

Deploy Sabayon

Sabayon is an app that runs on its own free Heroku dyno, accessing the app you’re adding SSL to once a day to check if its SSL certificate is expiring soon and, if so, renew it. The certificates are issued and renewed by LetsEncrypt using a protocol called ACME.

Click the link below to start the deploying process.
Deploy Sabayon to Heroku

Name the app something helpful, like exampleapp-sabayon so you can easily find it later if you have many dynos on your Heroku account.

Now, you’ll set the Config Variables for Sabayon.

ACME_DOMAIN: Enter the domain (and subdomain) you wish the certificate to be valid for. Generally, you’ll want to think about what you want the default URL for your app to be if you haven’t thought of this already. For some websites, this might be a special subdomain. For most, the default URL will be either the naked domain (example.com) or using the www subdomain (www.example.com). Decide which is right for you and enter it as I’ve written it here. For my app, I created a subdomain of www on my domain registrar’s control panel and made a CNAME record pointing to my Heroku app. I’ll discuss this more below when I talk about making DNS record changes. I will assume you made the www choice going forward in my examples.

ACME_EMAIL: Enter your e-mail.

ACME_APP_NAME: Enter the Heroku app name.

HEROKU_TOKEN: Enter your Heroku authorization token that you either created or retrieved already.

RESTART_WAIT_TIME: This is set at 20 by default and shouldn’t need to be changed.

Click “Deploy”.

Configure Your App

When Sabayon tries contact LetsEncrypt to generate a certificate, LetsEncrypt wants proof that you control the domain you are trying to generate a certificate for. You will add code to your app to properly respond to this verification request. I will detail the procedure for my example Rails app here, but if you’re using a different language or web framework, you can use the different code examples Sabayon provides.

Within your Rails project, in the app directory, create a new directory named middleware if one doesn’t already exist. Within that directory create a new file titled sabayon_middleware.rb and within in, add the following code.

class SabayonMiddleware
def initialize(app)
@app = app
end

def call(env)
data = []
if ENV['ACME_KEY'] && ENV['ACME_TOKEN']
data << { key: ENV['ACME_KEY'], token: ENV['ACME_TOKEN'] }
else
ENV.each do |k, v|
if d = k.match(/^ACME_KEY_([0-9]+)/)
index = d[1]
data << { key: v, token: ENV["ACME_TOKEN_#{index}"] }
end
end
end

data.each do |e|
if env["PATH_INFO"] == ("/.well-known/" +
"acme-challenge/#{e[:token]}")
return [200, { "Content-Type" => "text/plain" }, [e[:key]]]
end
end

@app.call(env)
end
end

Then open up config/application.rb and add this line within the Application class like so to include the middleware you just defined.

module ExampleApp
class Application < Rails::Application
# Add this line to insert SabayonMiddleware
config.middleware.insert_before 0, "SabayonMiddleware"
end
end
What’s going on behind the scenes

Set Up Heroku Scheduler

Now you will setup Heroku Scheduler to run Sabayon once a day to check if your certificate needs to be renewed.

Enter your Heroku Dashboard and click on the exampleapp-sabayon dyno you deployed. Then click on Heroku Scheduler and a separate window will open.

Type bin/sabayon as pictured above, set the Frequency to Daily and then set the time each day you want the command run. I recommend scheduling it in the early hours around 3am or so but you can choose whatever you want. Keep in mind all times scheduled here are UTC so you’ll need to do the conversion from your local time zone to be sure you’re setting the time you want.

Manually Run Sabayon

Since you have never run Sabayon before, you need to manually run it the first time to generate a certificate for you. Run this command only after successfully completing everything before this, specifying the name of your Sabayon Heroku dyno on the end.

heroku run bin/sabayon -a exampleapp-sabayon

If it tells you the operation was successful, then you’re ready to move on to the next phase. If it tells you ERROR: Challenge is invalid! then you made a mistake in configuring your Rails app. Go back and follow the instructions carefully, then try again.

Update Your DNS Records

On my app I had a domain registered with 1and1.com, a cheap but not totally ideal domain registrar for use with services like Heroku, that do not provide you with a static IP address. Heroku recommends going with a registrar like DNSimple that is better suited to configuration with Heroku. But I already had a domain registered with 1and1 and I didn’t want to transfer it, so my solution was to create a subdomain of www in 1and1’s control panel and make a CNAME record on it pointing to the proper DNS target. The proper DNS target can be found under the “Domains and Certificates” section within the “Settings” tab within your app on the Heroku Dashboard after your certificate has been successfully added. Earlier, I had generated my certificate with Sabayon for this subdomain, setting ACME_DOMAIN to www.example.com.

Now I had a new problem: What if a visitor goes to the naked domain of example.com? With force_ssl enabled in Rails, Rails would try to force the URL to be https://example.com but the browser would encounter a certificate only valid for www.example.com and throw an error. My solution was to change the destination of the naked domain on my 1and1 control panel to be a redirect to https://www.example.com.

Regardless of what domain registrar you are using, you’ll need to find your DNS target in the Heroku Dashboard and change your DNS records to point to that. It will look something like exampleapp.herokudns.com.

Test it!

Now it’s time to test if everything’s working properly in the browser. How long will it take to start working? That depends on how long it takes your DNS update to propagate to the servers your ISP is using. This can take up to 48 hours, but rarely does. In my experience you should see the change take effect in under an hour.

Clear your browser cache and go directly to the https version of your URL like this: https://www.example.com

If you recently tried to go to this and it didn’t work properly, your browser may remember that, so you may have to keep clearing the cache every time to re-test it. The last time I did this, I got it to work on my phone using mobile data, but still couldn’t get it to work properly on my laptop. I then remembered that my wireless router may be keeping its own cache and after power-cycling the router, it worked!

A sign of success

If you want to test it more thoroughly, you can try running it through this series of SSL tests.

That’s what we want to see!

Force it!

Now that your https URL is working perfectly, let’s force it to be used all the time! Enter your Rails app’s config/environments/production.rb, find the line below and change it from false to true.

config.force_ssl = true

Going forward, the Heroku Scheduler job you set up earlier will take care of babysitting your certificate, so don’t be concerned that it expires in 3 months, but do make a test job set to fire in a few minutes and view your logs afterward with heroku logs to verify that there are no errors. If there are, make sure you set the correct authorization token retrieved using the commands I described, as that is a common pitfall.

Happy SSLing!

--

--

Eric Marchese
0 Followers

A developer living under the long shadow of the maple leaf in Montréal http://ericmarchese.com