HAProxy Request Limit 50 Requests per Second

Hello,
in an Ubuntu 24.04 Server machine, im using HAProxy (2.8.5-1ubuntu3.3) to load balance my company’s API. Load Balancing works great but i am having issues with rate limiting of incoming requests. What I want is to limit 50 requests per second per source ip, so for example if a specific source ip is sending 60rps, I want HAProxy to allow the first 50 requests and limit the rest (10) for each second separately.

I read the official documentation: Traffic policing | HAProxy config tutorials

so in haproxy.cfg I created a stick table (inside frontend https_in):

stick-table type ip size 100k expire 30s store http_req_rate(1s)

Then i added an http-request track directive to store the client’s IP address with their request count in the stick table:

http-request track-sc0 src

And then an http-request return directive of status 299 and custom message if someone makes over 50rps:

http-request return status 299 content-type “text/plain” lf-string “Rate limit exceeded.” if { sc_http_req_rate(0) gt 50 }

In order to test this setup, I am using a hey script which makes many requests per second for 10 seconds.

What I expect is HAProxy to allow 50rps and a total of 500 requests, because the test lasts for 10s.

When test is completed, the report shows that only 50 responses were allowed (status code 200) and the rest of them got rate limited with status code 299.

Same with Apache JMeter..I am sending 60rps for 10s, but only 50 were allowed, instead of 500.

Can you please help me? This is my frontend https_in block config in haproxy:

frontend https_in

bind *:443

ssl crt /etc/ssl/private/myssl.pem

mode http

maxconn 9000

option httplog

option forwardfor

log-format “%ci:%cp [%t] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Tt %ST %B %ts %ac/%fc/%bc/%sc/%rc %sq/%bq TLS:%[ssl_fc_protocol] %r”

stick-table type ip size 100k expire 30s store http_req_rate(1s)

http-request track-sc0 src

http-request return status 299 content-type “text/plain” lf-string “Rate limit exceeded.” if { sc_http_req_rate(0) gt 50 }

acl invalid_method method TRACE OPTIONS TRACK CONNECT DELETE PUT HEAD

http-request deny if invalid_method

http-request del-header X-Powered-By

http-request del-header Server

acl host_test1 hdr(host) -i test1.mycompany.com

acl host_test2 hdr(host) -i test2.mycompany.com

use_backend test1 if host_test1

use_backend test2 if host_test2

default_backend reject_backend

I was able to find the solution (big thanks to M.). I had to put the track source IP directive:

http-request track-sc0 src

after my rate limiting rule

http-request return status 299 content-type “text/plain” lf-string “Rate limit exceeded.” if { sc_http_req_rate(0) gt 50 }

That way, the request is first evaluated against the current rate before it contributes to the counter. If it passes, it is then tracked and included in the stick-table rate count.