Skip to main content

HAProxy

Install

sudo apt update
sudo apt install -y haproxy
sudo systemctl enable --now haproxy

Ubuntu packages include a systemd unit.

Basic reverse proxy (HTTP only first)

Edit /etc/haproxy/haproxy.cfg:

global
log /dev/log local0
log /dev/log local1 notice
maxconn 4096
daemon

defaults
mode http
log global
option httplog
option forwardfor
timeout connect 5s
timeout client 30s
timeout server 30s

frontend http-in
bind :80
# ACME HTTP-01 challenge goes to certbot (see HTTPS section)
acl acme_http path_beg /.well-known/acme-challenge/
use_backend acme if acme_http
default_backend app

backend app
server app1 127.0.0.1:3000 check

mode http lets HAProxy parse/modify HTTP and add X-Forwarded-For.

Reload:

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

HTTPS with Let’s Encrypt (no downtime approach)

Run Certbot in standalone mode on an alternate port and route only ACME paths to it:

  1. Start/renew certificates via Certbot on port 8888:
sudo snap install --classic certbot
sudo certbot certonly --standalone --preferred-challenges http \
--http-01-port 8888 -d example.com
  1. Add an ACME backend to HAProxy:
backend acme
server certbot 127.0.0.1:8888
  1. Concatenate cert + key for HAProxy:
sudo mkdir -p /etc/haproxy/certs
sudo bash -c 'cat /etc/letsencrypt/live/example.com/fullchain.pem \
/etc/letsencrypt/live/example.com/privkey.pem \
> /etc/haproxy/certs/example.com.pem'
sudo chmod 600 /etc/haproxy/certs/example.com.pem
  1. Add HTTPS frontend and redirect HTTP→HTTPS:
frontend https-in
bind :443 ssl crt /etc/haproxy/certs/example.com.pem
default_backend app

# Optional: redirect all plain HTTP to HTTPS when not ACME
frontend http-in
bind :80
acl acme_http path_beg /.well-known/acme-challenge/
use_backend acme if acme_http
http-request redirect scheme https code 301 if !acme_http
default_backend app

Renewals: schedule certbot renew (snap sets systemd timers) and re-concat the PEM via a deploy hook:

sudo bash -c 'printf "%s\n" "#!/bin/sh" \
"cat /etc/letsencrypt/live/example.com/fullchain.pem /etc/letsencrypt/live/example.com/privkey.pem > /etc/haproxy/certs/example.com.pem" \
"systemctl reload haproxy" > /etc/letsencrypt/renewal-hooks/deploy/haproxy.sh'
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/haprory.sh

Notes

  • HAProxy config/manual references for HTTP mode & examples: docs.haproxy.org
  • Let’s Encrypt challenge types context (HTTP-01): Let's Encrypt
  • HAProxy + ACME approaches (overview with acme.sh option): blog post.