Skip to content

Instantly share code, notes, and snippets.

@lmmendes
Last active August 31, 2024 23:22
Show Gist options
  • Save lmmendes/7fad9a2c8f389b39b6661d0945b72cb7 to your computer and use it in GitHub Desktop.
Save lmmendes/7fad9a2c8f389b39b6661d0945b72cb7 to your computer and use it in GitHub Desktop.
# HAProxy and Let's Encrypt

HAProxy and Let's Encrypt

HAProxy is a open-source TCP/HTTP load-balancing proxy server supporting native SSL, keep-alive, compression CLI, and other modern features.

Let’s Encrypt is a free, automated, and open certificate authority (CA), run for the public’s benefit. Let’s Encrypt is a service provided by the Internet Security Research Group (ISRG).

Concept

Let's Encrypt offers many options and plugins to create and validate certificate via its client.

If you’re running a local webserver for which you have the ability to modify the content being served, and you’d prefer not to stop the webserver during the certificate issuance process, you can use the webroot plugin to obtain a cert by including certonly and --webroot on the command line. In addition, you’ll need to specify --webroot-path or -w with the top-level directory (“web root”) containing the files served by your webserver. For example, --webroot-path /var/www/html or --webroot-path /usr/share/nginx/html are two common webroot paths. Read more about this option here https://certbot.eff.org/docs/using.html#webroot

In our production environment we have the following setup, HAProxy will handle all HTTP and HTTPS connections (it will terminate the HTTPS connections) and forward all incomming connections to the backend servers in HTTP.

Meanwhile let's encrypt client will be running in the background auto renewing the certificates when they are about to expire.

Let’s Encrypt CA issues short-lived certificates (90 days). Make sure you renew the certificates at least once in 3 months.

See https://certbot.eff.org/docs/using.html#renewal for more information.

HAProxy & Let's Encrypt

But there is a catch in our design, Let's Encrypt client using the --standalone plugin will try to launch a webserver that will bind it self to port 80 or 443 (depending the configuration) in order to perform the domain validation.

This happens because Let's Encrypt server will try to make request to the Let's Encrypt client web server using only the port 80 or 443.

So the problem is that you can't stop HAProxy to allow that the Let's Encript client binds to the port 80 or 433 because that means downtime.

So here is a good exercise to use HAProxy proxy capabilities, our plan will to be we will have HAProxy running and binding to the 80 and 443 ports, handling incoming HTTP and HTTPS connections to our backends but when a request comes from the Let's Encrypt server trying to validate the domain, HAProxy will proxy the request to the Let's Encrypt client's web server running on a different port in this case we will be using the 63443 port.

Assumptions and prerequisites

This tutorial was created using Ubuntu 14.04 and HAProxy 1.5. The version of the letencrypt at the time was 0.8.1.

I assume that you how a domain eg: letsencrypt.passworks.io

1.Update the base system

Update your server's package manager:

$ sudo apt-get update

Ensure th

$ sudo apt-get upgrade

2. Install Let's encrypt client

Install the git and bc packages with apt-get:

$ sudo apt-get -y install git bc

We can now clone the Let’s Encrypt repository in /opt with this command:

sudo git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt

3. Install HAProxy

Before installing HAProxy we need to install install

sudo apt-get install --yes software-properties-common

Install HAProxy

sudo apt-add-repository ppa:vbernat/haproxy-1.5
sudo apt-get update
sudo apt-get install --yes haproxy

4. Configure HAProxy

frontend fe_http
        log global
        mode http
        option httplog
        bind PUBLIC_HAPROXY_IP:80
        acl app_letsencrypt path_beg /.well-known/acme-challenge/
        use_backend be_letsencrypt if app_letsencrypt
        
frontend fe_https
        log global
        mode http
        option httplog
        bind 0.0.0.0:443 ssl crt /etc/haproxy/ssl/

        tcp-request inspect-delay 5s
        tcp-request content accept if { req.ssl_hello_type 1 }

HAProxy backend rule for HTTP (80) traffic:

backend be_letsencrypt
        log global
        mode http
        option httplog
        server srv_letsencrypt 127.0.0.1:63443

5. Generate certificates using Let's Encrypt

Let's try to generate our certificates for passworks.io and for the subdomain www.passworks.io (don't forget to replace the domain passworks.io and www.passworks.io with or desired domain and replace [email protected] with our own e-mail address.

To generate the certifica type:

/opt/letsencrypt/letsencrypt-auto certonly \
    --authenticator standalone \
    --keep-until-expiring \
    --standalone-supported-challenges http-01 \
    --http-01-port 63443 \
    --text \
    --expand \
    --agree-tos \
    --email [email protected] \
    -d passworks.io \
    -d www.passworks.io \

If everything goes right you will have your new certificates in /etc/letsencrypt/live

Now we will need to copy the new certificates to a folder where HAProxy can see them so let's start to create the folder inside the HAProxy installation folder:

mkdir -p /etc/haproxy/ssl/

Let's concat the private certificate with the full public certificate chain and make it available to HAProxy:

cat /etc/letsencrypt/live/passworks.io/{fullchain.pem,privkey.pem} > /etc/haproxy/ssl/passworks.io.pem

Now restart your hproxy and open your browser and start testing your new certificates:

sudo service haproxy reload

6. Automation

#!/bin/bash

# Path to the letsencrypt-auto tool
LETSENCRYPT_BIN=/opt/letsencrypt/letsencrypt-auto

# Directory where the acme client puts the generated certs
LETSENCRYPT_CERT_OUTPUT=/etc/letsencrypt/live

# Create or renew certificate for the domain(s) supplied for this tool
$LETSENCRYPT_BIN certonly \
    --authenticator standalone \
    --keep-until-expiring \
    --standalone-supported-challenges http-01 \
    --http-01-port 63443 \
    --text \
    --expand \
    --agree-tos \
    --email [email protected] \
    -d passworks.io \
    -d www.passworks.io \
    -d brodo.passworks.io \
    -d manage.passworks.io \
    -d api.passworks.io

# Cat the certificate chain and the private key together for haproxy
for path in $(find $LETSENCRYPT_CERT_OUTPUT/* -type d -exec basename {} \;); do
  cat $LETSENCRYPT_CERT_OUTPUT/$path/{fullchain.pem,privkey.pem} > /etc/haproxy/ssl/${path}.pem
done

Edit your crontab using sudo crontab -e

2       1,13       *       *       *       sudo /home/ubuntu/letsencrypt/letsencrypt-passworks.sh
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment