How to Implement Rate Limiting and IP Blocking for /login Requests in HAProxy to Prevent Brute Force Attacks


29 views

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.