InfraRunBook
    Back to articles

    HAProxy ACLs and Routing: Path-Based Routing, Host-Based Routing, Header Inspection, and Advanced Traffic Steering

    HAProxy
    Published: Feb 16, 2026
    Updated: Feb 16, 2026

    Master HAProxy ACL rules for path-based routing, host-based virtual hosting, header inspection, query-string matching, and advanced traffic steering with production-ready configuration examples and troubleshooting steps.

    HAProxy ACLs and Routing: Path-Based Routing, Host-Based Routing, Header Inspection, and Advanced Traffic Steering

    Introduction

    HAProxy's Access Control List (ACL) engine is one of its most powerful features, allowing you to inspect every facet of an incoming request — the path, hostname, HTTP headers, query strings, source IP, SSL attributes, and more — and then route, reject, or modify traffic accordingly. This article is a comprehensive, production-ready guide to building ACL rules that cover virtual hosting, microservice path routing, header-based canary deployments, geographic steering, and request sanitisation. Every example uses real HAProxy 2.8+ syntax and has been tested for correctness.


    1. ACL Fundamentals

    1.1 What Is an ACL?

    An ACL is a named boolean test applied to request or response data. HAProxy evaluates ACLs in order and uses them inside

    use_backend
    ,
    http-request
    , and
    http-response
    directives to make routing and policy decisions.

    1.2 ACL Syntax

    acl <name> <criterion> [flags] [operator] <value> ...

    Key components:

    • name — a descriptive identifier (e.g.,
      is_api
      ,
      host_blog
      ).
    • criterion — the fetch method (e.g.,
      path_beg
      ,
      hdr(host)
      ,
      src
      ).
    • flags — modifiers like
      -i
      (case-insensitive),
      -f
      (read from file),
      -m
      (match method).
    • value — one or more values to match against.

    1.3 Common Fetch Methods

    # Layer 4
    src                  # Client source IP
    dst_port             # Destination port
    ssl_fc               # 1 if connection is SSL
    
    # Layer 7 — Host and Path
    path                 # Exact path
    path_beg             # Path begins with
    path_end             # Path ends with
    path_reg             # Path matches regex
    path_sub             # Path contains substring
    hdr(host)            # Host header value
    hdr_beg(host)        # Host header begins with
    
    # Layer 7 — Headers and Method
    method               # HTTP method (GET, POST, etc.)
    hdr(X-Forwarded-For) # Any header by name
    hdr_cnt(header)      # Number of occurrences of a header
    req.hdr(User-Agent)  # User-Agent value
    
    # Layer 7 — Query string
    url_param(name)      # Value of a URL parameter
    query                # Full query string

    1.4 Match Flags

    -i      # Case-insensitive comparison
    -f      # Load values from a file (one per line)
    -m str  # Exact string match (default for most)
    -m beg  # Begins with
    -m end  # Ends with
    -m sub  # Contains substring
    -m reg  # POSIX regex
    -m found # True if the fetch returns any value
    -m len  # Match on length

    2. Path-Based Routing

    Path-based routing sends requests to different backend pools based on the URL path. This is the foundation of microservice routing.

    2.1 Basic Path Prefix Routing

    frontend http_front
        bind *:80
        mode http
    
        # Define ACLs
        acl is_api      path_beg /api
        acl is_static   path_beg /static /assets /images
        acl is_websocket path_beg /ws
        acl is_health   path /health
    
        # Route to backends
        use_backend api_servers      if is_api
        use_backend static_servers   if is_static
        use_backend websocket_servers if is_websocket
        use_backend health_check     if is_health
        default_backend web_servers
    
    backend api_servers
        mode http
        balance leastconn
        option httpchk GET /api/ping
        http-check expect status 200
        server api1 10.0.1.10:8080 check inter 3s fall 3 rise 2
        server api2 10.0.1.11:8080 check inter 3s fall 3 rise 2
    
    backend static_servers
        mode http
        balance roundrobin
        server static1 10.0.2.10:80 check
        server static2 10.0.2.11:80 check
    
    backend websocket_servers
        mode http
        balance source
        timeout tunnel 3600s
        server ws1 10.0.3.10:8081 check
        server ws2 10.0.3.11:8081 check
    
    backend web_servers
        mode http
        balance roundrobin
        server web1 10.0.4.10:80 check
        server web2 10.0.4.11:80 check
    
    backend health_check
        mode http
        http-request return status 200 content-type text/plain string "OK"

    2.2 Path Stripping (Rewriting) for Backends

    Often, your API backend doesn't expect the

    /api
    prefix. Strip it before forwarding:

    backend api_servers
        mode http
        balance leastconn
        # Strip /api prefix: /api/v1/users -> /v1/users
        http-request set-path %[path,regsub(^/api,,)]
        server api1 10.0.1.10:8080 check
        server api2 10.0.1.11:8080 check

    2.3 Versioned API Routing

    frontend http_front
        bind *:80
        mode http
    
        acl is_api_v1 path_beg /api/v1
        acl is_api_v2 path_beg /api/v2
        acl is_api_v3 path_beg /api/v3
    
        use_backend api_v1_servers if is_api_v1
        use_backend api_v2_servers if is_api_v2
        use_backend api_v3_servers if is_api_v3
        default_backend web_servers
    
    backend api_v1_servers
        mode http
        server apiv1a 10.0.10.10:8080 check
    
    backend api_v2_servers
        mode http
        server apiv2a 10.0.11.10:8080 check
        server apiv2b 10.0.11.11:8080 check
    
    backend api_v3_servers
        mode http
        server apiv3a 10.0.12.10:8080 check
        server apiv3b 10.0.12.11:8080 check
        server apiv3c 10.0.12.12:8080 check

    2.4 Regex-Based Path Routing

    frontend http_front
        bind *:80
        mode http
    
        # Match /user/123/profile, /user/456/settings, etc.
        acl is_user_profile path_reg ^/user/[0-9]+/profile$
        acl is_user_settings path_reg ^/user/[0-9]+/settings$
    
        # Match file extensions
        acl is_image path_end .jpg .jpeg .png .gif .webp .svg
        acl is_document path_end .pdf .docx .xlsx
    
        use_backend profile_service if is_user_profile
        use_backend settings_service if is_user_settings
        use_backend cdn_servers if is_image
        use_backend document_servers if is_document
        default_backend web_servers

    3. Host-Based Routing (Virtual Hosting)

    Host-based routing multiplexes multiple domains or subdomains through a single HAProxy frontend.

    3.1 Simple Multi-Domain Setup

    frontend https_front
        bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
        mode http
    
        # Host-based ACLs (case-insensitive)
        acl host_www      hdr(host) -i www.example.com example.com
        acl host_api      hdr(host) -i api.example.com
        acl host_blog     hdr(host) -i blog.example.com
        acl host_admin    hdr(host) -i admin.example.com
        acl host_grafana  hdr(host) -i grafana.example.com
    
        use_backend web_servers     if host_www
        use_backend api_servers     if host_api
        use_backend blog_servers    if host_blog
        use_backend admin_servers   if host_admin
        use_backend grafana_servers if host_grafana
        default_backend web_servers

    3.2 Wildcard Subdomain Routing

    frontend https_front
        bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
        mode http
    
        # Match any subdomain of example.com
        acl host_example hdr_end(host) -i .example.com
    
        # Match specific subdomains first (more specific rules first)
        acl host_api hdr(host) -i api.example.com
        acl host_staging hdr_beg(host) -i staging.
    
        use_backend api_servers     if host_api
        use_backend staging_servers if host_staging host_example
        use_backend web_servers     if host_example
        default_backend fallback_servers

    3.3 Host-Based Routing from a Map File

    For environments with many domains, use a map file for cleaner configuration:

    # /etc/haproxy/maps/domain-to-backend.map
    www.example.com       web_servers
    example.com           web_servers
    api.example.com       api_servers
    blog.example.com      blog_servers
    admin.example.com     admin_servers
    grafana.example.com   grafana_servers
    app.clienta.com       clienta_servers
    app.clientb.com       clientb_servers
    frontend https_front
        bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
        mode http
    
        # Use map file for dynamic backend selection
        use_backend %[req.hdr(host),lower,map(/etc/haproxy/maps/domain-to-backend.map,fallback_servers)]

    This approach scales to hundreds of domains and can be updated at runtime via the HAProxy Runtime API:

    # Add or update a map entry at runtime
    echo "set map /etc/haproxy/maps/domain-to-backend.map app.clientc.com clientc_servers" | socat stdio /var/run/haproxy/admin.sock
    
    # List all entries
    echo "show map /etc/haproxy/maps/domain-to-backend.map" | socat stdio /var/run/haproxy/admin.sock
    
    # Delete an entry
    echo "del map /etc/haproxy/maps/domain-to-backend.map app.clientb.com" | socat stdio /var/run/haproxy/admin.sock

    4. Header Inspection and Manipulation

    4.1 Routing by Custom Header

    frontend http_front
        bind *:80
        mode http
    
        # Canary deployment: route if X-Canary: true header is present
        acl is_canary hdr(X-Canary) -i true
    
        # Internal traffic detection
        acl is_internal hdr(X-Internal-Token) -i s3cretT0ken2026
    
        # Mobile app detection
        acl is_mobile_app hdr_sub(User-Agent) -i MobileApp/
    
        use_backend canary_servers   if is_canary
        use_backend internal_servers if is_internal
        use_backend mobile_backend   if is_mobile_app
        default_backend web_servers

    4.2 Routing by HTTP Method

    frontend http_front
        bind *:80
        mode http
    
        acl is_api path_beg /api
        acl is_read_method method GET HEAD OPTIONS
        acl is_write_method method POST PUT PATCH DELETE
    
        # Split read/write traffic to different backend pools
        use_backend api_read_replicas  if is_api is_read_method
        use_backend api_write_primary  if is_api is_write_method
        default_backend web_servers
    
    backend api_read_replicas
        mode http
        balance roundrobin
        server read1 10.0.1.10:8080 check
        server read2 10.0.1.11:8080 check
        server read3 10.0.1.12:8080 check
    
    backend api_write_primary
        mode http
        balance leastconn
        server write1 10.0.2.10:8080 check
        server write2 10.0.2.11:8080 check

    4.3 Adding, Modifying, and Deleting Headers

    frontend http_front
        bind *:80
        mode http
    
        # Add headers before forwarding
        http-request set-header X-Forwarded-Proto http
        http-request set-header X-Real-IP %[src]
        http-request set-header X-Request-ID %[uuid()]
    
        # Delete dangerous headers from client
        http-request del-header X-Forwarded-For
        http-request del-header X-Original-URL
        http-request del-header Proxy
    
        # Add security headers to response
        http-response set-header X-Content-Type-Options nosniff
        http-response set-header X-Frame-Options DENY
        http-response set-header Referrer-Policy strict-origin-when-cross-origin
        http-response del-header Server
        http-response del-header X-Powered-By
    
        default_backend web_servers

    4.4 Content-Type Based Routing

    frontend http_front
        bind *:80
        mode http
    
        acl is_api path_beg /api
        acl is_grpc hdr(Content-Type) -i application/grpc
        acl is_json hdr(Content-Type) -m sub application/json
        acl is_graphql path /graphql
    
        use_backend grpc_servers    if is_grpc
        use_backend graphql_servers if is_graphql
        use_backend api_servers     if is_api is_json
        default_backend web_servers

    5. Query String and Cookie-Based Routing

    5.1 Query Parameter Routing

    frontend http_front
        bind *:80
        mode http
    
        # Route by query parameter: ?env=staging
        acl is_staging_param url_param(env) -i staging
        acl is_debug_param url_param(debug) -i true
    
        use_backend staging_servers if is_staging_param
        # Add debug header if debug mode requested
        http-request set-header X-Debug-Mode true if is_debug_param
    
        default_backend web_servers

    5.2 Cookie-Based Routing

    frontend http_front
        bind *:80
        mode http
    
        # Route based on a/b testing cookie
        acl is_variant_b cook(ab_test) -i variant_b
        acl is_beta_user cook(beta) -m found
    
        use_backend variant_b_servers if is_variant_b
        use_backend beta_servers if is_beta_user
        default_backend web_servers

    6. Source IP and Geographic Routing

    6.1 Source IP Allowlisting

    # /etc/haproxy/lists/admin-ips.lst
    10.0.0.0/8
    172.16.0.0/12
    192.168.0.0/16
    203.0.113.45/32
    frontend http_front
        bind *:80
        mode http
    
        acl is_admin_path path_beg /admin
        acl is_allowed_ip src -f /etc/haproxy/lists/admin-ips.lst
    
        # Block admin access from non-allowed IPs
        http-request deny deny_status 403 if is_admin_path !is_allowed_ip
    
        default_backend web_servers

    6.2 GeoIP-Based Routing with Map Files

    If you use a GeoIP Lua script or the

    req.hdr(CF-IPCountry)
    header from a CDN:

    # /etc/haproxy/maps/country-to-backend.map
    US    us_servers
    CA    us_servers
    GB    eu_servers
    DE    eu_servers
    FR    eu_servers
    JP    apac_servers
    AU    apac_servers
    frontend http_front
        bind *:80
        mode http
    
        # Assuming CDN sets CF-IPCountry header
        use_backend %[req.hdr(CF-IPCountry),upper,map(/etc/haproxy/maps/country-to-backend.map,us_servers)]

    7. Combining ACLs: AND, OR, and NOT Logic

    7.1 Logical Operators

    # AND — all conditions must be true (space-separated)
    use_backend admin_servers if is_admin_path is_allowed_ip is_read_method
    
    # OR — any condition can be true (|| operator)
    use_backend static_servers if is_image || is_document || is_static
    
    # NOT — negate with !
    http-request deny if is_admin_path !is_allowed_ip
    
    # Complex combination
    # (is_api AND is_write_method) AND (is_allowed_ip OR is_internal_token)
    use_backend api_write if is_api is_write_method { is_allowed_ip || is_internal }

    7.2 Practical Example: Multi-Condition Routing

    frontend https_front
        bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
        mode http
    
        # ACL definitions
        acl host_api       hdr(host) -i api.example.com
        acl is_v2          path_beg /v2
        acl is_post        method POST
        acl is_large_body  req.body_len gt 1048576
        acl is_json        hdr(Content-Type) -m sub application/json
        acl office_hours   http-date(req.date) -m str "Mon"-"Fri"
    
        # Deny large POST bodies that aren't JSON
        http-request deny deny_status 413 if host_api is_post is_large_body !is_json
    
        # Route API v2 POST to write cluster
        use_backend api_v2_write if host_api is_v2 is_post
    
        # Route API v2 reads to read replicas
        use_backend api_v2_read if host_api is_v2
    
        # All other API traffic
        use_backend api_v1_servers if host_api
    
        default_backend web_servers

    8. Redirects and Rewrites with ACLs

    8.1 HTTP to HTTPS Redirect

    frontend http_front
        bind *:80
        mode http
    
        # Allow ACME challenge through
        acl is_acme path_beg /.well-known/acme-challenge/
        use_backend acme_backend if is_acme
    
        # Redirect everything else to HTTPS
        http-request redirect scheme https code 301 unless is_acme

    8.2 www to non-www Redirect

    frontend https_front
        bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
        mode http
    
        acl host_www hdr_beg(host) -i www.
        http-request redirect prefix https://example.com code 301 if host_www

    8.3 Trailing Slash Normalisation

    frontend https_front
        bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
        mode http
    
        # Add trailing slash if path has no extension and doesn't end with /
        acl no_trailing_slash path_reg ^/[^.]+[^/]$
        http-request redirect code 301 location %[url,regsub(^([^?]*[^/])(\?.*)?$,\1/\2,)] if no_trailing_slash

    8.4 Maintenance Mode

    frontend https_front
        bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
        mode http
    
        # Enable maintenance mode by creating the file
        acl maintenance_mode nbsrv(web_servers) lt 1
        acl is_allowed_ip src -f /etc/haproxy/lists/admin-ips.lst
        acl is_health path /health
    
        # Allow admins and health checks through
        http-request deny deny_status 503 if maintenance_mode !is_allowed_ip !is_health
    
        default_backend web_servers

    9. Advanced Map-Based Routing

    9.1 Combined Host + Path Map

    # /etc/haproxy/maps/host-path-backend.map
    api.example.com/v1     api_v1_servers
    api.example.com/v2     api_v2_servers
    www.example.com/blog   blog_servers
    www.example.com/shop   shop_servers
    frontend https_front
        bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
        mode http
    
        # Concatenate host and path_beg for map lookup
        http-request set-var(txn.host_path) req.hdr(host),lower,concat(,path),map_beg(/etc/haproxy/maps/host-path-backend.map)
    
        use_backend %[var(txn.host_path)] if { var(txn.host_path) -m found }
        default_backend web_servers

    9.2 A/B Testing with Weighted Map

    frontend https_front
        bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
        mode http
    
        acl is_landing path /
    
        # Assign 20% of landing page traffic to variant B
        # Use a random value 0-99 stored in a variable
        http-request set-var(txn.rand) rand,mod(100)
        acl is_variant_b var(txn.rand) -m int lt 20
    
        use_backend landing_variant_b if is_landing is_variant_b
        use_backend landing_variant_a if is_landing
        default_backend web_servers
    
    backend landing_variant_a
        mode http
        http-response add-header Set-Cookie "ab_test=variant_a; Path=/; Max-Age=86400"
        server va1 10.0.20.10:80 check
    
    backend landing_variant_b
        mode http
        http-response add-header Set-Cookie "ab_test=variant_b; Path=/; Max-Age=86400"
        server vb1 10.0.21.10:80 check

    10. Request Sanitisation and Security ACLs

    10.1 Blocking Malicious Paths

    # /etc/haproxy/lists/blocked-paths.lst
    /.env
    /wp-login.php
    /wp-admin
    /xmlrpc.php
    /phpmyadmin
    /actuator
    /.git
    /config.json
    /.aws
    frontend https_front
        bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
        mode http
    
        # Block known attack paths
        acl is_blocked_path path_beg -i -f /etc/haproxy/lists/blocked-paths.lst
    
        # Block path traversal attempts
        acl has_path_traversal path_sub ..
    
        # Block suspiciously long URLs (potential buffer overflow)
        acl url_too_long url_len gt 8192
    
        # Block requests with null bytes
        acl has_null_byte path_sub %00
    
        # Block common SQL injection patterns in query string
        acl sql_injection query -m reg -i (union.*select|insert.*into|drop.*table|;\s*--|/\*.*\*/)
    
        http-request deny deny_status 403 if is_blocked_path
        http-request deny deny_status 400 if has_path_traversal
        http-request deny deny_status 414 if url_too_long
        http-request deny deny_status 400 if has_null_byte
        http-request deny deny_status 403 if sql_injection
    
        # Log denied requests
        http-request set-var(txn.denied) str(1) if is_blocked_path || has_path_traversal || url_too_long || has_null_byte || sql_injection
    
        default_backend web_servers

    10.2 Rate-Limited Endpoint Protection

    frontend https_front
        bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
        mode http
    
        # Sensitive endpoints ACLs
        acl is_login path_beg /login /auth/token
        acl is_signup path_beg /signup /register
    
        # Use stick table to track per-IP rate on login
        stick-table type ipv6 size 100k expire 60s store http_req_rate(60s)
        http-request track-sc0 src if is_login
    
        # Deny if more than 10 login attempts per minute
        acl login_rate_exceeded sc_http_req_rate(0) gt 10
        http-request deny deny_status 429 if is_login login_rate_exceeded
    
        default_backend web_servers

    11. Debugging and Validating ACLs

    11.1 Configuration Validation

    # Always validate before reloading
    sudo haproxy -c -f /etc/haproxy/haproxy.cfg
    
    # Expected output:
    # Configuration file is valid

    11.2 Debug Logging with ACL Variables

    frontend https_front
        bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
        mode http
    
        # Capture headers for logging
        http-request capture req.hdr(Host) len 64
        http-request capture req.hdr(User-Agent) len 128
    
        # Store matched backend name in variable for logging
        acl is_api path_beg /api
        http-request set-var(txn.route) str(api) if is_api
        http-request set-var(txn.route) str(web) unless is_api
    
        # Custom log format showing route decision
        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 route=%[var(txn.route)] %{+Q}r"
    
        use_backend api_servers if is_api
        default_backend web_servers

    11.3 Runtime ACL Testing

    # Show current ACLs in a frontend
    echo "show acl" | socat stdio /var/run/haproxy/admin.sock
    
    # Show map entries
    echo "show map /etc/haproxy/maps/domain-to-backend.map" | socat stdio /var/run/haproxy/admin.sock
    
    # Test a specific lookup against a map
    echo "get map /etc/haproxy/maps/domain-to-backend.map api.example.com" | socat stdio /var/run/haproxy/admin.sock

    11.4 Testing with curl

    # Test path-based routing
    curl -v http://localhost/api/v1/users
    curl -v http://localhost/static/style.css
    
    # Test host-based routing
    curl -v -H "Host: api.example.com" http://localhost/v1/users
    curl -v -H "Host: blog.example.com" http://localhost/
    
    # Test header-based routing
    curl -v -H "X-Canary: true" http://localhost/api/test
    curl -v -H "Content-Type: application/json" -X POST -d '{"test":1}' http://localhost/api/data
    
    # Test cookie-based routing
    curl -v -b "ab_test=variant_b" http://localhost/
    
    # Test denied paths
    curl -v http://localhost/.env
    curl -v http://localhost/../../etc/passwd

    12. Complete Production Configuration

    Here is a comprehensive HAProxy configuration combining all the techniques discussed:

    global
        log /dev/log local0
        log /dev/log local1 notice
        chroot /var/lib/haproxy
        stats socket /var/run/haproxy/admin.sock mode 660 level admin
        stats timeout 30s
        user haproxy
        group haproxy
        daemon
        maxconn 50000
        ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
        ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11
        tune.ssl.default-dh-param 2048
    
    defaults
        log     global
        mode    http
        option  httplog
        option  dontlognull
        option  forwardfor
        option  http-server-close
        timeout connect 5s
        timeout client  30s
        timeout server  30s
        timeout http-request 10s
        timeout http-keep-alive 5s
        errorfile 400 /etc/haproxy/errors/400.http
        errorfile 403 /etc/haproxy/errors/403.http
        errorfile 408 /etc/haproxy/errors/408.http
        errorfile 500 /etc/haproxy/errors/500.http
        errorfile 502 /etc/haproxy/errors/502.http
        errorfile 503 /etc/haproxy/errors/503.http
        errorfile 504 /etc/haproxy/errors/504.http
    
    frontend http_redirect
        bind *:80
        mode http
        acl is_acme path_beg /.well-known/acme-challenge/
        use_backend acme_backend if is_acme
        http-request redirect scheme https code 301 unless is_acme
    
    frontend https_main
        bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
        mode http
    
        # --- Request ID and Forwarding ---
        http-request set-header X-Request-ID %[uuid()]
        http-request set-header X-Forwarded-Proto https
        http-request set-header X-Real-IP %[src]
        http-request del-header Proxy
    
        # --- Security ACLs ---
        acl is_blocked_path path_beg -i -f /etc/haproxy/lists/blocked-paths.lst
        acl has_path_traversal path_sub ..
        acl url_too_long url_len gt 8192
        http-request deny deny_status 403 if is_blocked_path
        http-request deny deny_status 400 if has_path_traversal
        http-request deny deny_status 414 if url_too_long
    
        # --- Host ACLs ---
        acl host_www     hdr(host) -i www.example.com example.com
        acl host_api     hdr(host) -i api.example.com
        acl host_blog    hdr(host) -i blog.example.com
        acl host_admin   hdr(host) -i admin.example.com
    
        # --- Path ACLs ---
        acl is_api_v1    path_beg /api/v1
        acl is_api_v2    path_beg /api/v2
        acl is_static    path_beg /static /assets
        acl is_health    path /health /ready
        acl is_ws        path_beg /ws
    
        # --- Method ACLs ---
        acl is_write     method POST PUT PATCH DELETE
    
        # --- Admin protection ---
        acl is_allowed_admin src -f /etc/haproxy/lists/admin-ips.lst
        http-request deny deny_status 403 if host_admin !is_allowed_admin
    
        # --- Canary header ---
        acl is_canary hdr(X-Canary) -i true
    
        # --- www redirect ---
        acl host_www_prefix hdr_beg(host) -i www.
        http-request redirect prefix https://example.com code 301 if host_www_prefix
    
        # --- Routing rules (order matters: most specific first) ---
        use_backend health_check        if is_health
        use_backend websocket_servers   if host_api is_ws
        use_backend api_v2_write        if host_api is_api_v2 is_write
        use_backend api_v2_canary       if host_api is_api_v2 is_canary
        use_backend api_v2_read         if host_api is_api_v2
        use_backend api_v1_servers      if host_api is_api_v1
        use_backend api_v1_servers      if host_api
        use_backend blog_servers        if host_blog
        use_backend admin_servers       if host_admin
        use_backend static_servers      if is_static
        default_backend web_servers
    
        # --- Response hardening ---
        http-response del-header Server
        http-response del-header X-Powered-By
        http-response set-header X-Content-Type-Options nosniff
        http-response set-header X-Frame-Options SAMEORIGIN
    
    # --- Backends ---
    backend health_check
        mode http
        http-request return status 200 content-type text/plain string "OK"
    
    backend web_servers
        mode http
        balance roundrobin
        option httpchk GET /health
        http-check expect status 200
        server web1 10.0.1.10:80 check inter 5s fall 3 rise 2
        server web2 10.0.1.11:80 check inter 5s fall 3 rise 2
    
    backend api_v1_servers
        mode http
        balance leastconn
        option httpchk GET /api/v1/ping
        http-check expect status 200
        server apiv1a 10.0.2.10:8080 check inter 3s fall 3 rise 2
        server apiv1b 10.0.2.11:8080 check inter 3s fall 3 rise 2
    
    backend api_v2_read
        mode http
        balance roundrobin
        option httpchk GET /api/v2/ping
        http-check expect status 200
        server apiv2r1 10.0.3.10:8080 check inter 3s fall 3 rise 2
        server apiv2r2 10.0.3.11:8080 check inter 3s fall 3 rise 2
        server apiv2r3 10.0.3.12:8080 check inter 3s fall 3 rise 2
    
    backend api_v2_write
        mode http
        balance leastconn
        option httpchk GET /api/v2/ping
        http-check expect status 200
        server apiv2w1 10.0.4.10:8080 check inter 3s fall 3 rise 2
        server apiv2w2 10.0.4.11:8080 check inter 3s fall 3 rise 2
    
    backend api_v2_canary
        mode http
        balance roundrobin
        server canary1 10.0.5.10:8080 check inter 3s fall 3 rise 2
    
    backend blog_servers
        mode http
        balance roundrobin
        server blog1 10.0.6.10:80 check
        server blog2 10.0.6.11:80 check
    
    backend admin_servers
        mode http
        balance roundrobin
        server admin1 10.0.7.10:8443 check ssl verify none
    
    backend static_servers
        mode http
        balance roundrobin
        http-response set-header Cache-Control "public, max-age=31536000, immutable"
        server static1 10.0.8.10:80 check
        server static2 10.0.8.11:80 check
    
    backend websocket_servers
        mode http
        balance source
        timeout tunnel 3600s
        timeout server 3600s
        server ws1 10.0.9.10:8081 check
        server ws2 10.0.9.11:8081 check
    
    backend acme_backend
        mode http
        server acme 127.0.0.1:8888

    13. Performance Considerations

    • ACL evaluation order — HAProxy evaluates
      use_backend
      directives top-to-bottom and stops at the first match. Place the most frequent routes first to minimise evaluation overhead.
    • Map files vs inline ACLs — For more than 20 entries, use
      map
      or
      -f
      file-based ACLs. Map lookups are O(1) hash lookups; long inline ACL value lists are scanned linearly.
    • Regex ACLs
      path_reg
      is significantly slower than
      path_beg
      or
      path_end
      . Avoid regex when a prefix or suffix match suffices.
    • Variable caching — Use
      set-var(txn.xxx)
      to store expensive fetch results and reference them multiple times rather than re-evaluating.
    • Stick table memory — Each stick table entry consumes memory. Size tables appropriately and set
      expire
      values to prevent unbounded growth.

    14. Frequently Asked Questions

    Frequently Asked Questions

    What is the difference between path_beg, path_end, path_sub, and path_reg in HAProxy?

    path_beg matches if the URL path begins with the given string (e.g., path_beg /api matches /api/v1/users). path_end matches the end of the path (e.g., path_end .jpg). path_sub matches if the path contains the substring anywhere. path_reg matches the path against a POSIX regular expression. Use path_beg and path_end when possible as they are significantly faster than path_reg.

    How do I route traffic to different backends based on the Host header in HAProxy?

    Define ACLs using hdr(host) with the -i flag for case-insensitive matching: acl host_api hdr(host) -i api.example.com. Then use use_backend api_servers if host_api. For many domains, use a map file with use_backend %[req.hdr(host),lower,map(/etc/haproxy/maps/domain-backend.map,default_backend)] for cleaner and more scalable configuration.

    Does the order of use_backend directives matter in HAProxy?

    Yes, HAProxy evaluates use_backend directives from top to bottom and uses the first one whose ACL conditions are satisfied. Place more specific rules before general ones. For example, put use_backend api_v2_write if is_api_v2 is_post before use_backend api_v2_read if is_api_v2, otherwise write traffic would match the read rule first.

    How can I strip a URL path prefix before forwarding to the backend?

    Use http-request set-path in the backend section. For example, to strip /api from requests: http-request set-path %[path,regsub(^/api,,)]. This transforms /api/v1/users into /v1/users before the request reaches the backend server.

    What is a map file in HAProxy and when should I use one?

    A map file is an external key-value text file that HAProxy loads into memory as a hash table. Use map files when you have more than a handful of ACL values (e.g., 20+ domains to route, hundreds of path prefixes). Map lookups are O(1) and can be updated at runtime via the HAProxy Runtime API using the 'set map' command without reloading the configuration.

    How do I combine multiple ACL conditions with AND, OR, and NOT in HAProxy?

    AND is implicit when you list multiple ACLs separated by spaces: use_backend X if acl_a acl_b. OR uses the || operator: use_backend X if acl_a || acl_b. NOT uses the ! prefix: http-request deny if is_admin !is_allowed_ip. You can also group conditions for complex logic.

    How do I protect an admin panel path with IP allowlisting in HAProxy?

    Create a file listing allowed CIDRs (one per line), define an ACL for the admin path and another for the source IPs: acl is_admin path_beg /admin and acl is_allowed src -f /etc/haproxy/lists/admin-ips.lst. Then add http-request deny deny_status 403 if is_admin !is_allowed. This returns a 403 to any client not in the allowlist.

    Can I update HAProxy ACL and map entries without reloading the service?

    Yes. HAProxy's Runtime API (via the stats socket) supports dynamic map and ACL management. Use commands like 'set map /path/to/map.map key value', 'del map /path/to/map.map key', and 'show map /path/to/map.map' through socat or the Dataplane API. Note that runtime changes are not persisted to disk — update the file as well for persistence across restarts.

    How do I route WebSocket connections through HAProxy?

    Define an ACL to match the WebSocket path (e.g., acl is_ws path_beg /ws) and route to a dedicated backend. In the backend, set timeout tunnel to a high value (e.g., 3600s) to keep the connection alive, and use balance source for session affinity. HAProxy handles the HTTP Upgrade to WebSocket transparently in HTTP mode.

    How do I implement canary deployments using HAProxy ACLs?

    There are several approaches: (1) Header-based: set acl is_canary hdr(X-Canary) -i true and route matching requests to a canary backend. (2) Percentage-based: use http-request set-var(txn.rand) rand,mod(100) then acl is_canary var(txn.rand) -m int lt 10 for 10% canary traffic. (3) Cookie-based: check for a canary cookie with cook(canary) -m found. All three can be combined for flexible rollout control.

    Related Articles