Skip to main content

Install HAProxy & CertBot (LetsEncrypt)

HAProxy

Install HAProxy

Install HAProxy v2.4 with the following commands:

sudo add-apt-repository -y ppa:vbernat/haproxy-2.4
sudo apt update
sudo install -y haproxy

Install WhoIs (which includes the mkpasswd utility) with the following command:

sudo apt install whois

Generate an encrypted password for the config file for accessing the statistics with the following command:

echo <password> | mkpasswd --stdin --method=sha-256

Configure HAProxy

Edit the HAProxy configuration with this command:

sudo nano /etc/haproxy/haproxy.cfg

Inside the global section, add the following lines to the end of the section to allow easier loading of certificates:

	ssl-load-extra-files all
	ssl-load-extra-del-ext

Inside the configuration, leave the global and defaults section as they are but add a frontend for stats and normal web access:

userlist StatsUsers
	user <username> password <salted_password>

frontend http_fe
	bind *:80
	bind *:443 ssl crt /etc/ssl/private

	# Capture additional/longer info for logs
	http-request capture req.hdr(Host) len 30
	capture request header User-Agent len 200
	capture request header Referer len 800
	capture request header X-Forwarded-For len 20

	# Test URI to see if it's a LetsEncrypt request
	acl letsencrypt_acl path_beg /.well-known/acme-challenge/
	use_backend certbot_be if letsencrypt_acl

	# The following redirect for non-https traffic breaks if used with Cloudflare Flexible Encryption
	redirect scheme https code 301 if !{ ssl_fc }

	# Local Stats
	acl stats_acl path_beg(host) /hastats
	acl stats_auth http_auth(StatsUsers)
	http-request auth realm Stats if stats_acl !stats_auth
	use_backend stats_be if stats_acl stats_auth

	# Common Redirects
	acl <redirect_acl_name> hdr_beg(host) -i <domain_prefix>
	http-request redirect location <url> if <redirect_acl_name>

	# <Name of website or any comment - Create these lines for every domain/backend>
	acl <acl_name> hdr_beg(host) -i <domain_prefix>.
	use_backend <backend_name> if <acl_name>

	# Default backend if no ACL rules are matched (Can be deleted for no default)
	default_backend <default_backend_name>

# <Name of the website or any comment - Create this section for every domain/backend>
backend <backend_name>
	balance roundrobin

	option httpchk HEAD /

	option forwardfor
	http-request set-header X-Forwarded-Port %[dst_port]
	http-request set-header X-Forwarded-Proto https if { ssl_fc }
        
	default-server check maxconn 20
	server <server_name> <server_ip>:<server_port>

# Local stats
backend stats_be
	stats enable
	stats uri /hastats
	stats refresh 10s
	stats show-modules
	no log

# Certbot for LetsEncrypt
backend certbot_be
	server certbot 127.0.0.1:8888

Here's an example using an Ignition Server running on 192.168.120.10 using a domain name beginning with scada. for remote access. This also enables HAProxy stats on any domain name using the path /hastats using a login of hastats and a password of password:

userlist StatsUsers
	user hastats password $5$cQEGcl6Wb$nG3i246htOnuW5LTYo3O73aa7ve7i.ePiQ4v6EoMW44

frontend http_fe
	bind *:80
	bind *:443 ssl crt /etc/ssl/private

	# Capture additional/longer info for logs
	http-request capture req.hdr(Host) len 30
	capture request header User-Agent len 200
	capture request header Referer len 800
	capture request header X-Forwarded-For len 20

	# Test URI to see if it's a LetsEncrypt request
	acl letsencrypt_acl path_beg /.well-known/acme-challenge/
	use_backend certbot_be if letsencrypt_acl

	# The following redirect for non-https traffic breaks if used with Cloudflare Flexible Encryption
	redirect scheme https code 301 if !{ ssl_fc }

	# Local Stats
	acl stats_acl path_beg(host) /hastats
	acl stats_auth http_auth(StatsUsers)
	http-request auth realm Stats if stats_acl !stats_auth
	use_backend stats_be if stats_acl stats_auth

	# Ignition Perspective Redirect to Project URL
	acl scada hdr_beg(host) -i scada.
	http-request redirect location https://ignition.icstexas.com/data/perspective/client/ProjectName if scada

	# Ignition
	acl ignition hdr_beg(host) -i ignition.
	use_backend ignition_be if ignition

