A fresh Ubuntu box, traffic to terminate, and a directive to “put HAProxy in front of it.” This article is the install procedure we follow in production: pull the right package version, get the service running under systemd, validate the config syntax, open the firewall narrowly, and leave the box in a state where a real haproxy.cfg can land safely.
How to verify
Before installing, check what is already there. Some Ubuntu images ship with an older HAProxy by default, and a Nginx or Apache process may already be bound to ports 80/443.
ss -ltnp | grep -E ':(80|443|8404)\b'
dpkg -l | grep -E '^ii.*haproxy'
systemctl list-unit-files | grep haproxy
apt-cache policy haproxy
The apt-cache policy line is what you compare against the version you actually want. Ubuntu LTS often ships a year-old HAProxy in the main archive; the official vbernat/haproxy-X.X PPA keeps you on a current stable.
What’s happening
The haproxy package on Ubuntu installs a single binary, a systemd unit, an empty config skeleton at /etc/haproxy/haproxy.cfg, a default chroot under /var/lib/haproxy/, and a haproxy system user. The unit reads EXTRAOPTS from /etc/default/haproxy if present, calls haproxy -f /etc/haproxy/haproxy.cfg, and uses Type=notify so systemd knows when the process is actually ready to accept connections.
Two install sources matter:
- Ubuntu archive (
apt install haproxy) — easy, supported, but the version is whatever the LTS shipped with. Acceptable for small sites. - vbernat PPA — maintained by HAProxy’s Debian packager. Tracks current stable (2.8 LTS, 3.0 LTS, etc.). This is what we use for any production fleet because it gives us a predictable upgrade cadence inside a single major.
You do not need to compile from source for normal deployments. Custom Lua, OpenSSL pins, or QUIC support are the only legitimate reasons to leave the packaged binary.
The procedure
-
Add the PPA and refresh. Pin to a specific HAProxy major so a future PPA tag bump does not silently move you across LTS lines:
sudo apt update sudo apt install -y software-properties-common sudo add-apt-repository -y ppa:vbernat/haproxy-2.8 sudo apt update -
Install the package. The PPA name and the apt package name are both
haproxy; apt picks the higher version from the PPA automatically:sudo apt install -y haproxy haproxy -vv | head -3 -
Validate the shipped config and start. The default
/etc/haproxy/haproxy.cfgcontains aglobalanddefaultsblock with no frontend or backend, so the service starts but accepts no traffic — exactly what you want for a first boot:sudo haproxy -c -f /etc/haproxy/haproxy.cfg sudo systemctl enable --now haproxy sudo systemctl status haproxy --no-pager -
Open the firewall narrowly. Use the application profile if one is registered; otherwise allow the ports you intend to serve. Do not blanket-allow 1024-65535:
sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw reload sudo ufw status verbose -
Add a minimal real frontend. Replace the default
defaultsblock with one that hastimeout client,timeout server,timeout connect, plus an HTTP frontend that proxies to a single backend. See HAProxy frontend and backend basics for the file we drop in. -
Reload (don’t restart). HAProxy supports hot reload via
systemctl reload haproxy. The unit shipped with the PPA wires this to a SIGUSR2 / master-worker reload that drains old workers — sessions are not killed. Always prefer reload over restart in production:sudo haproxy -c -f /etc/haproxy/haproxy.cfg sudo systemctl reload haproxy -
Wire logs. HAProxy logs over a Unix socket to the local syslog daemon by default. On Ubuntu that means rsyslog or journald. The default rsyslog config does not have an
haproxy.logrecipe; drop one in/etc/rsyslog.d/49-haproxy.conf:if $programname == 'haproxy' then -/var/log/haproxy.log & stopRestart rsyslog, then verify lines land. The default format is a single dense line per request; for production we move to a structured format documented in HAProxy custom log format.
Common pitfalls
- Installing from the Ubuntu archive on a long-lived box leaves you on an HAProxy that ages out of upstream support after a couple of years — schedule a PPA migration before that happens.
- The default
chroot /var/lib/haproxymeans HAProxy cannot read files outside the chroot. Putting certificates in/etc/ssl/private/and trying to reference them by path works only because HAProxy reads them before chrooting; runtime path references that resolve later (Lua, error files) will fail. systemctl restart haproxydrops in-flight TCP connections. Always usereloadunless you are changing a directive that requires a process restart (rare — mostlynbthread,nbproc, and the master-worker model itself).- The shipped systemd unit has
Type=notifybut expects the binary to support sd_notify — the-Ws(master-worker) mode does. If you start HAProxy with a custom command that omits-Ws, the unit hangs at “activating” until timeout. - Forgetting to
haproxy -cbefore reload is the classic outage cause; build a habit of running configtest, then reload, in that order — or wire it into a deployment script.
Stack Harbor stands up HAProxy as the default L4/L7 edge for clients running clustered or multi-region applications — install scripted, config under Git, certificates auto-rotating, runtime API gated behind mTLS. If you have an HAProxy fleet that drifted across versions and config styles and you want it brought under one operating playbook, this is what we run as part of Managed Operations.