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:

  1. 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:

    haproxy
    frontend www
    bind :80
    stick-table type ip size 1m expire 10s store http_req_rate(10s)
    haproxy
    frontend www
    bind :80
    stick-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:

    haproxy
    frontend www
    bind :80
    stick-table type ip size 1m expire 10s store http_req_rate(10s),conn_rate(10s),bytes_in_rate(10s)
    haproxy
    frontend www
    bind :80
    stick-table type ip size 1m expire 10s store http_req_rate(10s),conn_rate(10s),bytes_in_rate(10s)
  2. 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 key type specified in the stick-table directive. For this example, we use the src fetch method because it matches stick table type ip.

    haproxy
    frontend www
    bind :80
    stick-table type ip size 1m expire 10s store http_req_rate(10s)
    http-request track-sc0 src
    haproxy
    frontend www
    bind :80
    stick-table type ip size 1m expire 10s store http_req_rate(10s)
    http-request track-sc0 src
  3. Add the http-request deny directive to deny any client whose request rate goes above 10:

    haproxy
    frontend www
    bind :80
    stick-table type ip size 1m expire 10s store http_req_rate(10s)
    http-request track-sc0 src
    http-request deny if { sc_http_req_rate(0) gt 10 }
    haproxy
    frontend www
    bind :80
    stick-table type ip size 1m expire 10s store http_req_rate(10s)
    http-request track-sc0 src
    http-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 mycluster
peer loadbalancer1 192.168.1.10:10000
peer loadbalancer2 192.168.1.11:10000
table sticktable1 type ip size 1m expire 10s store http_req_rate(10s)
frontend www
bind :80
http-request track-sc0 src table mycluster/sticktable1
http-request deny if { sc_http_req_rate(0,mycluster/sticktable1) gt 10 }
haproxy
peers mycluster
peer loadbalancer1 192.168.1.10:10000
peer loadbalancer2 192.168.1.11:10000
table sticktable1 type ip size 1m expire 10s store http_req_rate(10s)
frontend www
bind :80
http-request track-sc0 src table mycluster/sticktable1
http-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 www
bind :80
stick-table type string size 1m expire 10s store http_req_rate(10s)
use_backend apiservers if { path_beg /api/ }
default_backend webservers
backend webservers
server s1 192.168.50.20:80
http-request track-sc0 be_name table www
backend apiservers
server s1 192.168.50.21:80
http-request track-sc0 be_name table www
haproxy
frontend www
bind :80
stick-table type string size 1m expire 10s store http_req_rate(10s)
use_backend apiservers if { path_beg /api/ }
default_backend webservers
backend webservers
server s1 192.168.50.20:80
http-request track-sc0 be_name table www
backend apiservers
server s1 192.168.50.21:80
http-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 www
bind :80
stick-table type ip size 1m expire 10s store http_req_rate(10s)
haproxy
frontend www
bind :80
stick-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 www
bind :80
stick-table type ip size 1m expire 10s store http_req_rate(10s)
http-request track-sc0 src
haproxy
frontend www
bind :80
stick-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 mycluster
peer loadbalancer1 192.168.1.10:10000
peer loadbalancer2 192.168.1.11:10000
table 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 www
bind :80
# key is source IP address
http-request track-sc0 src table mycluster/sticktable1
# key is backend name
http-request track-sc1 be_name table mycluster/sticktable2
# key is Host header
http-request track-sc2 req.hdr(Host) mycluster/table sticktable3
haproxy
peers mycluster
peer loadbalancer1 192.168.1.10:10000
peer loadbalancer2 192.168.1.11:10000
table 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 www
bind :80
# key is source IP address
http-request track-sc0 src table mycluster/sticktable1
# key is backend name
http-request track-sc1 be_name table mycluster/sticktable2
# key is Host header
http-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 www
bind :80
stick-table type ip size 1m expire 10s store http_err_rate(10s),bytes_out_rate(10s),glitch_rate(10s)
http-request track-sc1 src
http-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 www
bind :80
stick-table type ip size 1m expire 10s store http_err_rate(10s),bytes_out_rate(10s),glitch_rate(10s)
http-request track-sc1 src
http-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 the sc-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 www
bind :80
stick-table type ip size 1m expire 10s store gpc_rate(1,10s)
http-request track-sc2 src
http-request sc-inc-gpc(0,2) if { path_beg /restricted/ }
http-request tarpit if { sc_gpc_rate(0,2) gt 5 }
haproxy
frontend www
bind :80
stick-table type ip size 1m expire 10s store gpc_rate(1,10s)
http-request track-sc2 src
http-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 counter sc2 to store a GPC record in the table.
  • The second http-request directive uses sticky counter sc2 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 counter sc2 and performs a tarpit 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 www