# Ignition
backend ignition_be
	balance roundrobin

	option httpchk HEAD /

	acl ignition_ping path_beg -i /main/StatusPing
	http-request set-path /StatusPing if ignition_ping

	option forwardfor
	http-request set-header Connection "Upgrade"
	http-request set-header X-Forwarded-Port %[dst_port]
	http-request set-header X-Forwarded-Proto https if { ssl_fc }

	default-server check maxconn 1000
	server iguana 192.168.120.10:8088 check inter 10s

# Local stats
backend stats_be
	stats enable
	stats uri /hastats
	stats refresh 10s
	stats show-modules
	no log

# Certbot for LetsEncrypt
backend certbot_be
	server certbot 127.0.0.1:8888

Any time you modify the configuration for HAProxy, you can check the configuration for errors with the following command:

sudo haproxy -f /etc/haproxy/haproxy.cfg -c

If the configuration file is valid (warnings are OK) you can reload HAProxy with the following command:

sudo service haproxy reload

Upgrade HAProxy

If you ever need to upgrade the version to a newer version, check the following site to see if the newer version exists:

https://launchpad.net/~vbernat/+ppa-packages

If it does, use the above commands substituting in the appropriate version to add the newer version's repository and upgrade HAProxy. Once HAProxy is upgraded and verified working, use the following command to remove the old version (again substituting the old version number):

sudo add-apt-repository --remove ppa:vbernat/haproxy-2.3

Certbot (LetsEncrypt)

Install CertBot (LetsEncrypt)

Install Certbot with the following commands:

sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo snap set certbot trust-plugin-with-root=ok

Create a new certificate using HTTP authentication

We'll need to get a new certificate for the domain name we want using the following command:

sudo certbot certonly --standalone -d <domain_name> --non-interactive --agree-tos --email <admin_email> --http-01-port=8888

If this fails, you'll need to comment out the following 2 lines in your HAProxy configuration so that it only listens on port 80 and not on port 443:

#	bind *:443 ssl crt /etc/ssl/private
#	redirect scheme https code 301 if !{ ssl_fc }

Don't forget to reload HAProxy after making this change and once you've created the certificate, you can un-comment these lines and reload HAProxy again.

Create a new certificate using DNS authentication

A list of DNS services that are supported along with the plugin names can be found here: https://certbot.eff.org/docs/using.html#dns-plugins

We'll need to install a plugin for the DNS service we will be using with the following command:

sudo snap install certbot-dns-<plugin>

Next you'll create your credentials file using the following command and putting the credentials needed as documented in the plugin documentation link from above:

sudo nano /root/.secrets/certbot/<dns_service_name>.ini

Now we'll get aour new certificate for the domain name we want using the following command:command (adjust as necessary according to the plugin documentation in the link above):

sudo certbot certonly --dns-<dns_service_name> --dns-<dns_service_name>-credentials /root/.secrets/certbot/<dns_service_name>.ini -d <domain>

If wanting a wildcard certificate, you'll need to use the following command instead (adjust as necessary according to the plugin documentation in the link above):

sudo certbot certonly --dns-<dns_service_name> --dns-<dns_service_name>-credentials /root/.secrets/certbot/<dns_service_name>.ini -d *.<domain>

Preparing the certificate for HAProxy

To prepare the certificate in a format HAProxy can use, we'll combine the 2 files into a single certificate using the following commands:

sudo ln -s /etc/letsencrypt/live/<domain>/fullchain.pem /etc/ssl/private/<domain>.crt
sudo ln -s /etc/letsencrypt/live/<domain>/privkey.pem /etc/ssl/private/<domain>.key

Schedule auto-renewal of certificates

We'll create a script which we'll run on a schedule to renew the certificate:

sudo nano /opt/update-certs.sh

Put the following text (update as needed) in the file:

#!/usr/bin/env bash
# Renew the certificate using TLS authentication on prot 8888 (comment out this line if using DNS authentication)
certbot renew --force-renewal --tls-sni-01-port=8888

# Renew the certificate using DNS authentication (uncomment if using DNS authentication)
#certbot renew --force-renewal

# Reload  HAProxy
service haproxy reload

Make the script executable:

sudo chmod +x /opt/update-certs.sh

We'll edit the certbot cron scheduler file to schedule this script:

sudo nano /etc/cron.d/certbot

This schedule will be used to run our renewal script on the 1st day of the month every month at midnight (add it to a new line of the file):

0 0 1 * * root bash /opt/update-certs.sh