This is a Nuts & Bolts Article – Tips & Tricks for Developers

Create and install SSL certificates with ease – a Capistrano recipe

by Torsten Bühl

How to generate the Private Key, what's the correct chaining order, or how to create a PEM Certificate? We all know it and we hate it: Installing or renewing the SSL certificates for our application.

And after you finally figured it out, you'll unlearn the progress till you need it again. So I wrote a small Capistrano recipe to help you with this annoying job.

(Find the revised Capistrano 3 version here.)

What it does

The recipe ships with three common tasks:

  • Generate the Private Key and CSR (Certificate Signature Request) files. You need them to purchase your SSL certificate.
  • Create a Chained Certificate file. No need to remember the right order of the certificates anymore. It also creates a PEM Certificate file.
  • Upload the certificates to your server and set the correct permissions.

To keep things simple there are a few conventions:

  • The certificate folder is YOUR_APP/config/certs/
  • The filename for the Intermediate Certificate file is #{fetch(:ssl_certificate_company)}-intermediate.crt. So rapidssl-intermediate.crt when you set the ssl_certificate_company variable to rapidssl.
  • The filename for the Domain Certificate file is #{fetch(:ssl_domain)}-#{fetch(:ssl_certificate_company)}.crt. So exceptiontrap.com-rapidssl.crt when you set the ssl_domain to exceptiontrap.com.

Installation & Configuration

First, grab the recipe.

# config/recipes/ssl.rb
namespace :ssl do
  desc "Install (upload) Certificates"
  task :install do
    upload_certificate domain_certificate_key_filename
    upload_certificate chained_certificate_filename
    upload_certificate domain_certificate_pem_filename
  end

  desc "Generate the Chained Certificate and the PEM Certificate files. To use them on the webserver"
  task :chain_certificates do
    # Chained CRT Certificate
    generate_chained_certificate(domain_certificate_filename, intermediate_certificate_filename, chained_certificate_filename)
    # PEM Certificate
    generate_chained_certificate(domain_certificate_key_filename, chained_certificate_filename, domain_certificate_pem_filename)
  end

  desc "Generate Private Key and CSR files"
  task :generate_private_key_and_csr do
    generate_private_key_and_csr_files
  end
end

# Upload a certificate to the remote server
def upload_certificate(filename)
  destination = "/var/certs/#{filename}"
  upload(certificate_file_for(filename), destination)
  run "#{sudo} chown root #{destination}"
  run "#{sudo} chmod 600 #{destination}"
end

# Chains the certificates to a new file
def generate_chained_certificate(certificate1, certificate2, chained_certificate)
  run_locally "sed -i '' -e '$a\\' #{certificate_file_for(certificate1)}" # Add newline to file unless there is one
  run_locally "cat #{certificate_file_for(certificate1)} #{certificate_file_for(certificate2)} > #{certificate_file_for(chained_certificate)}"
end

def generate_private_key_and_csr_files
  run_locally "openssl req -nodes -newkey rsa:2048 -sha256 -keyout #{certificate_file_for(domain_certificate_key_filename)} -out #{certificate_file_for(domain_certificate_csr_filename)}"
end

# Get the full path of a certificate file
def certificate_file_for(filename)
  File.expand_path("config/certs/#{filename}")
end

# Filenames of the different certificates
def chained_certificate_filename
  "#{fetch(:ssl_domain)}-#{fetch(:ssl_certificate_company)}-chain.crt"
end

def domain_certificate_filename
  "#{fetch(:ssl_domain)}-#{fetch(:ssl_certificate_company)}.crt"
end

def domain_certificate_pem_filename
  "#{fetch(:ssl_domain)}-#{fetch(:ssl_certificate_company)}.pem"
end

def domain_certificate_csr_filename
  "#{fetch(:ssl_domain)}-#{fetch(:ssl_certificate_company)}.csr"
end

def domain_certificate_key_filename
  "#{fetch(:ssl_domain)}-#{fetch(:ssl_certificate_company)}.key"
end

def intermediate_certificate_filename
  "#{fetch(:ssl_certificate_company)}-intermediate.crt"
end

Then load and configure it.

# config/deploy.rb
set :ssl_domain, "exceptiontrap.com" # Your domain goes here
set :ssl_certificate_company, "rapidssl" # Name of your CA company

load "config/recipes/ssl"

Please Note: We shouldn't store private keys in source control. So add config/certs/ to your .gitignore file.

How to use it

  1. cap ssl:generate_private_key_and_csr to generate the Private Key and CSR.
  2. Order your certificate.
  3. Copy your Domain Certificate to e.g. exceptiontrap.com-rapidssl.crt
  4. Copy the CAs Intermediate Certificate to e.g. rapidssl-intermediate.crt
  5. cap ssl:chain_certificates to create the Chained Certificate and PEM Certificate. Nginx needs the Chained Certificate file, Apache doesn't.
  6. cap ssl:install to upload the certificate files to your web server.

Let me know

Did this work for you or do you use another approach? Just ping me at @tbuehl

This is a Nuts & Bolts Series post – join the mailing list below to get more tips & tricks.


← Back to Overview

Try our simple and powerful
application error tracking