InfraRunBook
    Back to articles

    HAProxy Stick Tables: Rate Limiting, Connection Tracking, and DDoS Mitigation

    HAProxy
    Published: Feb 15, 2026
    Updated: Feb 15, 2026

    Master HAProxy stick tables for real-time rate limiting, connection tracking, and DDoS mitigation with production-ready configurations, detailed examples, and advanced patterns.

    HAProxy Stick Tables: Rate Limiting, Connection Tracking, and DDoS Mitigation

    Introduction

    HAProxy stick tables are one of the most powerful yet under-utilised features in the HAProxy ecosystem. They provide an in-memory key-value store that tracks connection metadata in real time — enabling you to implement rate limiting, connection throttling, abuse detection, and full DDoS mitigation without any external dependencies. Unlike tools that rely on external databases or third-party WAFs, stick tables operate entirely within HAProxy's event loop, adding near-zero latency to request processing.

    This run book covers everything from basic stick table anatomy to advanced multi-layered defence patterns. Every configuration is production-tested and ready to deploy.


    1. Stick Table Fundamentals

    1.1 What Is a Stick Table?

    A stick table is a fixed-size in-memory hash table attached to a frontend or backend. Each entry is keyed by a data type (IP address, integer, string, or binary) and can store multiple counters simultaneously. HAProxy updates these counters automatically as traffic flows through.

    1.2 Core Syntax

    backend st_src_tracking
        stick-table type ip size 1m expire 10m nopurge peers mypeers store http_req_rate(10s),conn_cur,conn_rate(10s),gpc0,gpc0_rate(60s),bytes_out_rate(60s)

    Let's break down each parameter:

    • type ip — Key type. Options:
      ip
      ,
      ipv6
      ,
      integer
      ,
      string len <n>
      ,
      binary len <n>
      .
    • size 1m — Maximum number of entries (1 million). Memory usage depends on counters stored.
    • expire 10m — Entries are evicted after 10 minutes of inactivity.
    • nopurge — Prevents purging of entries when the table is full; new entries are rejected instead. Omit this for automatic LRU eviction.
    • peers mypeers — Enables stick table replication (covered in section 6).
    • store — Comma-separated list of counters to track.

    1.3 Available Counters

    The following counters can be stored in a stick table:

    • conn_cnt — Total number of connections since entry creation.
    • conn_cur — Current concurrent connections.
    • conn_rate(<period>) — Connection rate over the specified period.
    • http_req_cnt — Total HTTP requests.
    • http_req_rate(<period>) — HTTP request rate over the specified period.
    • http_err_cnt — Total HTTP errors (4xx/5xx from backend).
    • http_err_rate(<period>) — HTTP error rate.
    • http_fail_cnt — Total HTTP failures.
    • http_fail_rate(<period>) — HTTP failure rate.
    • bytes_in_cnt — Total bytes received from client.
    • bytes_in_rate(<period>) — Bytes received rate.
    • bytes_out_cnt — Total bytes sent to client.
    • bytes_out_rate(<period>) — Bytes sent rate.
    • gpc0 / gpc1 — General purpose counters (manually incremented).
    • gpc0_rate(<period>) / gpc1_rate(<period>) — Rate of GPC increments.
    • server_id — Last server ID used (for session persistence).

    2. Tracking Connections with Sticky Counters

    2.1 Understanding Sticky Counters (sc0, sc1, sc2)

    HAProxy provides three sticky counter slots per connection:

    sc0
    ,
    sc1
    , and
    sc2
    . Each slot binds the current connection to one stick table entry. You can track three different dimensions simultaneously — for example, source IP in sc0, a URL path in sc1, and an API key header in sc2.

    2.2 TCP-Level Tracking

    Track the source IP as soon as the TCP connection is established:

    frontend ft_web
        bind *:80
        bind *:443 ssl crt /etc/haproxy/certs/site.pem
    
        # Track source IP at TCP layer using sc0
        tcp-request connection track-sc0 src table st_src_tracking
    
        # Reject if too many concurrent connections from one IP
        tcp-request connection reject if { src_conn_cur(st_src_tracking) gt 50 }
    
        default_backend bk_web
    
    backend st_src_tracking
        stick-table type ip size 500k expire 5m store conn_cur,conn_rate(10s),http_req_rate(10s),http_err_rate(10s)

    2.3 HTTP-Level Tracking

    Track at the HTTP layer for finer-grained control:

    frontend ft_web
        bind *:443 ssl crt /etc/haproxy/certs/site.pem
    
        tcp-request connection track-sc0 src table st_src_tracking
    
        # Track at HTTP layer — needed for http_req_rate counters
        http-request track-sc1 src table st_http_tracking
    
        # Rate limit based on HTTP request rate
        http-request deny deny_status 429 if { sc_http_req_rate(1) gt 100 }
    
        default_backend bk_web
    
    backend st_src_tracking
        stick-table type ip size 500k expire 5m store conn_cur,conn_rate(10s)
    
    backend st_http_tracking
        stick-table type ip size 500k expire 5m store http_req_rate(10s),http_err_rate(10s)

    Key insight:

    conn_rate
    and
    conn_cur
    are updated at the TCP layer.
    http_req_rate
    ,
    http_err_rate
    , and related HTTP counters require the request to be parsed, so tracking must happen via
    http-request track-sc
    or at minimum after the TCP content inspection phase.


    3. Rate Limiting Patterns

    3.1 Basic Request Rate Limiting

    Deny any source IP making more than 60 requests in 10 seconds:

    frontend ft_api
        bind *:443 ssl crt /etc/haproxy/certs/api.pem
    
        # Whitelist internal monitoring
        acl is_monitor src 10.0.0.0/8 172.16.0.0/12
    
        http-request track-sc0 src table st_api_rate
        http-request deny deny_status 429 if !is_monitor { sc_http_req_rate(0) gt 60 }
    
        # Return a Retry-After header on 429
        http-response set-header Retry-After 10 if { status 429 }
    
        default_backend bk_api
    
    backend st_api_rate
        stick-table type ip size 200k expire 2m store http_req_rate(10s)

    3.2 Tiered Rate Limiting (Soft + Hard)

    Apply a soft limit (delay via tarpit) before a hard deny:

    frontend ft_api
        bind *:443 ssl crt /etc/haproxy/certs/api.pem
    
        http-request track-sc0 src table st_tiered_rate
    
        # Hard limit: 200 req/10s → deny
        acl is_hard_abuse sc_http_req_rate(0) gt 200
        http-request deny deny_status 429 if is_hard_abuse
    
        # Soft limit: 100 req/10s → tarpit for 5s (holds connection open)
        acl is_soft_abuse sc_http_req_rate(0) gt 100
        http-request tarpit deny_status 429 if is_soft_abuse
    
        timeout tarpit 5s
    
        default_backend bk_api
    
    backend st_tiered_rate
        stick-table type ip size 200k expire 2m store http_req_rate(10s)

    Note: Tarpit holds the connection open silently, consuming attacker resources. The response is only sent after the

    timeout tarpit
    period.

    3.3 Per-URL Rate Limiting

    Rate-limit expensive endpoints like login or search independently:

    frontend ft_web
        bind *:443 ssl crt /etc/haproxy/certs/site.pem
    
        # General tracking
        http-request track-sc0 src table st_global_rate
    
        # Track login endpoint separately — combine IP + path as key
        acl is_login path_beg /api/login
        http-request track-sc1 src table st_login_rate if is_login
    
        # Global: 200 req/10s
        http-request deny deny_status 429 if { sc_http_req_rate(0) gt 200 }
    
        # Login: 10 req/60s
        http-request deny deny_status 429 if is_login { sc_http_req_rate(1) gt 10 }
    
        default_backend bk_web
    
    backend st_global_rate
        stick-table type ip size 500k expire 2m store http_req_rate(10s)
    
    backend st_login_rate
        stick-table type ip size 200k expire 5m store http_req_rate(60s)

    3.4 Rate Limiting by API Key

    Track by a custom header instead of IP:

    frontend ft_api
        bind *:443 ssl crt /etc/haproxy/certs/api.pem
    
        http-request track-sc0 req.hdr(X-API-Key) table st_apikey_rate if { req.hdr(X-API-Key) -m found }
    
        # Fallback: track by IP if no API key
        http-request track-sc1 src table st_ip_rate unless { req.hdr(X-API-Key) -m found }
    
        # API key limit: 1000 req/60s
        http-request deny deny_status 429 if { sc_http_req_rate(0) gt 1000 }
    
        # IP fallback limit: 30 req/60s
        http-request deny deny_status 429 if { sc_http_req_rate(1) gt 30 }
    
        default_backend bk_api
    
    backend st_apikey_rate
        stick-table type string len 64 size 100k expire 5m store http_req_rate(60s)
    
    backend st_ip_rate
        stick-table type ip size 200k expire 5m store http_req_rate(60s)

    4. Connection Tracking and Abuse Detection

    4.1 Slowloris / Slow Connection Detection

    Detect and reject slow clients that open many connections but send data slowly:

    frontend ft_web
        bind *:443 ssl crt /etc/haproxy/certs/site.pem
    
        tcp-request connection track-sc0 src table st_slowloris
    
        # More than 20 concurrent connections from a single IP
        tcp-request connection reject if { src_conn_cur(st_slowloris) gt 20 }
    
        # More than 40 new connections in 10 seconds
        tcp-request connection reject if { src_conn_rate(st_slowloris) gt 40 }
    
        # Aggressive timeouts to kill slow clients
        timeout client 15s
        timeout http-request 5s
        timeout http-keep-alive 5s
    
        default_backend bk_web
    
    backend st_slowloris
        stick-table type ip size 500k expire 2m store conn_cur,conn_rate(10s)

    4.2 Error Rate Monitoring (Credential Stuffing Detection)

    Detect IPs generating a high rate of HTTP errors (failed logins return 401/403):

    frontend ft_web
        bind *:443 ssl crt /etc/haproxy/certs/site.pem
    
        http-request track-sc0 src table st_error_tracking
    
        # Block if more than 30 errors in 60 seconds
        acl is_bruteforce sc_http_err_rate(0) gt 30
        http-request deny deny_status 403 if is_bruteforce
    
        default_backend bk_web
    
    backend st_error_tracking
        stick-table type ip size 200k expire 10m store http_err_rate(60s),http_err_cnt

    4.3 Using General Purpose Counters (GPC) for Flagging

    GPC counters let you "flag" an IP for custom conditions. For example, flag an IP as abusive and block it for a period:

    frontend ft_web
        bind *:443 ssl crt /etc/haproxy/certs/site.pem
    
        http-request track-sc0 src table st_gpc_flag
    
        # If already flagged, deny immediately
        http-request deny deny_status 403 if { src_get_gpc0(st_gpc_flag) gt 0 }
    
        # Flag if request rate exceeds 150/10s
        acl is_abusive sc_http_req_rate(0) gt 150
        http-request sc-set-gpt0(0) 1 if is_abusive
        http-request sc-inc-gpc0(0) if is_abusive
    
        default_backend bk_web
    
    backend st_gpc_flag
        stick-table type ip size 200k expire 30m store http_req_rate(10s),gpc0,gpc0_rate(60s)

    Once flagged, the IP is denied for the full 30-minute expiry window, even if the request rate drops. This is useful for blacklisting repeat offenders longer than the initial burst.


    5. DDoS Mitigation Strategy

    5.1 Multi-Layer Defence Configuration

    A comprehensive DDoS mitigation setup combines TCP-level and HTTP-level protections:

    # ===========================================
    # Global settings
    # ===========================================
    global
        log /dev/log local0 info
        log /dev/log local1 notice
        maxconn 100000
        tune.ssl.default-dh-param 2048
        stats socket /run/haproxy/admin.sock mode 660 level admin
        stats timeout 30s
    
    # ===========================================
    # Defaults
    # ===========================================
    defaults
        log     global
        mode    http
        option  httplog
        option  dontlognull
        timeout connect 5s
        timeout client  15s
        timeout server  15s
        timeout http-request 5s
        timeout http-keep-alive 5s
        timeout queue 30s
        timeout tarpit 5s
    
    # ===========================================
    # Stick tables (dedicated backends)
    # ===========================================
    backend st_tcp_conntrack
        stick-table type ip size 1m expire 5m store conn_cur,conn_rate(5s)
    
    backend st_http_ratelimit
        stick-table type ip size 1m expire 5m store http_req_rate(10s),http_err_rate(60s)
    
    backend st_http_abuse
        stick-table type ip size 500k expire 60m store gpc0,http_req_cnt
    
    # ===========================================
    # Frontend: DDoS-hardened
    # ===========================================
    frontend ft_https
        bind *:443 ssl crt /etc/haproxy/certs/site.pem alpn h2,http/1.1
        bind *:80
        redirect scheme https code 301 unless { ssl_fc }
    
        # ---- Whitelist trusted sources ----
        acl is_trusted src 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
        acl is_healthcheck path /health
    
        # ---- Layer 4: Connection tracking ----
        tcp-request connection track-sc0 src table st_tcp_conntrack
        tcp-request connection reject if !is_trusted { src_conn_cur(st_tcp_conntrack) gt 80 }
        tcp-request connection reject if !is_trusted { src_conn_rate(st_tcp_conntrack) gt 60 }
    
        # ---- Layer 7: HTTP rate tracking ----
        http-request track-sc1 src table st_http_ratelimit
        http-request track-sc2 src table st_http_abuse
    
        # ---- Check if already flagged as abusive ----
        acl is_flagged src_get_gpc0(st_http_abuse) gt 0
        http-request deny deny_status 403 if is_flagged !is_trusted !is_healthcheck
    
        # ---- Hard deny: >300 req/10s ----
        acl is_flood sc_http_req_rate(1) gt 300
        http-request deny deny_status 429 if is_flood !is_trusted
    
        # ---- Flag abusers: >500 req/10s → flagged for 60 minutes ----
        acl is_severe_flood sc_http_req_rate(1) gt 500
        http-request sc-inc-gpc0(2) if is_severe_flood !is_trusted
    
        # ---- Tarpit: 150-300 req/10s ----
        acl is_suspicious sc_http_req_rate(1) gt 150
        http-request tarpit deny_status 429 if is_suspicious !is_flood !is_trusted
    
        # ---- Error rate abuse: >50 errors/60s ----
        acl is_error_abuse sc_http_err_rate(1) gt 50
        http-request deny deny_status 403 if is_error_abuse !is_trusted
    
        # ---- Custom 429 response header ----
        http-response set-header Retry-After 30 if { status 429 }
    
        # ---- Logging tagged connections ----
        http-request set-header X-RateLimit-Remaining %[sc_http_req_rate(1)]
        http-request add-header X-Flagged true if is_flagged
    
        default_backend bk_web
    
    # ===========================================
    # Backend
    # ===========================================
    backend bk_web
        balance roundrobin
        option httpchk GET /health HTTP/1.1\r\nHost:\ localhost
        http-check expect status 200
        server web1 10.0.1.10:8080 check inter 3s fall 3 rise 2
        server web2 10.0.1.11:8080 check inter 3s fall 3 rise 2
        server web3 10.0.1.12:8080 check inter 3s fall 3 rise 2

    5.2 Explanation of Defence Layers

    1. Layer 4 — Connection Limiting: Drops TCP connections from IPs with >80 concurrent or >60 new connections in 5 seconds. Stops SYN floods before HTTP parsing.
    2. Layer 7 — Soft Tarpit: IPs sending 150–300 req/10s are tarpitted for 5 seconds per request, wasting attacker resources.
    3. Layer 7 — Hard Deny: IPs exceeding 300 req/10s receive an immediate 429.
    4. Layer 7 — Flagging: IPs exceeding 500 req/10s are flagged via GPC0, blocking them for 60 minutes even if the attack stops.
    5. Error Rate: IPs generating >50 backend errors/minute are blocked (catches credential stuffing and fuzzing).

    6. Stick Table Replication with Peers

    For high-availability setups, stick tables must be synchronised across HAProxy instances:

    peers mypeers
        peer haproxy1 10.0.1.1:1024
        peer haproxy2 10.0.1.2:1024
    
    backend st_src_tracking
        stick-table type ip size 1m expire 5m peers mypeers store conn_cur,conn_rate(10s),http_req_rate(10s),gpc0

    Key notes on peer replication:

    • Each HAProxy instance must have a
      peer
      entry matching its own hostname (the hostname must match
      hostname
      output or be set explicitly).
    • Port 1024 (or any chosen port) must be open between peers — ensure firewall rules allow it.
    • Replication is asynchronous; there is a brief window where one node may not have the latest counters.
    • Use
      bind-process
      if using multi-process mode to ensure the peers section runs on the correct process.
    # Verify peers are connected via the stats socket
    echo "show peers" | socat stdio /run/haproxy/admin.sock

    7. Monitoring and Managing Stick Tables

    7.1 Runtime API Commands

    HAProxy's runtime API (via the stats socket) provides powerful stick table management:

    # Show all stick tables and their sizes
    echo "show table" | socat stdio /run/haproxy/admin.sock
    
    # Show contents of a specific table
    echo "show table st_http_ratelimit" | socat stdio /run/haproxy/admin.sock
    
    # Show a specific entry
    echo "show table st_http_ratelimit data.http_req_rate gt 50" | socat stdio /run/haproxy/admin.sock
    
    # Manually set gpc0 to flag an IP
    echo "set table st_http_abuse key 203.0.113.50 data.gpc0 1" | socat stdio /run/haproxy/admin.sock
    
    # Clear a specific entry (unblock an IP)
    echo "clear table st_http_abuse key 203.0.113.50" | socat stdio /run/haproxy/admin.sock
    
    # Clear all entries in a table
    echo "clear table st_http_ratelimit" | socat stdio /run/haproxy/admin.sock

    7.2 Exporting Stick Table Data for Monitoring

    Create a script that exports top offenders to your monitoring system:

    #!/bin/bash
    # /usr/local/bin/haproxy-top-offenders.sh
    # Export top IPs by request rate to a file for Prometheus/Grafana
    
    SOCKET="/run/haproxy/admin.sock"
    TABLE="st_http_ratelimit"
    THRESHOLD=50
    OUTPUT="/var/log/haproxy/top_offenders.log"
    
    echo "show table ${TABLE} data.http_req_rate gt ${THRESHOLD}" \
      | socat stdio "${SOCKET}" \
      | awk 'NR>1 {print strftime("%Y-%m-%d %H:%M:%S"), $0}' \
      >> "${OUTPUT}"
    
    # Optional: send to syslog
    logger -t haproxy-ratelimit -p local0.warning "$(wc -l < ${OUTPUT}) IPs above threshold ${THRESHOLD}"

    7.3 Stats Page Integration

    Enable the stats page to visually inspect table sizes:

    frontend ft_stats
        bind 127.0.0.1:8404
        mode http
        stats enable
        stats uri /stats
        stats refresh 5s
        stats show-legends
        stats show-node
        stats admin if TRUE

    8. Memory Sizing Guide

    Stick table memory usage depends on the key type and stored counters. Use this formula to estimate:

    Memory ≈ entries × (key_size + 40 bytes base + Σ counter_sizes)

    Approximate counter sizes:

    • Each simple counter (conn_cnt, http_req_cnt, gpc0, etc.): ~8 bytes
    • Each rate counter (conn_rate, http_req_rate, etc.): ~16 bytes
    • IP key: 4 bytes (IPv4) or 16 bytes (IPv6)
    • String key: len + 4 bytes

    Example calculation for 1M entries with IPv4 key storing

    conn_cur,conn_rate(10s),http_req_rate(10s),gpc0
    :

    Per entry = 4 (key) + 40 (base) + 8 (conn_cur) + 16 (conn_rate) + 16 (http_req_rate) + 8 (gpc0) = 92 bytes
    Total = 1,000,000 × 92 = ~88 MB

    Ensure your HAProxy instance has sufficient RAM. Set

    global
    maxconn
    and OS-level limits accordingly.


    9. Testing Your Configuration

    9.1 Validate Configuration Syntax

    haproxy -c -f /etc/haproxy/haproxy.cfg

    9.2 Simulate Load with curl

    # Quick burst test — send 200 requests rapidly
    for i in $(seq 1 200); do
        curl -s -o /dev/null -w "%{http_code}\n" https://example.com/api/test
    done | sort | uniq -c | sort -rn

    9.3 Load Test with hey (recommended)

    # Install hey
    go install github.com/rakyll/hey@latest
    
    # Send 500 requests, 50 concurrent
    hey -n 500 -c 50 https://example.com/api/test
    
    # Check stick table after test
    echo "show table st_http_ratelimit" | socat stdio /run/haproxy/admin.sock

    9.4 Test from Multiple IPs

    # Use different source IPs via network namespaces (Linux)
    for ip in 192.168.100.{10..15}; do
        ip addr add ${ip}/24 dev eth0 2>/dev/null
        curl --interface ${ip} -s -o /dev/null -w "${ip}: %{http_code}\n" https://example.com/
        ip addr del ${ip}/24 dev eth0 2>/dev/null
    done

    10. Production Best Practices

    • Always whitelist monitoring and internal IPs — Use ACLs to bypass rate limiting for health checks, Prometheus scrapers, and internal services.
    • Start with soft limits — Deploy with logging only (no deny) first. Analyse the data before enforcing blocks.
    • Use separate tables for separate concerns — Don't cram all counters into one table. Use dedicated tables for TCP tracking, HTTP rate limiting, and abuse flagging.
    • Set appropriate expire times — Short expiry (2-5 min) for rate tables, longer expiry (30-60 min) for abuse flag tables.
    • Monitor table utilisation — Alert when a table reaches 80% capacity. An eviction storm can cause legitimate entries to be lost.
    • Log denied requests — Add
      http-request capture
      and custom log-format to track denials for forensics.
    • Test replication — In HA setups, regularly verify peers are synced and tables are consistent.
    • Document your thresholds — Keep a run book of each threshold, why it was chosen, and when it was last reviewed.
    # Custom log format that includes stick table counters
    log-format "%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs {%[sc_http_req_rate(1)]|%[src_conn_cur]} %{+Q}r"

    11. Troubleshooting Common Issues

    Issue: Stick table counters always show 0

    Cause: The

    track-sc
    directive is placed after the
    deny
    rule, so the counter is never incremented.

    Fix: Always place

    track-sc
    directives before any deny/tarpit rules.

    Issue: Rate limiting doesn't work on HTTP/2 connections

    Cause: HTTP/2 multiplexes many requests over a single TCP connection.

    conn_rate
    only counts new TCP connections.

    Fix: Use

    http_req_rate
    instead of
    conn_rate
    for HTTP/2 environments.

    Issue: Legitimate users behind NAT are being blocked

    Cause: Many users share a single source IP. IP-based tracking can cause collateral blocking.

    Fix: Use a combination of IP + header (e.g.,

    X-Forwarded-For
    , session cookie, or API key) as the stick table key, or increase thresholds for known NAT ranges.

    # Track by X-Forwarded-For if present, otherwise by src
    acl has_xff req.hdr(X-Forwarded-For) -m found
    http-request track-sc0 req.hdr_ip(X-Forwarded-For) table st_rate if has_xff
    http-request track-sc0 src table st_rate unless has_xff

    Issue: Table fills up and entries are not evicted

    Cause:

    nopurge
    is set and the table has reached
    size
    .

    Fix: Remove

    nopurge
    to allow LRU eviction, or increase the table size.

    Frequently Asked Questions

    What is an HAProxy stick table?

    A stick table is an in-memory key-value store built into HAProxy that tracks connection metadata in real time. Each entry is keyed by a data type (IP, string, integer, or binary) and can store multiple counters such as request rates, connection counts, error rates, and general purpose counters. Stick tables operate within HAProxy's event loop with near-zero latency overhead.

    How many sticky counters (sc) can I use per connection?

    HAProxy provides three sticky counter slots per connection: sc0, sc1, and sc2. Each slot can bind to a different stick table, allowing you to track three independent dimensions simultaneously — for example, source IP in sc0, URL path in sc1, and API key in sc2.

    What is the difference between tcp-request track-sc and http-request track-sc?

    tcp-request connection track-sc operates at the TCP layer (Layer 4) and updates counters like conn_cur and conn_rate as soon as the connection is established. http-request track-sc operates at the HTTP layer (Layer 7) after the request is parsed, and is required for HTTP-specific counters like http_req_rate and http_err_rate. For complete protection, use both.

    How do I implement rate limiting in HAProxy without external tools?

    Define a stick table with http_req_rate(period) storage, track the source IP with http-request track-sc0 src table <table_name>, then add an http-request deny rule checking if sc_http_req_rate(0) exceeds your threshold. For example: http-request deny deny_status 429 if { sc_http_req_rate(0) gt 100 } will return a 429 to any IP making more than 100 requests per tracking period.

    What is a tarpit in HAProxy and when should I use it?

    A tarpit holds the client connection open for a specified duration (set via timeout tarpit) without sending a response, consuming attacker resources. Use http-request tarpit instead of http-request deny for moderate abuse levels — it wastes the attacker's time and connection slots. The response is only sent after the tarpit timeout expires. This is particularly effective against automated tools that wait for responses.

    How do I replicate stick tables across multiple HAProxy nodes?

    Configure a peers section listing all HAProxy instances with their IP and a dedicated port, then reference it in the stick table definition with the peers keyword. For example: peers mypeers / peer haproxy1 10.0.1.1:1024 / peer haproxy2 10.0.1.2:1024. Then add 'peers mypeers' to your stick-table directive. Ensure the peer port is open in your firewall and each node's peer name matches its hostname.

    How much memory do HAProxy stick tables use?

    Memory usage depends on the number of entries, key size, and stored counters. A rough formula is: entries × (key_size + 40 bytes base + sum of counter sizes). Simple counters use ~8 bytes each, rate counters use ~16 bytes. For example, 1 million IPv4 entries with conn_cur, conn_rate, http_req_rate, and gpc0 uses approximately 88 MB.

    How do I unblock an IP that was incorrectly rate-limited?

    Use the HAProxy runtime API via the stats socket: echo "clear table <table_name> key <ip_address>" | socat stdio /run/haproxy/admin.sock. This immediately removes the entry and all its counters. If the IP was flagged via GPC, you can also reset just the counter: echo "set table <table_name> key <ip_address> data.gpc0 0" | socat stdio /run/haproxy/admin.sock.

    What are GPC (General Purpose Counters) and how are they used?

    GPC0 and GPC1 are general-purpose counters that you increment manually based on custom conditions. They are commonly used to 'flag' entries — for example, incrementing gpc0 when an IP exceeds a rate threshold, then checking src_get_gpc0 > 0 to deny flagged IPs. Unlike rate counters that decay over time, GPC values persist until the entry expires, making them ideal for longer-term blacklisting within the stick table expiry window.

    Why do my stick table counters show 0 even though traffic is flowing?

    The most common cause is rule ordering: the track-sc directive must appear before any deny, tarpit, or redirect rules that might terminate request processing. If a deny rule fires before track-sc, the counter is never incremented. Also verify that HTTP counters (http_req_rate, http_err_rate) are being tracked with http-request track-sc, not tcp-request connection track-sc, as TCP-level tracking does not update HTTP counters.

    Related Articles