
Let's say you are load balancing two different domains, foo.com and bar.com.
frontend main
bind *:80
bind *:443 ssl crt /etc/pki/tls/my.pem
balance roundrobin
option prefer-last-server
acl foo hdr(host) -i www.foo.com
use_backend foo if foo
acl bar hdr(host) -i www.bar.com
use_backend bar if bar
default_backend foo
backend foo
cookie sticky insert indirect nocache
server server1 10.0.0.1:11111 check cookie server1
server server2 10.0.0.2:11111 check cookie server2
backend bar
cookie sticky insert indirect nocache
server server1 10.0.0.1:22222 check cookie server1
server server2 10.0.0.2:22222 check cookie server2
And you want to try and prevent HTTP Flood and Denial of Service (DoS) attacks. stick-table can be used to store and increment counters associated with clients as they make requests to your frontends which can then be used to deny requests from clients, such as bots and web scrapers, as a way to try to prevent and mitigate Denial of Service (DoS) attacks.
Be aware that stick-table was released in 2010 with version 1.4 of HAProxy thus you'll want to ensure your HAProxy is version 1.4 or higher. The haproxy -v command can be used to display the version of HAProxy you are running.
~]# haproxy -v
HA-Proxy version 2.3.10-4764f0e 2021/04/23 - https://haproxy.org/
Status: stable branch - will stop receiving fixes around Q1 2022.
Known bugs: http://www.haproxy.org/bugs/bugs-2.3.10.html
Running on: Linux 5.11.12-300.fc34.x86_64 #1 SMP Wed Apr 7 16:31:13 UTC 2021 x86_64
Let's first create backends.
- The per_ip_and_url_rates backend will track the number of times from a client in the last 24 hours
- The per_ip_rates backend used the gpc0 (general purpose counter) which will increment when a client visits a page for the first time
- stick-table stores and increment counters associated with clients as they make requests to your frontends
- type ip means the backend will keep track of clients by their IP addresses
backend per_ip_and_url_rates
stick-table type binary len 8 size 1m expire 24h store http_req_rate(24h)
backend per_ip_rates
stick-table type ip size 1m expire 24h store gpc0,gpc0_rate(30s)
At this point if you restart HAProxy your haproxy.log should contain something like this. So far, so good.
Jul 9 04:25:45 localhost haproxy[26810]: Proxy per_ip_rates started.
Next let's add the following to the frontend.
- http-request track-sc0 enables the per_ip_rates backend in the frontend
- http-request track-sc1 enables the per_ip_and_url_rates backend in the frontend in this example ignores requests for .css .js .png .jpg .jpeg and .gif files
- acl exceeds_limit is used in conjunction with http-request deny to deny requests for 15 seconds if the gpc0 (general purpose counter) is reached
frontend fe_mywebsite
bind *:80
bind *:443 ssl crt /etc/pki/tls/my.pem
balance roundrobin
option prefer-last-server
# per_ip_rates backend to track requests from source IP address
http-request track-sc0 src table per_ip_rates
# track the number of times that a client has requested the current webpage during the last 24 hours
http-request track-sc1 url32+src table per_ip_and_url_rates unless { path_end .css .js .png .jpg .jpeg .gif }
# sets exceeds_limit to 15 seconds
acl exceeds_limit sc_gpc0_rate(0) gt 15
# This MUST come after the prior http-request track-sc0 and http-request track-sc1 and acl exceeds_limit
# increment the per_ip_rates general purpose counter (gpc) if a client is visit a URL for the first time
http-request sc-inc-gpc0(0) if { sc_http_req_rate(1) eq 1 }
http-request deny deny_status 429 if exceeds_limit
acl foo hdr(host) -i www.foo.com
use_backend foo if foo
acl bar hdr(host) -i www.bar.com
use_backend bar if bar
default_backend foo
Before restarting HAProxy, let's use the haproxy command with the -c (check mode) flag to validate the haproxy.cfg file does not have any syntax errors.
~]$ sudo haproxy -c -f /etc/haproxy/haproxy.cfg
Configuration file is valid
And then restart HAProxy for this change to take effect.
sudo systemctl restart haproxy
For proof of concept, I created a bash shell script with a collection of URLs that would be requested simultaneously.
curl --silent --output /dev/null --url https://www.freekb.net/Article?id=1
curl --silent --output /dev/null --url https://www.freekb.net/Article?id=2
curl --silent --output /dev/null --url https://www.freekb.net/Article?id=3
curl --silent --output /dev/null --url https://www.freekb.net/Article?id=4
curl --silent --output /dev/null --url https://www.freekb.net/Article?id=5
curl --silent --output /dev/null --url https://www.freekb.net/Article?id=6
curl --silent --output /dev/null --url https://www.freekb.net/Article?id=7
curl --silent --output /dev/null --url https://www.freekb.net/Article?id=8
curl --silent --output /dev/null --url https://www.freekb.net/Article?id=9
curl --silent --output /dev/null --url https://www.freekb.net/Article?id=10
curl --silent --output /dev/null --url https://www.freekb.net/Article?id=11
curl --silent --output /dev/null --url https://www.freekb.net/Article?id=12
curl --silent --output /dev/null --url https://www.freekb.net/Article?id=13
curl --silent --output /dev/null --url https://www.freekb.net/Article?id=14
curl --silent --output /dev/null --url https://www.freekb.net/Article?id=15
curl --silent --output /dev/null --url https://www.freekb.net/Article?id=16
curl --silent --output /dev/null --url https://www.freekb.net/Article?id=17
curl --silent --output /dev/null --url https://www.freekb.net/Article?id=18
curl --silent --output /dev/null --url https://www.freekb.net/Article?id=19
curl --silent --output /dev/null --url https://www.freekb.net/Article?id=20
If you have not configured HAProxy to log events to a custom log file, check out my article FreeKB - HAProxy (Load Balance) - Logging to a custom log file. You will want your log-format directive to include %ST to log the HTTP response code such as 200 OK or
log-format %[date,utime(%Y%m%d%H%M%S)]\ %ci:%cp\ %b\ %hr\ %ST\ %hs\ %r\ %ST
When I ran the bash shell script, I saw the following in my haproxy.log, where the first 15 requests returned 200 OK and the subsequent requests returned 403 Forbidden. Nice, it works!
Jul 23 11:12:31 localhost haproxy[181346]: 1721733151 135.111.222.333:50306 www GET https://www.freekb.net/Article?id=1 HTTP/2.0 200
Jul 23 11:12:31 localhost haproxy[181346]: 1721733151 135.111.222.333:50308 www GET https://www.freekb.net/Article?id=2 HTTP/2.0 200
Jul 23 11:12:32 localhost haproxy[181346]: 1721733152 135.111.222.333:50310 www GET https://www.freekb.net/Article?id=3 HTTP/2.0 200
Jul 23 11:12:32 localhost haproxy[181346]: 1721733152 135.111.222.333:50312 www GET https://www.freekb.net/Article?id=4 HTTP/2.0 200
Jul 23 11:12:32 localhost haproxy[181346]: 1721733152 135.111.222.333:50314 www GET https://www.freekb.net/Article?id=5 HTTP/2.0 200
Jul 23 11:12:32 localhost haproxy[181346]: 1721733152 135.111.222.333:50316 www GET https://www.freekb.net/Article?id=6 HTTP/2.0 200
Jul 23 11:12:32 localhost haproxy[181346]: 1721733152 135.111.222.333:50318 www GET https://www.freekb.net/Article?id=7 HTTP/2.0 200
Jul 23 11:12:32 localhost haproxy[181346]: 1721733152 135.111.222.333:50320 www GET https://www.freekb.net/Article?id=8 HTTP/2.0 200
Jul 23 11:12:32 localhost haproxy[181346]: 1721733152 135.111.222.333:50322 www GET https://www.freekb.net/Article?id=9 HTTP/2.0 200
Jul 23 11:12:33 localhost haproxy[181346]: 1721733153 135.111.222.333:50324 www GET https://www.freekb.net/Article?id=10 HTTP/2.0 200
Jul 23 11:12:33 localhost haproxy[181346]: 1721733153 135.111.222.333:50326 www GET https://www.freekb.net/Article?id=11 HTTP/2.0 200
Jul 23 11:12:33 localhost haproxy[181346]: 1721733153 135.111.222.333:50328 www GET https://www.freekb.net/Article?id=12 HTTP/2.0 200
Jul 23 11:12:33 localhost haproxy[181346]: 1721733153 135.111.222.333:50330 www GET https://www.freekb.net/Article?id=13 HTTP/2.0 200
Jul 23 11:12:33 localhost haproxy[181346]: 1721733153 135.111.222.333:50332 www GET https://www.freekb.net/Article?id=14 HTTP/2.0 200
Jul 23 11:12:33 localhost haproxy[181346]: 1721733153 135.111.222.333:50334 www GET https://www.freekb.net/Article?id=15 HTTP/2.0 200
Jul 23 11:12:34 localhost haproxy[181346]: 1721733154 135.111.222.333:50338 my_frontend GET https://www.freekb.net/Article?id=16 HTTP/2.0 429
Jul 23 11:12:34 localhost haproxy[181346]: 1721733154 135.111.222.333:50340 my_frontend GET https://www.freekb.net/Article?id=17 HTTP/2.0 429
Jul 23 11:12:34 localhost haproxy[181346]: 1721733154 135.111.222.333:50342 my_frontend GET https://www.freekb.net/Article?id=18 HTTP/2.0 429
Jul 23 11:12:34 localhost haproxy[181346]: 1721733154 135.111.222.333:50344 my_frontend GET https://www.freekb.net/Article?id=19 HTTP/2.0 429
Jul 23 11:12:34 localhost haproxy[181346]: 1721733154 135.111.222.333:50346 my_frontend GET https://www.freekb.net/Article?id=120 HTTP/2.0 429
Did you find this article helpful?
If so, consider buying me a coffee over at