Core concepts
Stick tables
You can define in-memory buffers that store data about traffic as it passes through the load balancer. These buffers, called stick tables, can be in the form of counters, which count the occurrences of specific events, or tags, which you can set to integers for any purpose. Then, you can write ACL expressions that trigger actions based on these data types, such as denying a user’s request when that user’s behavior seems abnormal.
Stick table read and write operations are implemented for efficiency and have no significant impact on performance.
Here are some stick table usage examples:
- Count the number of requests a client makes.
- Count errors a client has triggered.
- Count the number of times a webpage has been accessed.
- Tag a client for submitting too many requests.
- Tag a backend server for exhibiting response delays.
The examples on this page show stick tables being built using data taken from HTTP requests, but you can build stick tables from other traffic as well. You can build stick tables in these directives: tcp-request connection
, tcp-request session
, tcp-request content
, http-request
, and http-response
.
Create a stick table Jump to heading
To create a stick table, you can either:
- Add a
stick-table
directive to a frontend or backend section. - Add a
table
directive to a peers section. Defining tables in a peers section and sharing them between load balancer nodes allows you to safeguard the table data if any peer goes down.
The load balancer allocates a new storage area for each stick-table
directive that you add to your configuration. This action is similar to a table in a relational database where each table holds its own set of records. Each record is identified by its key and associated with whatever data items you define.
In a frontend or backend section Jump to heading
To create a stick table in a frontend or backend section:
-
Add a
stick-table
directive to a frontend or backend. In the following example, we define a stick table that tracks the HTTP request rate of each client that passes through the load balancer:haproxyfrontend wwwbind :80stick-table type ip size 1m expire 10s store http_req_rate(10s)haproxyfrontend wwwbind :80stick-table type ip size 1m expire 10s store http_req_rate(10s)In this example:
- The table’s primary key is of type
ip
, which means that the keys will be IPv4 addresses. - The table holds a maximum of
1m
records, which is 1,048,576 records. - A record expires after
10
seconds unless it is accessed during that time. - We store (associate) the
http_req_rate(10s)
counter with each IP address, which calculates the HTTP request rate over the last 10 seconds.
You can track multiple counters by joining them with commas. Example:
haproxyfrontend wwwbind :80stick-table type ip size 1m expire 10s store http_req_rate(10s),conn_rate(10s),bytes_in_rate(10s)haproxyfrontend wwwbind :80stick-table type ip size 1m expire 10s store http_req_rate(10s),conn_rate(10s),bytes_in_rate(10s) - The table’s primary key is of type
-
Add an
http-request track-sc0
directive, which adds a record to the table for the current request. As an argument, specify a fetch method that matches the keytype
specified in thestick-table
directive. For this example, we use thesrc
fetch method because it matches stick table typeip
.haproxyfrontend wwwbind :80stick-table type ip size 1m expire 10s store http_req_rate(10s)http-request track-sc0 srchaproxyfrontend wwwbind :80stick-table type ip size 1m expire 10s store http_req_rate(10s)http-request track-sc0 src -
Add the
http-request deny
directive to deny any client whose request rate goes above 10:haproxyfrontend wwwbind :80stick-table type ip size 1m expire 10s store http_req_rate(10s)http-request track-sc0 srchttp-request deny if { sc_http_req_rate(0) gt 10 }haproxyfrontend wwwbind :80stick-table type ip size 1m expire 10s store http_req_rate(10s)http-request track-sc0 srchttp-request deny if { sc_http_req_rate(0) gt 10 }When the IP address goes into the table, the associated HTTP request rate counter begins counting that client’s rate of requests. Each time that the same client makes a request, this record and its associated counters update.
The
http-request deny
directive rejects clients with an HTTP request rate greater than 10 within the time period tracked by the counter: 10 seconds.
In a peers section Jump to heading
This section applies to:
- HAProxy 2.0 and newer
- HAProxy Enterprise 2.0r1 and newer
- HAProxy ALOHA 11.5 and newer
You can define a stick table in a peers
section using the table
directive. When you operate multiple load balancers in an active-active or active-standby setup, you’ll use a peers
section to synchronize stick table data between them. Note that active-active clustering requires an HAProxy Enterprise module.
In the following example, we’ve added a stick table definition via the table
directive to the peers
section and updated it to have the name sticktable1
. We then reference it on the http-request track-sc0
and http-request deny
lines in the frontend by prefixing it with the peers
section name:
haproxy
peers myclusterpeer loadbalancer1 192.168.1.10:10000peer loadbalancer2 192.168.1.11:10000table sticktable1 type ip size 1m expire 10s store http_req_rate(10s)frontend wwwbind :80http-request track-sc0 src table mycluster/sticktable1http-request deny if { sc_http_req_rate(0,mycluster/sticktable1) gt 10 }
haproxy
peers myclusterpeer loadbalancer1 192.168.1.10:10000peer loadbalancer2 192.168.1.11:10000table sticktable1 type ip size 1m expire 10s store http_req_rate(10s)frontend wwwbind :80http-request track-sc0 src table mycluster/sticktable1http-request deny if { sc_http_req_rate(0,mycluster/sticktable1) gt 10 }
Stick table arguments Jump to heading
In this section, we will describe a stick table’s arguments.
type Jump to heading
Choose any of the following data types as the primary key in the stick table. Set this as the type
argument on the stick table definition.
ip
(IPv4 address only)ipv6
(IPv6 address only)integer
(32 bits)string [len <length>]
(default length: 32 characters)binary [len <length>]
(default length: 32 bytes)
For example, you could track the HTTP request rate on a per-backend basis rather than on a per-client basis by setting the table’s type
to string
. Then, include a http-request track-sc0
directive that captures the name of the backend using the be_name
fetch method:
haproxy
frontend wwwbind :80stick-table type string size 1m expire 10s store http_req_rate(10s)use_backend apiservers if { path_beg /api/ }default_backend webserversbackend webserversserver s1 192.168.50.20:80http-request track-sc0 be_name table wwwbackend apiserversserver s1 192.168.50.21:80http-request track-sc0 be_name table www
haproxy
frontend wwwbind :80stick-table type string size 1m expire 10s store http_req_rate(10s)use_backend apiservers if { path_beg /api/ }default_backend webserversbackend webserversserver s1 192.168.50.20:80http-request track-sc0 be_name table wwwbackend apiserversserver s1 192.168.50.21:80http-request track-sc0 be_name table www
size Jump to heading
Set the stick table’s size
argument to one of the following values:
Size | Number of records the table can store |
---|---|
1 |
1 |
1k |
1 x 210 = 1,024 |
1m |
1 x 220 = 1,048,576 |
1g |
1 x 230 = 1,073,741,824 |
Sticky counters Jump to heading
A sticky counter is a variable that temporarily holds the fetch sample to use as the primary key for the record in the stick table. The record contains one or more values associated with the key. These values may be data types, general purpose counters, or general purpose tags. While the sticky counter contains the key for the record, the other counters contain the data fields in the record.
For example, if you want to store a client’s HTTP request rate, you first define a stick table where the key is the client IP and the record consists of the client request rate.
haproxy
frontend wwwbind :80stick-table type ip size 1m expire 10s store http_req_rate(10s)
haproxy
frontend wwwbind :80stick-table type ip size 1m expire 10s store http_req_rate(10s)
Then, use http-request track-sc0
to create or update the record for the client IP returned by the fetch src
.
haproxy
frontend wwwbind :80stick-table type ip size 1m expire 10s store http_req_rate(10s)http-request track-sc0 src
haproxy
frontend wwwbind :80stick-table type ip size 1m expire 10s store http_req_rate(10s)http-request track-sc0 src
There are three variants of the track-sc0
argument, depending on which sticky counter you want to use:
http-request track-sc0
http-request track-sc1
http-request track-sc2
In the example, the sticky counter sc0
holds the IP address as you create or update the stick table record. If you want to track additional fetch samples, you can use the sc1
and sc2
sticky counters the same way. Below, we track a record in the table by the client’s source IP address, the name of the backend, and the Host
header:
haproxy
peers myclusterpeer loadbalancer1 192.168.1.10:10000peer loadbalancer2 192.168.1.11:10000table sticktable1 type ip size 1m expire 10s store http_req_rate(10s)table sticktable2 type string size 1m expire 10s store http_req_rate(10s)table sticktable3 type string size 1m expire 10s store http_req_rate(10s)frontend wwwbind :80# key is source IP addresshttp-request track-sc0 src table mycluster/sticktable1# key is backend namehttp-request track-sc1 be_name table mycluster/sticktable2# key is Host headerhttp-request track-sc2 req.hdr(Host) mycluster/table sticktable3
haproxy
peers myclusterpeer loadbalancer1 192.168.1.10:10000peer loadbalancer2 192.168.1.11:10000table sticktable1 type ip size 1m expire 10s store http_req_rate(10s)table sticktable2 type string size 1m expire 10s store http_req_rate(10s)table sticktable3 type string size 1m expire 10s store http_req_rate(10s)frontend wwwbind :80# key is source IP addresshttp-request track-sc0 src table mycluster/sticktable1# key is backend namehttp-request track-sc1 be_name table mycluster/sticktable2# key is Host headerhttp-request track-sc2 req.hdr(Host) mycluster/table sticktable3
All fetch methods that retrieve a record from a stick table use the ID of the sticky counter that holds the key. For instance, the sc_http_req_rate
fetch takes the sticky counter number as its first argument:
haproxy
http-request deny if { sc_http_req_rate(0,mycluster/sticktable1) gt 10 }
haproxy
http-request deny if { sc_http_req_rate(0,mycluster/sticktable1) gt 10 }
Data types Jump to heading
There are many predefined data types that you can store in a table record. Data types are fetches that capture information about traffic and use it to count events or to calculate the rate at which events occur. For example, conn_cnt
counts the number of connections received from matching clients, and conn_rate
reports the average connection rate over a specified period of time.
Specify one or more data types as arguments to the store
argument of the table
or stick-table
directive. If you specify multiple data types, separate them with commas. For example, the following table uses sticky counter sc1
to store values useful for detecting and denying suspicious clients:
haproxy
frontend wwwbind :80stick-table type ip size 1m expire 10s store http_err_rate(10s),bytes_out_rate(10s),glitch_rate(10s)http-request track-sc1 srchttp-request deny if { sc_http_err_rate(1) gt 5 }http-request deny if { sc_bytes_out_rate(1) gt 1m }http-request deny if { sc_glitch_rate(1) gt 5 }
haproxy
frontend wwwbind :80stick-table type ip size 1m expire 10s store http_err_rate(10s),bytes_out_rate(10s),glitch_rate(10s)http-request track-sc1 srchttp-request deny if { sc_http_err_rate(1) gt 5 }http-request deny if { sc_bytes_out_rate(1) gt 1m }http-request deny if { sc_glitch_rate(1) gt 5 }
This example uses data types as follows:
Data type | Description | Denial criteria |
---|---|---|
http_err_rate |
The rate at which clients are inducing HTTP request errors: invalid, truncated, denied, tarpitted, or failed authentication. | Deny request if client is averaging more than 5 HTTP errors per 10-second window. |
bytes_out_rate |
The rate of outgoing data per client. | Deny request if client is averaging more than 1 million bytes of outgoing data per 10-second window. |
glitch_rate |
The rate of frontend protocol glitches per client. | Deny request if client is averaging more than 5 frontend glitches per 10-second window. |
For more information on stick table data types, see stick-table reference.
General purpose counters Jump to heading
This section applies to:
- HAProxy Enterprise 2.5r1 and newer
In addition to data types, you can allocate an array of General Purpose Counters (GPCs). GPCs aren’t maintained by the load balancer like data types; instead, they are variables you can increment with any arbitrary values you calculate, provided they are 32-bit positive integers. You can use GPCs in conditional actions or ACLs, just like data types.
For each GPC you allocate, you can also assign an automatically maintained rate variable. For example, you can use gpc(3)
to count requests for a specific resource and gpc_rate(3)
to test how frequently those requests occur.
You define the GPC array in the table
or stick-table
directive’s store
argument. Specify gpc(<n>)
to store a GPC array having <n>
elements, where <n>
is an integer from 1
to 100
. To define an array of GPC rates, specify gpc_rate(<n>,<period>)
, where <n>
is the array size and <period>
is the span of time over which the rate is calculated.
There are two actions available for incrementing GPCs:
- Increment a GPC by
1
using thesc-inc-gpc
action. - HAProxy ALOHA 15.5 / HAProxy Enterprise 2.8r1 and up: increase a GPC by any value using the
sc-add-gpc
action. The value can be an integer or an expression.
You can use these actions in the directives tcp-request connection
, tcp-request session
, tcp-request content
, http-request
, and http-response
.
There are two sample fetches available for reading GPCs:
- Read the GPC counter using
sc_get_gpc
. - Read the GPC rate using
sc_gpc_rate
.
There are two converters that take the fetch sample as a string and convert to the corresponding integer. For the GPC counter, use table_gpc
. For the GPC rate counter, use table_gpc_rate
.
Info
While the gpc
and gpc_rate
data types take an argument between 1
and 100
, you must use a zero-based index in the fetches, actions, and converters. For example, if you specify gpc(5)
as the data type in the store
argument, the fetches available to you are sc_gpc(0)
through sc_gpc(4)
.
For example, tarpit a client if they attempt to access a resource in the /restricted/
directory more than 5 times in a 10-second period:
haproxy
frontend wwwbind :80stick-table type ip size 1m expire 10s store gpc_rate(1,10s)http-request track-sc2 srchttp-request sc-inc-gpc(0,2) if { path_beg /restricted/ }http-request tarpit if { sc_gpc_rate(0,2) gt 5 }
haproxy
frontend wwwbind :80stick-table type ip size 1m expire 10s store gpc_rate(1,10s)http-request track-sc2 srchttp-request sc-inc-gpc(0,2) if { path_beg /restricted/ }http-request tarpit if { sc_gpc_rate(0,2) gt 5 }
In this example:
- The
stick-table
directive defines a GPC rate array of length 1. - The first
http-request
directive uses sticky countersc2
to store a GPC record in the table. - The second
http-request
directive uses sticky countersc2
to increment the GPC if the client attempts to access a path starting with/restricted/
. - The third
http-request
directive fetches the GPC rate record from the table associated with sticky countersc2
and performs atarpit
action if the client has attempted more than 5/restricted/
accesses in the past 10 seconds.
Info
If you use the array-based form, you cannot use the legacy form (gpc0
, gpc1
, gpc2
) for the same table.
Legacy general purpose counters Jump to heading
The legacy form of general purpose counters provides two counters, gpc0
and gpc1
, instead of a user-definable array of counters.
The following stick table registers both of the legacy counters. It uses http-request sc-inc-gpc0(0)
to increment gpc0
and http-request sc-inc-gpc1(0)
to increment gpc1
:
haproxy
frontend wwwbind :80stick-table type ip size 1m expire 10s store gpc0,gpc1http-request track-sc0 srchttp-request sc-inc-gpc0(0) if { req.hdr(Host) example.com }http-request sc-inc-gpc1(0) if { url_param(example) test }
haproxy
frontend wwwbind :80stick-table type ip size 1m expire 10s store gpc0,gpc1http-request track-sc0 srchttp-request sc-inc-gpc0(0) if { req.hdr(Host) example.com }http-request sc-inc-gpc1(0) if { url_param(example) test }
Here, gpc0
increments whenever a request’s Host
header equals example.com
. The gpc1
counter increments whenever the URL parameter example
has the value test
.
Use the gpc0_rate(<period>)
and gpc1_rate(<period>)
counters to track the rate at which the gpc0
and gpc1
counters increment during the given time period:
haproxy
frontend wwwbind :80stick-table type ip size 1m expire 10s store gpc0,gpc1,gpc0_rate(10s),gpc1_rate(10s)
haproxy
frontend wwwbind :80stick-table type ip size 1m expire 10s store gpc0,gpc1,gpc0_rate(10s),gpc1_rate(10s)
General purpose tags Jump to heading
This section applies to:
- HAProxy Enterprise 2.5r1 and newer
In addition to counters, you can allocate an array of General Purpose Tags (GPTs). GPTs are variables that you can set to any arbitrary values you calculate, provided they are 32-bit positive integers. You can use GPTs in conditional actions or ACLs, just like counters.
The difference between a counter and a tag is that a counter is designed exclusively to be increased, whereas a tag can be set to any value.
You define the GPT array in the table
or stick-table
directive’s store
argument. Specify gpt(<n>)
to store a GPT array having <n>
elements, where <n>
is an integer from 1
to 100
.
To set a GPT, use the sc-set-gpt
action. You can use this action in directives like http-request
, tcp-connection
, and so on.
The sample fetch for reading GPTs is sc_get_gpt
.
To convert a GPT fetch sample, provided as a string, to the integer value, use the table_gpt
converter.
Info
While the gpt
data type takes an argument between 1
and 100
, you must use a zero-based index in sc-set-gpt
, sc_get_gpt
, and table_gpt
. For example, if you specify gpt(5)
as the data type in the store
argument, the fetches available to you are sc_get_gpt(0)
through sc_get_gpt(4)
.
For example, tag a client based on the values of the Host
header and URL parameter example
:
haproxy
frontend wwwbind :80stick-table type ip size 1m expire 10s store gpt(2)http-request track-sc0 srchttp-request sc-set-gpt(0,0) if { req.hdr(Host) example.com }http-request sc-set-gpt(1,0) if { url_param(example) test }
haproxy
frontend wwwbind :80stick-table type ip size 1m expire 10s store gpt(2)http-request track-sc0 srchttp-request sc-set-gpt(0,0) if { req.hdr(Host) example.com }http-request sc-set-gpt(1,0) if { url_param(example) test }
Here, gpt(0)
is set if the client ever sends a request where the Host
header equals example.com
. The gpt(1)
counter is set if the client ever sends a request where the URL parameter example
has the value test
.
Info
If you use the array-based form, you cannot use the legacy form (gpt0
, gpt1
, gpt2
) for the same table.
Legacy general purpose tags Jump to heading
The legacy form of GPTs supports only two GPTs: gpt0
and gpt1
. There is no array notation.
The following stick table registers legacy tag gpt0
. It uses http-request sc-set-gpt0
to set the tag:
haproxy
frontend wwwbind :80stick-table type ip size 1m expire 10s store gpt0,gpt1http-request track-sc0 srchttp-request sc-set-gpt0(0) 1 if { req.hdr(Host) example.com }http-request sc-set-gpt0(0) 2 if { url_param(example) test }
haproxy
frontend wwwbind :80stick-table type ip size 1m expire 10s store gpt0,gpt1http-request track-sc0 srchttp-request sc-set-gpt0(0) 1 if { req.hdr(Host) example.com }http-request sc-set-gpt0(0) 2 if { url_param(example) test }
Here, we set the gpt0
tag to 1
whenever a request’s Host
header equals example.com
. We set the tag to 2
whenever the URL parameter example
has the value test
.
Synchronize stick tables across peers Jump to heading
A peers
section enables the replication of stick table data between two or more load balancers. This feature implements one-way replication of data. This makes it ideal for an active-standby cluster where the active node pushes data to the standby node. When the data replicates from the active node to the standby node, it overwrites existing data on the standby node. For active-active clustering, an HAProxy Enterprise module exists.
Info
While the stick table operations themselves are designed for efficiency, configurations using the peers protocol may exhibit a performance impact when stick tables have been configured with a large number of counters and tags. This impact occurs because all data is pushed each time any of the peers is updated.
Enable synchronization Jump to heading
To enable synchronization:
-
Add one or more
peer
lines to apeers
section. Each one identifies a load balancer that takes part in the synchronization. One of thepeer
lines must be the local host:haproxypeers mycluster# local host, active nodepeer loadbalancer1 192.168.1.10:10000# standby nodepeer loadbalancer2 192.168.1.11:10000haproxypeers mycluster# local host, active nodepeer loadbalancer1 192.168.1.10:10000# standby nodepeer loadbalancer2 192.168.1.11:10000Ensure the host name specified in the
peer
directive for the local host matches the name of the host as determined by one of the following methods, in order of precedence:- The
-L
argument specified in the command line used to start the load balancer process. - The
localpeer
name specified in theglobal
section of the load balancer configuration. - The host name returned by the
hostname
command. This is the default. The other methods are recommended.
It is strongly recommended you use the exact same
peers
section on all peers and then rely on the-L
orlocalpeer
methods, above, to set the peer host name for each load balancer. This type of configuration makes it easier to maintain a consistent configuration across all peers. - The
-
Add a
peers
attribute to yourstick-table
directive to include that stick table in the synchronization. The attribute references the name of thepeers
section you defined:haproxybackendstick-table type ip size 1m expire 10s store http_req_rate(10s) peers myclusterhaproxybackendstick-table type ip size 1m expire 10s store http_req_rate(10s) peers mycluster
Persist data at reload Jump to heading
A useful side effect of using a peers
section is that the load balancer will persist stick table data after a reload. This is because during a reload the old process connects to the new one and shares all of its stick table entries with it.
To use this feature, define a peers
section with only the local host address:
haproxy
peers myclusterpeer local 127.0.0.1:10000
haproxy
peers myclusterpeer local 127.0.0.1:10000
Without this, stick table data will be lost during a reload.
Add tables to peers section Jump to heading
This section applies to:
- HAProxy 2.0 and newer
- HAProxy Enterprise 2.0r1 and newer
- HAProxy ALOHA 11.5 and newer
You can also add stick table definitions directly to the peers
section, where you don’t need to use the peers
attribute on the stick table. Then, reference the table as peers-section-name/table-name
.
In the following example, we’ve added a stick table definition via the table
line to the peers
section and updated it to have the name sticktable1
. We then reference it on the http-request track-sc0
and http-request deny
lines in the frontend:
haproxy
peers myclusterpeer loadbalancer1 192.168.1.10:10000peer loadbalancer2 192.168.1.11:10000table sticktable1 type ip size 1m expire 10s store http_req_rate(10s)frontend wwwbind :80http-request track-sc0 src table mycluster/sticktable1http-request deny if { sc_http_req_rate(0,mycluster/sticktable1) gt 10 }
haproxy
peers myclusterpeer loadbalancer1 192.168.1.10:10000peer loadbalancer2 192.168.1.11:10000table sticktable1 type ip size 1m expire 10s store http_req_rate(10s)frontend wwwbind :80http-request track-sc0 src table mycluster/sticktable1http-request deny if { sc_http_req_rate(0,mycluster/sticktable1) gt 10 }
Server syntax Jump to heading
This section applies to:
- HAProxy 2.0 and newer
- HAProxy Enterprise 2.0r1 and newer
- HAProxy ALOHA 11.5 and newer
Instead of writing peer
lines, you can useserver
lines. This allows the same functionality of a server as seen in a backend section, such as the ability to connect to the remote peer using TLS. You can also use a default-server
line to set defaults for the server lines that follow.
haproxy
peers mycluster# peers will receive sync traffic over the bound port# optional: enable SSLbind :10000 ssl crt /ssl.pem# define defaults for 'server' lines# e.g. 'ssl', peers will send sync traffic using SSLdefault-server ssl# do not set an IP address and port for the local peerserver loadbalancer1server loadbalancer2 192.168.1.11:10000
haproxy
peers mycluster# peers will receive sync traffic over the bound port# optional: enable SSLbind :10000 ssl crt /ssl.pem# define defaults for 'server' lines# e.g. 'ssl', peers will send sync traffic using SSLdefault-server ssl# do not set an IP address and port for the local peerserver loadbalancer1server loadbalancer2 192.168.1.11:10000
See also Jump to heading
Do you have any suggestions on how we can improve the content of this page?