global log stderr local0 err maxconn 4096 # Performance tuning tune.ssl.default-dh-param 2048 ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 defaults log global mode http option httplog option dontlognormal timeout connect 5s timeout client 30s timeout server 30s timeout http-request 10s timeout http-keep-alive 5s frontend https_in bind *:443 ssl crt /etc/haproxy/certs/origin.pem alpn h2,http/1.1 # Gzip compression compression algo gzip compression type text/html text/plain application/json text/css # Access to port 443 is already restricted to Cloudflare IPs at the UFW level # Therefore, all requests reaching HAProxy come from Cloudflare; no additional ACL validation is required. # HAProxy's own forwardfor must be disabled, otherwise it will insert Cloudflare node IPs no option forwardfor # Overwrite standard headers directly with the real IP forcibly added by Cloudflare http-request set-header X-Forwarded-For %[req.hdr(CF-Connecting-IP)] http-request set-header X-Real-IP %[req.hdr(CF-Connecting-IP)] # Rate limiting: 30 requests per minute per IP (based on CF-Connecting-IP) stick-table type string len 45 size 100k expire 1m store http_req_rate(1m) http-request track-sc0 req.hdr(CF-Connecting-IP) http-request deny deny_status 429 if { sc_http_req_rate(0) gt 30 } # Block /health from external access acl is_health path /health http-request deny deny_status 403 if is_health default_backend app backend app server rustyip app:3000 check