Howto configure your HAProxy-server with standard settings for haproxy.cfg Examples for minimal security, a wizard for configuring front- and backends and some checks.
  • JavaScript 42.7%
  • HTML 35.3%
  • Shell 20.4%
  • Lua 1.1%
  • Python 0.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-11-03 15:18:43 +01:00
builder HAProxy Builder 1.4.7 2026-11-03 15:18:43 +01:00
dist HAProxy Builder 1.4.7 2026-11-03 15:18:43 +01:00
package HAProxy Builder 1.4.7 2026-11-03 15:18:43 +01:00
tests HAProxy Builder 1.4.7 2026-11-03 15:18:43 +01:00
.gitattributes HAProxy Builder 1.4.7 2026-11-03 15:18:43 +01:00
.gitignore HAProxy Builder 1.4.7 2026-11-03 15:18:43 +01:00
CHANGELOG.md HAProxy Builder 1.4.7 2026-11-03 15:18:43 +01:00
LICENSE HAProxy Builder 1.4.7 2026-11-03 15:18:43 +01:00
README.md HAProxy Builder 1.4.7 2026-11-03 15:18:43 +01:00
VERSION HAProxy Builder 1.4.7 2026-11-03 15:18:43 +01:00

HAProxy Builder

A web tool that builds a complete, secure HAProxy setup: haproxy.cfg, map files, error pages, certificate handling, security.txt and the scripts to install and update it on a server. You fill in your sites and backends; the builder makes sure the result meets the minimum security requirements of Internet.nl (100% score), SSL Labs (A+), the NCSC TLS guidelines (as required by the Dutch Forum Standaardisatie) and the relevant RFCs – with defaults that never break a site.

Builder version 1.4.7 (see VERSION, CHANGELOG.md)
Author BvO
License MIT
Generates config for HAProxy 3.4 LTS
Tested with HAProxy 3.4.0
Minimum HAProxy 3.2 (needed for default-crt and named defaults)

The builder is one HTML file, dist/haproxy-builder.html. It runs in any current browser, needs no server and no internet connection, and keeps your project in the browser until you download it.


Contents

  1. Quick start
  2. Minimum requirements
  3. Installing HAProxy 3.4
  4. First installation on the server
  5. Later changes
  6. Security requirements the builder enforces
  7. CrowdSec and the WAF
  8. Importing an existing configuration
  9. Version numbers
  10. Repository layout
  11. Development
  12. License

Quick start

  1. Open dist/haproxy-builder.html in a browser.

  2. Use Add a site (wizard), or Import your current haproxy.cfg or a package ZIP.

  3. Check Compliance and Checks. The download is only possible when there are no errors.

  4. Download ZIP, copy it to the server and run:

    unzip haproxy-<name>.zip
    sudo haproxy-<name>/setup.sh
    

    setup.sh installs the package into /usr/local/src/haproxy-<name> and runs from there.

Minimum requirements

To use the builder: a current browser (Chrome, Edge, Firefox, Safari).

On the server:

Requirement Why
Linux with systemd; Debian 12+ or Ubuntu 22.04+ (default) the scripts use systemctl and apt-get; RHEL/Rocky/Alma/Fedora work with dnf for the tools and CrowdSec
HAProxy 3.2 or newer, 3.4 LTS recommended features used in the generated config
HAProxy built with OpenSSL 1.1.1+ TLS 1.3
HAProxy built with Lua security.txt is served by a small Lua script
HAProxy built with QUIC only when HTTP/3 is switched on
OpenSSL 3.5+ only for the post-quantum option (X25519MLKEM768)
root access (sudo) installs into /etc/haproxy
ports 80/tcp and 443/tcp open (443/udp for HTTP/3) plus extra frontend ports you add
DNS A and AAAA records pointing to the server before certificates are requested
bash, openssl, curl, socat, python3 setup.sh installs what is missing; acme.sh and gpg too when needed

Too old or missing a feature? setup.sh and update.sh first run scripts/preflight.sh. It reads the package's REQUIREMENTS file and haproxy -vv, and stops before anything is changed when:

  • HAProxy is older than 3.2 (with instructions to install 3.4),
  • HTTP/3 is used but HAProxy has no QUIC support,
  • security.txt is used but HAProxy has no Lua,
  • the post-quantum option is used but OpenSSL is older than 3.5.

A newer HAProxy than the tested version is accepted; haproxy -c then confirms the configuration. You can run the check by hand:

