I've been trying to implement rate limiting for some HTTP POST requests on my website. It works great, except for one detail: the expiration of my entry in my stick-table is always reset to 30 seconds, which means that if the client mistakenly makes a request 29 seconds after being blocked, it will be blocked, again, for 30 seconds.
Here's my config, stripped down to the bare minimal for ease of reading:
frontend http-in
mode http
bind *:80
### Request limiting
# Declare stick table
stick-table type string size 100k expire 30s store gpc0
# Inspect layer 7
tcp-request inspect-delay 15s
# Declare ACLs
acl source_is_abuser sc0_get_gpc0 gt 0
tcp-request content track-sc0 req.cook(frontend) if !source_is_abuser
### End Request limiting
use_backend rate-limit if source_is_abuser
default_backend mybackend
backend mybackend
mode http
stick-table type string size 100k expire 30s store http_req_rate(30s)
tcp-request content track-sc1 req.cook(frontend) if METH_POST
acl post_req_rate_abuse sc1_http_req_rate gt 30
acl mark_as_abuser sc0_inc_gpc0 gt 0
tcp-request content accept if post_req_rate_abuse mark_as_abuser
server myLocalhost 127.0.0.1:8081
backend rate-limit
mode http
errorfile 503 /usr/local/etc/haproxy/rate-limit.http
With this config, as soon as a client makes more than 1 request per second over 30 seconds, this client is marked as an abuser by mybackend
. The following request are then, as expected, blocked by the http-in
frontend.
However, every time the currently marked source_is_abuser
client sends a request, the expiration counter of http-in
's stick-table is reset to 30 seconds. I would expect the expiration counter to keep going down, since the connection is supposedly only tracked when !source_is_abuser
.
Any insight into what I am doing wrong?