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:
- 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
- Add an ACME backend to HAProxy:
backend acme
  server certbot 127.0.0.1:8888
- 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
- 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.shoption): blog post.