# /etc/haproxy/haproxy.cfg
# (c) 2026, Bernard Condrau (this config file)
#
# 2026-09-14: initial
#
# Features:
# HTTP/1, HTTP/2, HTTP/3, acme integration, static blacklist, static and dynamic whitelist
# handles transition from one backend server without reverse proxy to HAProxy with several backend servers
# Notes:
# # = comment, ## = acme integration, remove ## once you have set up acme (https://www.haproxy.com/blog/haproxy-and-let-s-encrypt)
# Remove the 'default_backend' once transition is done from Certbot / Apache to acme / HAProxy
# Remove 'public_inbound_https' once all hosts are SSL terminated here
#
# 0. Settings
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
user haproxy
group haproxy
daemon
# Modern SSL/TLS security settings for Debian 13
# Minimum TLS version requirement
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
# TLS 1.3 Cipher Suites (handled by ssl-default-bind-ciphersuites)
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
# TLS 1.2 Ciphers (handled by ssl-default-bind-ciphers)
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
# Administrative socket for stats/management
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
## setenv ACCOUNT_THUMBPRINT 'lCufto4sDRTHdmWL0EugFywGV54hBCuTTXvwifi65R4'
defaults
log global
mode tcp
log-format "%ci:%cp [%t] %ft %b/%s %Tw/%Tc/%Tt %B %ts %ac/%fc/%bc/%sc/%rc %sq/%bq"
timeout connect 5s
timeout client 30s
timeout server 5m # set this high enough to allow complex websites to reply, for example Joomla during install or php calculations
# 1. Public Interface (SNI Router)
# Receives traffic through TCP (HTTP)
frontend public_inbound_http
bind *:80
mode http
# Handle Letsencrypt requests
##http-request return status 200 content-type text/plain lf-string "%[path,field(-1,/)].${ACCOUNT_THUMBPRINT}\n" if { path_beg '/.well-known/acme-challenge/' }
# Deny traffic based on origin
http-request deny if { src -f /etc/haproxy/blacklist.lst }
# Redirect all incoming traffic to HTTPS permanently
http-request redirect scheme https code 301 unless { ssl_fc }
# Receives traffic through TCP (HTTP/1, HTTP/2)
frontend public_inbound_https
bind :443
mode tcp
# Deny traffic based on origin
tcp-request connection reject if { src -f /etc/haproxy/blacklist.lst }
# Wait for the TLS client hello to parse the SNI extension
tcp-request inspect-delay 5s
tcp-request content accept if { req.ssl_hello_type 1 }
# Define the domains (one line for each) you want to terminate SSL for
# Remove once all hosts are terminated here
acl terminate_ssl req.ssl_sni -i bak.condrau.com
# Route conditionally based on SNI
use_backend http_dispatcher if terminate_ssl
default_backend passthrough_server
# Receives traffic through UDP (HTTP/3), decrypts SSL, and processes HTTP rules
# HTTP/3 cannot be routed to apache web server without decrypting SSL first
frontend public_inbound_https3
# pass certs directory path so all pem files will be loaded
# uncomment the following line once all hosts are SSL terminated here
# bind :443
bind quic4@:443 ssl crt /etc/haproxy/certs/ accept-proxy strict-sni
mode http
# Handle Letsencrypt requests
##http-request return status 200 content-type text/plain lf-string "%[path,field(-1,/)].${ACCOUNT_THUMBPRINT}\n" if { path_beg '/.well-known/acme-challenge/' }
# Deny traffic based on origin (host aware)
acl trusted_ip src -f /etc/haproxy/whitelist.lst # static whitelist
acl whitelist_ip src,in_table(stick_table_whitelist) # dynamic whitelist
acl backuppc hdr(host) -i bak.condrau.com
http-request deny if backuppc !trusted_ip !whitelist_ip
# Let clients know we support HTTP/3
http-response set-header Alt-Svc 'h3=":443"; ma=86400'
# Inject headers so backend servers know the original request was HTTPS
http-request set-header X-Forwarded-Proto https
http-request set-header X-Forwarded-Port 443
http-request set-header X-Forwarded-For %[src]
# Route traffic based on HTTP host headers
use_backend backuppc_server if backuppc
default_backend http_server
# 2. Transparent passthrough for encrypted TLS domains
backend passthrough_server
mode tcp
# downstream server handling its own TLS certificates (Calypso)
server calypso 192.168.3.90:443 check
# 3. Intermediary loopback backend
# This forwards traffic to the Layer 7 (HTTP) processing frontend
backend http_dispatcher
mode tcp
server loopback 127.0.0.1:8443 send-proxy-v2
# 4. Layer 7 http frontend (HTTP/1, HTTP/2)
# Receives traffic from the loopback, decrypts SSL, and processes HTTP rules
frontend internal_inbound_http
# pass certs directory path so all pem files will be loaded
bind 127.0.0.1:8443 ssl crt /etc/haproxy/certs/ accept-proxy strict-sni
mode http
# Handle Letsencrypt requests
##http-request return status 200 content-type text/plain lf-string "%[path,field(-1,/)].${ACCOUNT_THUMBPRINT}\n" if { path_beg '/.well-known/acme-challenge/' }
# Deny traffic based on origin (host aware)
acl trusted_ip src -f /etc/haproxy/whitelist.lst # static whitelist
acl whitelist_ip src,in_table(stick_table_whitelist) # dynamic whitelist
acl backuppc hdr(host) -i bak.condrau.com
http-request deny if backuppc !trusted_ip !whitelist_ip
# Let clients know we support HTTP/3
http-response set-header Alt-Svc 'h3=":443"; ma=86400'
# Inject headers so backend servers know the original request was HTTPS
http-request set-header X-Forwarded-Proto https
http-request set-header X-Forwarded-Port 443
http-request set-header X-Forwarded-For %[src]
# Route traffic based on HTTP host headers
use_backend backuppc_server if backuppc
default_backend http_server
# 5. HTTP backends
backend backuppc_server
mode http
http-reuse safe
cookie SERVERID insert indirect nocache
server epione 192.168.1.50:8080 check
backend http_server
mode http
http-reuse aggressive
cookie SERVERID insert indirect nocache
server serenity 192.168.3.100:80 check
# 6. stick table that stores IPv4 addresses
backend stick_table_whitelist
mode http
stick-table type ip size 10 expire 1h