bind :80
stick-table type ip size 1m expire 10s store gpc0,gpc1
http-request track-sc0 src
http-request sc-inc-gpc0(0) if { req.hdr(Host) example.com }
http-request sc-inc-gpc1(0) if { url_param(example) test }
haproxy
frontend www
bind :80
stick-table type ip size 1m expire 10s store gpc0,gpc1
http-request track-sc0 src
http-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 www
bind :80
stick-table type ip size 1m expire 10s store gpc0,gpc1,gpc0_rate(10s),gpc1_rate(10s)
haproxy
frontend www
bind :80
stick-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 www
bind :80
stick-table type ip size 1m expire 10s store gpt(2)
http-request track-sc0 src
http-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 www
bind :80
stick-table type ip size 1m expire 10s store gpt(2)
http-request track-sc0 src
http-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 www
bind :80
stick-table type ip size 1m expire 10s store gpt0,gpt1
http-request track-sc0 src
http-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 www
bind :80
stick-table type ip size 1m expire 10s store gpt0,gpt1
http-request track-sc0 src
http-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:

  1. Add one or more peer lines to a peers section. Each one identifies a load balancer that takes part in the synchronization. One of the peer lines must be the local host:

    haproxy
    peers mycluster
    # local host, active node
    peer loadbalancer1 192.168.1.10:10000
    # standby node
    peer loadbalancer2 192.168.1.11:10000
    haproxy
    peers mycluster
    # local host, active node
    peer loadbalancer1 192.168.1.10:10000
    # standby node
    peer loadbalancer2 192.168.1.11:10000

    Ensure 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 the global 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 or localpeer 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.

  2. Add a peers attribute to your stick-table directive to include that stick table in the synchronization. The attribute references the name of the peers section you defined:

    haproxy
    backend
    stick-table type ip size 1m expire 10s store http_req_rate(10s) peers mycluster
    haproxy
    backend
    stick-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 mycluster
peer local 127.0.0.1:10000
haproxy
peers mycluster
peer 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 mycluster
peer loadbalancer1 192.168.1.10:10000
peer loadbalancer2 192.168.1.11:10000
table sticktable1 type ip size 1m expire 10s store http_req_rate(10s)
frontend www
bind :80
http-request track-sc0 src table mycluster/sticktable1
http-request deny if { sc_http_req_rate(0,mycluster/sticktable1) gt 10 }
haproxy
peers mycluster
peer loadbalancer1 192.168.1.10:10000
peer loadbalancer2 192.168.1.11:10000
table sticktable1 type ip size 1m expire 10s store http_req_rate(10s)
frontend www
bind :80
http-request track-sc0 src table mycluster/sticktable1
http-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 SSL
bind :10000 ssl crt /ssl.pem
# define defaults for 'server' lines
# e.g. 'ssl', peers will send sync traffic using SSL
default-server ssl
# do not set an IP address and port for the local peer
server loadbalancer1
server loadbalancer2 192.168.1.11:10000
haproxy
peers mycluster
# peers will receive sync traffic over the bound port
# optional: enable SSL
bind :10000 ssl crt /ssl.pem
# define defaults for 'server' lines
# e.g. 'ssl', peers will send sync traffic using SSL
default-server ssl
# do not set an IP address and port for the local peer
server loadbalancer1
server 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?