When facing a distributed brute force attack where thousands of IPs are hammering your /login endpoint, simply returning HTTP 500 errors isn't enough. We need to implement proper request rate limiting at the HAProxy level before these requests even reach your application servers.
frontend http-in
bind *:80
bind *:443 ssl crt /etc/ssl/certs/example.com.pem
# Define ACL for login path
acl is_login path_beg /login
# Track login attempts per IP
stick-table type ip size 1m expire 10m store http_req_rate(10s)
# Track excessive login attempts (more than 5 in 10 seconds)
tcp-request content track-sc0 src if is_login
acl login_abuse sc0_http_req_rate(login) gt 5
# Reject abusive IPs with 403
http-request deny if login_abuse
# Default backend
default_backend servers
For persistent attackers, we can automatically blacklist IPs that continue abusive patterns:
frontend http-in
# ... previous configuration ...
# Blacklist table (persists across reloads)
stick-table type ip size 1m expire 24h store gpc0
# Increment blacklist counter
tcp-request content track-sc1 src if login_abuse
acl is_blacklisted sc1_get_gpc0 gt 0
# Deny blacklisted IPs
http-request deny if is_blacklisted
# Blacklist after 3 violations
http-request track-sc1 src if login_abuse
http-request sc-inc-gpc0(1) if { sc1_http_req_rate(login) gt 5 }
To verify the effectiveness of these measures, add detailed logging:
frontend http-in
# ... previous configuration ...
# Custom logging
capture request header User-Agent len 150
capture request header X-Forwarded-For len 15
log-format "%ci:%cp [%tr] %ft %b/%s %Tq/%Tw/%Tc/%Tr/%Tt %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r"
# Special log for blocked requests
http-request set-var(req.blocked) bool(true) if { sc1_get_gpc0 gt 0 }
http-request capture var(req.blocked) len 1
Here's a comprehensive configuration combining all elements:
global
log /dev/log local0
log /dev/log local1 notice
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
frontend http-in
bind *:80
bind *:443 ssl crt /etc/ssl/certs/example.com.pem
# Track login attempts
stick-table type ip size 1m expire 10m store http_req_rate(10s)
acl is_login path_beg /login
tcp-request content track-sc0 src if is_login
acl login_abuse sc0_http_req_rate(login) gt 5
# Blacklist system
stick-table type ip size 1m expire 24h store gpc0
tcp-request content track-sc1 src if login_abuse
acl is_blacklisted sc1_get_gpc0 gt 0
http-request deny if is_blacklisted
http-request track-sc1 src if login_abuse
http-request sc-inc-gpc0(1) if { sc1_http_req_rate(login) gt 5 }
# Block excessive requests
http-request deny if login_abuse
# Logging
capture request header User-Agent len 150
log-format "%ci:%cp [%tr] %ft %b/%s %Tq/%Tw/%Tc/%Tr/%Tt %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r"
default_backend servers
backend servers
balance roundrobin
server server1 10.0.0.1:80 check
server server2 10.0.0.2:80 check
After implementing these changes, test your configuration:
# Check current stick-table contents
echo "show table http-in" | sudo socat stdio /run/haproxy/admin.sock
# Simulate attack for testing (from another server)
ab -n 100 -c 10 https://yoursite.com/login
Monitor your HAProxy logs to verify blocking is working as expected. The logs will show 403 responses for blocked requests.
From your log samples, I can see multiple IPs (46.161.62.79, 46.161.63.132, etc.) repeatedly hitting the /login endpoint with GET requests. This is a classic brute force attack pattern where attackers try credential stuffing at scale.
Here's a complete solution using HAProxy's stick tables for tracking request rates:
frontend http-in
bind *:80
acl is_login path_beg /login
acl is_abuser src_get_gpc0(http-in) gt 5
tcp-request connection track-sc0 src table http-in
tcp-request connection reject if is_abuser
use_backend login_backend if is_login
default_backend main_backend
backend login_backend
stick-table type ip size 100k expire 1h store gpc0,http_req_rate(10s)
http-request track-sc0 src table login_backend
http-request sc-inc-gpc0(0) if { path_beg /login }
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 10 } || { sc_get_gpc0(0) gt 5 }
server server1 127.0.0.1:8080
1. stick-table: Creates an in-memory table tracking IPs with:
- 100k entries capacity
- 1 hour expiration
- Tracks request counts (gpc0) and rate (http_req_rate)
2. Rate Limiting Rules:
- Denies requests exceeding 10/login attempts in 10 seconds
- Blocks after 5 total attempts (persistent for 1 hour)
- Returns HTTP 429 (Too Many Requests) status
Add this to your HAProxy configuration to log blocked attempts:
frontend http-in
log-format "%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r"
capture request header User-Agent len 128
option httplog
option logasap
Check your stick table contents in real-time:
echo "show table login_backend" | sudo socat stdio /var/run/haproxy.sock
Typical output shows offender tracking:
# table: login_backend, type: ip, size:1048576, used:42
0x564e8e6e3c60: key=46.161.62.79 use=0 exp=3545 gpc0=6 http_req_rate(10000)=12
0x564e8e6e3d20: key=46.161.63.132 use=0 exp=3587 gpc0=8 http_req_rate(10000)=15
For persistent attacks from specific regions:
acl from_attack_country src -f /etc/haproxy/blocked_countries.lst
http-request deny if from_attack_country
Where blocked_countries.lst contains CIDR ranges for high-risk countries.