SSL certificate with letsencrypt and haproxy

Introduction

Letsencrypt allows you to get SSL certificates for your domain for free. Here I'll show you how to obtain such a certificate using the acme client and use it in haproxy.

Install acme client

The Acme client is used to request a ssl certificate from letsencrypt.

# installs acme client to ~/.acme.sh
curl -s https://get.acme.sh | sh

# creates symbolic link to acme client make it accessible
ln -s ~/.acme.sh/acme.sh /usr/local/bin/acme

Prepare acme and haproxy

Create folder for your certificates

mkdir /etc/ssl/acme

Add certificate path to haproxy.cfg

...
frontend http-in
   ...
   bind *:443 ssl crt /etc/ssl/acme

Register letsencrypt account

acme --register-account

In the output you'll see the ACCOUNT_THUMBPRINT. You will need that later to verify your domain.

Install cronjob to revalidate your certificates periodically

acme --install-cronjob

Script for creating certificate in haproxy compatible format

Haproxy requires certificates in a special format. They have to contain the full chain as well as the key. Therefore we create a script, which will be executed after issuing a certificate to create these files.

Add the file /usr/local/bin/create_cert with the following content:

#!/bin/sh 
cat $LE_WORKING_DIR/$1/fullchain.cer $LE_WORKING_DIR/$1/$1.key > /etc/ssl/acme/$1.pem;

Make it executable:

chmod +x /usr/local/bin/create_cert

Create a service which verifies your domains

Method 1: With docker container

I already created a docker image, which includes the script below. You can start it via:

docker run -d -e TOKEN='insert the ACCOUNT_THUMBPRINT here' --name ssl_verify -p '9090:8080' mightyplow/stateless-ssl-verify

Method 2: Build it yourself

I use nodejs here to create that service.

Create the file /usr/local/bin/verify_domain with the following content:

#!/usr/bin/env node

const token = [insert the ACCOUNT_THUMBPRINT here];
const port = 9090;

const http = require('http');
const path = require('path');

const server = http.createServer((req, res) => {
   const requestToken = path.basename(req.url);
   res.end(requestToken + '.' + token);
});

server.listen(port);

Make the script executable

chmod +x /usr/local/bin/verify_domain

Start the service in background

verify_domain &

Add the route to the verification service to your haproxy.cfg

frontend http-in
   ...
   acl host_verify path_beg -i /.well-known/
   ...
   use_backend verify-ssl if host_verify
   ...

backend verify-ssl
   server node1 localhost:9090

Reload the haproxy config:

systemctl reload haproxy

Create and install certificate for domain

acme --issue --standalone -d [DOMAIN] --httpport [ANY_FREE_PORT] --post-hook "create_cert [DOMAIN]" --reloadcmd "systemctl reload haproxy"

The port is required when you're running another service on port 80, which is usually a web server or the haproxy. You can choose any free port.

Done

That's it. For any new domain, you can repeat the step for creating and installing the certificate.