HAProxy Integration
This guide explains how to plug the generated rules into HAProxy using ACL files.
Quick start
- Download
haproxy_waf.zipfrom the latest release. - Drop the ACL files into
/etc/haproxy/(or any path you prefer). - Reference them from a
frontendblock. - Reload HAProxy.
Files in the archive
| File | Purpose |
|---|---|
waf.acl | Pre-compiled regex patterns covering every OWASP CRS category |
bots.acl | Bad-bot User-Agent patterns |
Step 1 — Reference the ACL files
The cleanest approach is to load the patterns from disk with -f:
haproxy
frontend http-in
bind *:80
bind *:443 ssl crt /etc/haproxy/certs/
acl waf_match path,url_dec -m reg -i -f /etc/haproxy/waf.acl
acl waf_match_q query -m reg -i -f /etc/haproxy/waf.acl
acl bad_bot hdr(User-Agent) -m reg -i -f /etc/haproxy/bots.acl
http-request deny deny_status 403 if waf_match || waf_match_q || bad_bot
default_backend serversStep 2 — Validate and reload
bash
sudo haproxy -c -f /etc/haproxy/haproxy.cfg && sudo systemctl reload haproxyACL primer
HAProxy ACLs match against fetch samples (path, query, headers, …) using converters and matchers:
haproxy
# Match path against a regex
acl sqli_path path -m reg -i union.*select
# Match a specific query parameter
acl sqli_qid url_param(id) -m reg -i union.*select
# Match a request header
acl bad_ref hdr(Referer) -m reg -i malicious-site\.com
# Combine with boolean operators
http-request deny if sqli_path || sqli_qidA complete example
haproxy
global
log /dev/log local0
maxconn 4096
defaults
mode http
log global
option httplog
timeout connect 5s
timeout client 50s
timeout server 50s
frontend http-in
bind *:80
# WAF
acl waf_match path,url_dec -m reg -i -f /etc/haproxy/waf.acl
acl waf_match_q query -m reg -i -f /etc/haproxy/waf.acl
acl bad_bot hdr(User-Agent) -m reg -i -f /etc/haproxy/bots.acl
# Block matching requests
http-request deny deny_status 403 if waf_match || waf_match_q || bad_bot
default_backend servers
backend servers
balance roundrobin
server srv1 127.0.0.1:8080 checkCustomization
Custom error response
Return a styled error body instead of the default empty 403:
haproxy
http-request deny deny_status 403 \
content-type "text/html; charset=utf-8" \
string "<h1>Blocked by WAF</h1>" if waf_matchLogging blocked requests
haproxy
http-request set-var(txn.blocked) str(1) if waf_match
http-request capture var(txn.blocked) len 1
log-format "%ci:%cp [%t] %ft %b/%s %ST %B blocked=%[var(txn.blocked)] ua=%[capture.req.hdr(0)]"Per-path whitelist
haproxy
acl is_webhook path_beg /api/webhook
http-request deny deny_status 403 if waf_match !is_webhookCombine with rate limiting
haproxy
stick-table type ip size 100k expire 30s store http_req_rate(10s)
http-request track-sc0 src
acl too_many sc_http_req_rate(0) gt 100
http-request deny deny_status 429 if too_manyTesting
bash
curl -I "http://example.com/?id=1' UNION SELECT * FROM users--"
curl -A "AhrefsBot" -I "http://example.com/"
echo "show stat" | sudo socat stdio /var/run/haproxy.sockTroubleshooting
- ACL never matches — run
haproxy -c -f haproxy.cfgto validate the syntax. Use-dfor debug output to watch ACL evaluation in real time. - Performance impact — complex regex over
path,url_decadds per-request cost. Benchmark with realistic traffic before enabling globally. - Configuration too large — the converter keeps
waf.aclflat and grep-friendly. Split it across multiple files if you need to apply different rule subsets to different frontends.