diff --git a/haproxy/haproxy.cfg b/haproxy/haproxy.cfg new file mode 100644 index 0000000..3d18042 --- /dev/null +++ b/haproxy/haproxy.cfg @@ -0,0 +1,49 @@ +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