Sane global configuration
global
maxconn 50000
log /dev/log local0 info alert
nbproc 4
cpu-map auto:1/1-4 0-3
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
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
ssl-dh-param-file /usr/local/etc/haproxy/dhparam-2048.pem
Sane defaults configuration
defaults
mode http
log global
option httplog
option dontlognull
option redispatch
option contstats
option forwardfor
retries 3
timeout connect 5s
timeout client 20s
timeout server 20s
timeout tunnel 3600s
timeout http-keep-alive 1s
timeout http-request 15s
timeout queue 30s
timeout tarpit 60s
maxconn 3000
default-server init-addr last,libc,none
errorfile 400 /usr/local/etc/haproxy/errorfiles/400.http
errorfile 403 /usr/local/etc/haproxy/errorfiles/403.http
errorfile 408 /usr/local/etc/haproxy/errorfiles/408.http
errorfile 500 /usr/local/etc/haproxy/errorfiles/500.http
errorfile 502 /usr/local/etc/haproxy/errorfiles/502.http
errorfile 503 /usr/local/etc/haproxy/errorfiles/503.http
errorfile 504 /usr/local/etc/haproxy/errorfiles/504.http
DNS resolver
resolvers resolver-name
nameserver dns1 <ip>:53
resolve_retries 3
timeout retry 1s
Userlist
userlist basic-auth-list
group <groupname> users <user1> <user2> <...>
user <username> password <password hash>
Generate password hash
mkpasswd -m sha-512 <password>
Make sure to prepend your command with a space to keep it from showing up in your history.
Bind on IPv4 and IPv6
bind :::80 v4v6
bind :::443 v4v6 ssl crt /path/to/key-cert-bundle.pem alpn h2,http/1.1
Set security headers
# HSTS
http-response set-header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
# Referrer-Policy
http-response set-header Referrer-Policy "same-origin"
# X-Content-Type-Options
http-response set-header X-Content-Type-Options "nosniff"
# Feature-Policy
http-response set-header Feature-Policy "geolocation 'self'; midi 'self'; sync-xhr 'self'; microphone 'self'; camera 'self'; magnetometer 'self'; gyroscope 'self'; speaker 'self'; fullscreen 'self'; payment 'self'"
# X-XSS-Protection
http-response set-header X-XSS-Protection "1; mode=block"
# Expect-CT
http-response set-header Expect-CT "enforce, max-age=3600, report-uri=\"https://moens.report-uri.com/r/d/ct/enforce\""
# Add report-uri to Content-Security-Policy
acl report-uri res.hdr(Content-Security-Policy),lower -m sub report-uri
http-response replace-header Content-Security-Policy (.*) \1;\ report-uri\ https://moens.report-uri.com/r/d/csp/enforce if !report-uri
# Delete Server and X-Powered-By headers
http-response del-header Server
http-response del-header X-Powered-By
Optionally, leave out includeSubdomains and/or preload from the HSTS header and change others based on your needs
Redirect HTTP to HTTPS
http-request redirect scheme https if !{ ssl_fc }
Redirect HTTP to HTTPS except for ACME challenges
acl acme-acl path_beg /.well-known/acme-challenge
http-request redirect scheme https if !{ ssl_fc } !acme-acl
use_backend acme if acme-acl
You will still need to add an acme backend.
The first acl that passes will be used. Make sure you check the acme acl before any others.
Return a simple 200 response
frontend
...
acl example-acl hdr(host) -i example.com
use_backend 200_response if example-acl
backend 200_response
errorfile 503 /path/to/errorfiles/200.http
vim /path/to/errorfiles/200.http
HTTP/1.1 200 OK
Content-Length: 0
Cache-Control: private
Strict-Transport-Security: max-age=63072000; includeSubdomains; preload
Optionally, remove the HSTS header
Use a map file to map hostnames to backends
use_backend %[req.hdr(host),lower,map_str(/path/to/map)]
Layout of the file is simply one mapping per line: example.com backend-name
HAProxy stats page
frontend stats
bind *:8888
stats enable
stats show-legends
stats uri /haproxy
stats realm Haproxy\ Statistics
stats refresh 30s
Simple backend example
backend backend-name
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
server node1 backend-ip:port check
Backend example with resolver and ssl on the backend (without cert verification)
backend backend-name
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
server node1 backend-url:port check ssl verify none resolvers resolver-name