Custom rules
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-tabledirective to a frontend or backend section. - Add a
tabledirective 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 the 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-tabledirective 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
1mrecords, which is 1,048,576 records. - A record expires after
10seconds 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-sc0directive, which adds a record to the table for the current request. As an argument, specify a fetch method that matches the keytypespecified in thestick-tabledirective. For this example, we use thesrcfetch 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 denydirective 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 denydirective 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:
haproxypeers 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 }
haproxypeers 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 can 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:
haproxyfrontend 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
haproxyfrontend 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.
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)
Then, use http-request track-sc0 to create or update the record for the client IP returned by the fetch src.
haproxyfrontend wwwbind :80stick-table type ip size 1m expire 10s store http_req_rate(10s)http-request track-sc0 src
haproxyfrontend 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-sc0http-request track-sc1http-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:
haproxypeers 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
haproxypeers 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:
haproxyhttp-request deny if { sc_http_req_rate(0,mycluster/sticktable1) gt 10 }
haproxyhttp-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:
haproxyfrontend 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 }
haproxyfrontend 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
1using thesc-inc-gpcaction. - HAProxy ALOHA 15.5 / HAProxy Enterprise 2.8r1 and up: increase a GPC by any value using the
sc-add-gpcaction. 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:
haproxyfrontend 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 }
haproxyfrontend 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-tabledirective defines a GPC rate array of length 1. - The first
http-requestdirective uses sticky countersc2to store a GPC record in the table. - The second
http-requestdirective uses sticky countersc2to increment the GPC if the client attempts to access a path starting with/restricted/. - The third
http-requestdirective fetches the GPC rate record from the table associated with sticky countersc2and performs atarpitaction 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:
haproxyfrontend 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 }
haproxyfrontend 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:
haproxyfrontend wwwbind :80stick-table type ip size 1m expire 10s store gpc0,gpc1,gpc0_rate(10s),gpc1_rate(10s)
haproxyfrontend 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:
haproxyfrontend 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 }
haproxyfrontend 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:
haproxyfrontend 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 }
haproxyfrontend 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
peerlines to apeerssection. Each one identifies a load balancer that takes part in the synchronization. One of thepeerlines 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
peerdirective for the local host matches the name of the host as determined by one of the following methods, in order of precedence:- The
-Largument specified in the command line used to start the load balancer process. - The
localpeername specified in theglobalsection of the load balancer configuration. - The host name returned by the
hostnamecommand. This is the default. The other methods are recommended.
It is strongly recommended you use the exact same
peerssection on all peers and then rely on the-Lorlocalpeermethods, 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
peersattribute to yourstick-tabledirective to include that stick table in the synchronization. The attribute references the name of thepeerssection 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:
haproxypeers myclusterpeer local 127.0.0.1:10000
haproxypeers 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:
haproxypeers 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 }
haproxypeers 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 use server 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.
haproxypeers 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
haproxypeers 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
See these related topics in the HAProxy Configuration Reference:
- To increment general-purpose counter
0, see http-request sc-inc-gpc0. - To set general-purpose tag
0, see http-request sc-set-gpt0. - To use sticky counter
0to generate a stick table entry from the current request, see http-request track-sc0. - To fetch the HTTP request rate from a stick table, see sc_http_req_rate.
- To create a peers table that multiple configurations can share, see peers.
- To define a stick table, see stick-table.
See these related topics in the HAProxy Runtime API Reference. The Runtime API allows you to perform actions on a running load balancer without interrupting the processing of traffic.
- To see stick table contents, see show table.
- To clear a stick table, see clear table.
- To add or update a record in a stick table, see set table.
Do you have any suggestions on how we can improve the content of this page?