sudo /usr/local/src/haproxy-<name>/scripts/preflight.sh

Example output on a server that is too old:

==> HAProxy compatibility
    ERROR  HAProxy 2.8.5 is too old: this configuration needs 3.2 or newer (tested with 3.4.0)
           Install a current HAProxy (3.4 LTS) from haproxy.debian.net:
             Ubuntu:  sudo add-apt-repository ppa:vbernat/haproxy-3.4 && sudo apt install haproxy=3.4.\*
             Debian:  follow https://haproxy.debian.net (choose your release and 3.4)
    -> not compatible: nothing was changed

Installing HAProxy 3.4

Distribution packages are often older than 3.2. Use the packages from haproxy.debian.net (built with OpenSSL, Lua and QUIC):

# Ubuntu
sudo add-apt-repository ppa:vbernat/haproxy-3.4
sudo apt update
sudo apt install haproxy=3.4.\*

# Debian: pick your release and "3.4" on https://haproxy.debian.net
# and follow the commands shown there.

haproxy -vv | head -1          # HAProxy version 3.4.x ...

First installation on the server

scp haproxy-<name>.zip server:
ssh server
unzip haproxy-<name>.zip            # anywhere, e.g. your home folder
# own certificates? put them in haproxy-<name>/certs/own/ first
sudo haproxy-<name>/setup.sh

Where it is installed. setup.sh copies the package to /usr/local/src/haproxy-<name> (that folder exists on every Linux system, see the Filesystem Hierarchy Standard) and continues from there; the unpacked folder can then be deleted. Certificates in certs/own/, certs/credentials.env, crowdsec/crowdsec.env and signed security.txt files already in that folder are kept; the previous version is kept as haproxy-<name>.previous. Set HAPROXY_PKG_DIR to use another folder.

setup.sh does everything in one step:

  1. compatibility check (scripts/preflight.sh: HAProxy version and build features, operating system), missing tools;
  2. CrowdSec, when it is on in the project: detects the operating system, adds the CrowdSec repository, installs crowdsec and crowdsec-haproxy-spoa-bouncer (apt on Debian/Ubuntu, dnf on the RHEL family), configures them, and checks every dependency – nothing is installed in /etc/haproxy if something is missing;
  3. security.txt, certificates (Let's Encrypt or your own), the statistics password, a test of every backend;
  4. haproxy -c, a backup of /etc/haproxy, a start test, and the restart.

Safe to run again, also with a newer ZIP: existing packages, repositories and keys are kept, services are only restarted when their configuration changed, and a configuration that fails haproxy -c or does not start is never left running – the previous /etc/haproxy is restored and HAProxy keeps running with it. BUILD-NOTES.md in the package explains the details of your project.

The WAF is never installed by setup.sh: it is off by default and, when you switch it on, installed separately on its own VM (see below).

Later changes

  1. Change the project in the builder (or import the package ZIP first when you use another browser).

  2. Download the ZIP and copy it to the server.

  3. Run either of:

    sudo /usr/local/src/haproxy-<name>/update.sh ~/haproxy-<name>.zip
    # or: unzip haproxy-<name>.zip && sudo haproxy-<name>/setup.sh
    

Both do the same: show the builder versions (old → new), run the compatibility check, replace the package in /usr/local/src/haproxy-<name> (keeping certificates and secrets), keep the changes made on the server (blocklist, maintenance list, extra security.txt domains), show what changes in haproxy.cfg, ask to confirm, and install with backup and rollback.

Security requirements the builder enforces

The minimum is a 100% score on Internet.nl, plus SSL Labs A+ and the NCSC TLS guidelines. Internet.nl only counts its required subtests for the score; recommended and optional subtests show advice but never lower it. The builder follows the same split:

  • Required settings cannot be weakened: weak options are not offered, imports are raised to the minimum, and custom lines that would weaken them are errors that block the download.
  • Recommended / optional settings get defaults that never break a site, and you can change them. The most important one is the CSP: HAProxy sets no CSP by default, so every site keeps the CSP its own backend sends. A CSP you add may contain 'unsafe-inline'; Internet.nl then gives advice, but the score stays 100%.
Area Setting Internet.nl level Builder default Source
TLS only TLS 1.2 and 1.3 required 1.2 + 1.3 Internet.nl, SSL Labs, NCSC, RFC 8996
TLS ECDHE with AES-GCM / ChaCha20-Poly1305, server cipher order required all "good" suites Internet.nl, SSL Labs, NCSC, RFC 9325
TLS X25519, P-256, P-384, X448 (+ optional X25519MLKEM768) required X25519, P-256, P-384 Internet.nl, NCSC
TLS SHA-2 signatures only; no compression, no 0-RTT required – Internet.nl, RFC 9155, RFC 5746
TLS at least TLS 1.2 to backends (NCSC) 1.2 NCSC
HTTP port 80 redirects to HTTPS required on Internet.nl, Forum Standaardisatie
HTTP HSTS max-age ≥ 1 year required 1 year, no includeSubDomains Internet.nl, SSL Labs A+, RFC 6797
HTTP X-Content-Type-Options: nosniff recommended on Internet.nl
HTTP Referrer-Policy recommended strict-origin-when-cross-origin (browser default) Internet.nl
HTTP Content-Security-Policy recommended none from HAProxy Internet.nl
HTTP security.txt (Expires < 1 year) recommended on Internet.nl, Forum Standaardisatie, RFC 9116
HTTP X-Frame-Options optional SAMEORIGIN Internet.nl, RFC 7034
HTTP Permissions-Policy, COOP, CORP not tested off (they can break logins, maps, embeds) –
Network IPv6 required on Internet.nl, Forum Standaardisatie
Outside HAProxy DNSSEC, RPKI (required), CAA (recommended) set at your DNS / network provider Internet.nl

The Compliance page in the builder shows this list for your project.

Addresses, NAT and certificates

Addresses. The page Addresses & NAT describes how the server is reached: one public IPv4 address behind NAT, several public addresses directly on the server, or a mix – for IPv4 and IPv6 alike. By default HAProxy listens on all addresses (one listener per port for IPv4 and IPv6), so every situation works without further settings; "only these addresses" binds to the listed local addresses instead. Either way IPv4 and IPv6 get exactly the same frontends, routing and headers, so Internet.nl never sees two different websites. The listed addresses are used for:

  • the port forwarding table (router) and the firewall ports (IPv4 and IPv6) in BUILD-NOTES.md;
  • the DNS records every host name needs (A and AAAA);
  • scripts/check-ipv46.sh (run by setup.sh, also usable from outside): it checks that every A/AAAA record points to this server and that IPv4 and IPv6 give the same status, security headers and page. A record that points to another server, or a port opened for one family only, is the usual reason Internet.nl reports "different content on IPv6".

install.sh stops before restarting when a listed local address is not configured on the server, and drops IPv6 listeners on a server without IPv6.

Certificates. Choose Let's Encrypt only, own certificates only, or both (for example Let's Encrypt for most names and a purchased wildcard for the rest). Own certificates are not in the ZIP; you add them on the server yourself:

Where /usr/local/src/haproxy-<name>/certs/own/ (before the first installation also certs/own/ in the unpacked ZIP)
Files per certificate <name>.pem (certificate + intermediates + key), or <name>.crt + <name>.key (+ <name>.ca-bundle)
Rights chown -R root:root …/certs/own, chmod 700 …/certs/own, chmod 600 …/certs/own/*
Installed as /etc/haproxy/ssl/certs/<first name>.pem, owner root, group haproxy, mode 640
Renewal put the new files in the same folder and run setup.sh again

List the names of each own certificate in the builder (Certificates page): it then checks that every host name is covered, and setup.sh ends with "STILL TO DO" as long as one is missing.

Mail is passed through by default: TLS untouched (the mail server keeps its own certificates) and the PROXY protocol for the real client address, to one or more mail servers (failover or spread).

CrowdSec and the WAF

CrowdSec (optional) runs on the HAProxy server. When it is on and the project has no errors, setup.sh installs it during the first installation: it reads /etc/os-release, uses apt on Debian/Ubuntu (the default) or dnf on RHEL/Rocky/Alma/Fedora, adds the CrowdSec repository only when it is not there yet, and installs CrowdSec and the HAProxy SPOA bouncer. On other systems install those two packages by hand first; preflight.sh warns before anything changes. Then setup.sh runs crowdsec/check-crowdsec.sh, which checks every dependency: the packages, both services, the local API, the crowdsecurity/haproxy collection, the log acquisition, the bouncer's API key and listening port, and the captcha files. If anything is missing it prints the fix and installs nothing, because the configuration depends on the bouncer.

The WAF (Coraza with the OWASP Core Rule Set) is off by default and optional; the builder only shows how to install it. It belongs on a separate VM: it uses a lot of CPU and memory, and a problem in the WAF cannot take HAProxy down. When you switch it on, the builder first explains what you need and shows a checklist: the VM's host name or IP address (optionally with a port), and HAProxy's address as the VM sees it. The download stays blocked until the checklist is complete. Then:

  1. copy the package's waf-vm/ folder to the VM and run sudo ./setup-waf-vm.sh there;
  2. run sudo ./setup.sh on the HAProxy server; it tests whether the VM answers.

HAProxy only sends requests to the WAF while its health check reaches the VM (nbsrv(coraza-spoa) gt 0). Until then requests pass uninspected (unless you chose fail-closed), so a missing or broken VM never takes your sites down. setup.sh ends with WAF: ACTIVE or WAF: NOT ACTIVE yet. New projects start in detect mode: switch to block after checking the log.

Importing an existing configuration

Import accepts a package ZIP, a project file (*.haproxy-builder.json), or loose files (haproxy.cfg, maps, certificates.txt, ...). The import report has four parts:

  • Imported – what was read into the builder;
  • Adjusted to meet the minimum requirements – settings that were weaker than allowed and what they were changed to (for example TLS 1.0 → 1.2, HSTS 600 s → 1 year, a CSP with unsafe-inline no longer set by HAProxy);
  • Needs your attention – what could not be imported completely, such as a plain-text statistics password;
  • Still to do before download – the remaining errors, each with a link to the page where you fix it.

Lines the builder has no field for are kept as custom lines; credentials are never imported. A project made by a newer builder version gives a warning.

Version numbers

The builder follows Semantic Versioning: MAJOR.MINOR.PATCH.

  • PATCH (1.0.1): fixes, no change to projects or generated config behaviour;
  • MINOR (1.1.0): new options or checks; existing projects still load;
  • MAJOR (2.0.0): an incompatible change in projects or in the generated configuration (for example a new minimum HAProxy version).

The version appears top right in the builder, in the header of every generated haproxy.cfg, in REQUIREMENTS and in the project file:

#=======================================================================
# my-haproxy - HAProxy 3.4 LTS                    HAProxy Builder 1.4.7
#=======================================================================
# Generated by HAProxy Builder 1.4.7 on 2026-10-31.
# Tested with HAProxy 3.4.0; needs 3.2 or newer (checked by scripts/preflight.sh).

Repository layout

LICENSE                     MIT, Copyright (c) 2026 BvO
VERSION                     the builder version (must match core.js)
CHANGELOG.md
dist/haproxy-builder.html   the built tool: open this file
builder/
  build.py                  builds dist/haproxy-builder.html
  src/core.js               generator, import, checks, requirements (also runs in Node.js)
  src/ui.html               the interface
  vendor/jszip.min.js       ZIP library (MIT), inlined at build time
package/                    scripts and docs copied into every package
  scripts/preflight.sh      HAProxy version and feature check
  scripts/install.sh        installs with backup, test and rollback
  scripts/check-ipv46.sh    DNS points here, IPv4 and IPv6 give the same answer
  update.sh                 installs a newer package ZIP (runs its setup.sh)
  ...                       plus a hand-written example config (haproxy.cfg, maps)
tests/
  run.sh                    all tests (build, unit tests, shell syntax, haproxy -c)
  test-core.js
  fixtures/weak.cfg         an old configuration below the requirements

Development

Requirements: Python 3.8+, Node.js 18+, and optionally HAProxy 3.2+ for the haproxy -c test.

python3 builder/build.py      # writes dist/haproxy-builder.html
tests/run.sh                  # build + all tests
HAPROXY_BIN=/usr/local/sbin/haproxy tests/run.sh

Edit builder/src/* and package/*, never dist/ by hand; commit the rebuilt dist/haproxy-builder.html together with the source change.

Releasing a new version

  1. Update VERSION and BUILDER_VERSION in builder/src/core.js (build.py refuses to build when they differ). When the HAProxy target changes, also update HAPROXY in core.js.
  2. Add the changes under a new heading in CHANGELOG.md.
  3. tests/run.sh, then commit and tag: git tag -a v1.1.0 -m "HAProxy Builder 1.1.0".

Never commit certificates, private keys, credentials.env, crowdsec.env or a project with real host names and internal addresses to a public repository; .gitignore covers the usual files.

License

MIT – Copyright (c) 2026 BvO. Free to use, change and share, also commercially, as long as the copyright notice stays. The bundled JSZip is used under its MIT license (see builder/vendor/JSZIP-LICENSE.md).