Files
rustyip/haproxy/haproxy.cfg
T

68 lines
3.0 KiB
INI

global
log stderr local0 err
maxconn 4096
# TLS configuration optimized for Cloudflare-to-origin connections
ssl-default-bind-curves X25519:prime256v1:secp384r1
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
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 ssl-min-ver TLSv1.2 no-tls-tickets
defaults
log global
mode http
option httplog
option dontlog-normal
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 crt /etc/haproxy/certs-ipv4/origin.pem alpn h2,http/1.1 strict-sni
# Gzip compression
compression algo gzip
compression type text/html text/plain application/json text/css application/javascript
# 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.
# Identify IPv4-only domain traffic by Host header
acl is_ipv4_domain hdr(host) -f /usr/local/etc/haproxy/ipv4_domain.lst
# HAProxy's own forwardfor is not enabled; real IP is set explicitly below via CF-Connecting-IP
# Main domain: standard headers
http-request set-header X-Forwarded-For %[req.hdr(CF-Connecting-IP)] if !is_ipv4_domain
http-request set-header X-Real-IP %[req.hdr(CF-Connecting-IP)] if !is_ipv4_domain
# IPv4-only domain: separate header to prevent confusion with the main domain
http-request set-header X-IPv4-Client-IP %[req.hdr(CF-Connecting-IP)] if is_ipv4_domain
http-request del-header X-Forwarded-For if is_ipv4_domain
http-request del-header X-Real-IP if is_ipv4_domain
# Rate limiting: tiered based on global request rate
# Track per-IP request rate (per minute)
stick-table type string len 45 size 100k expire 1m store http_req_rate(1m)
http-request track-sc0 req.hdr(CF-Connecting-IP)
# Track global request rate (per second)
http-request track-sc1 str(global) table global_rate_tracking
# Tier 3: global >= 200 RPS -> limit each IP to 5 RPM
http-request deny deny_status 429 if { sc_http_req_rate(1,global_rate_tracking) ge 200 } { sc_http_req_rate(0) gt 5 }
# Tier 2: global >= 100 RPS -> limit each IP to 10 RPM
http-request deny deny_status 429 if { sc_http_req_rate(1,global_rate_tracking) ge 100 } { sc_http_req_rate(0) gt 10 }
# Tier 1: default -> limit each IP to 30 RPM
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 global_rate_tracking
stick-table type string len 6 size 1 expire 10m store http_req_rate(1s)
backend app
server rustyip app:3000 check