Core concepts

ACLs

An Access Control List (ACL) examines a statement and returns either true or false. You can use ACLs in many scenarios, including routing traffic, blocking traffic, and transforming messages. An ACL has no effect on your configuration until you reference it with an if or unless condition on another line.

You use ACLs to make a decision in your configuration. For example:

  • Should I route this request to backend A or backend B?
  • Should I redirect this request to another domain?
  • Should I reject this client’s connection?

ACLs allow you to test various conditions and perform actions based on those tests.

ACLs can inspect aspects of a request or response. They can search for strings or patterns, check the client’s IP address, look up recent request rates (via stick tables), inspect for authentication status, etc. The action you take can include making routing decisions, redirecting requests, returning static responses and so much more. While using logic operators (AND, OR, NOT) in other proxy solutions might be cumbersome, ACLs embrace them to form more complex conditions.

Basic ACL syntax Jump to heading

The following ACL, which begins with the acl keyword, returns true if the requested URL path begins with /images/:

haproxy
frontend www
bind :80
acl images_url path -i -m beg /images/
haproxy
frontend www
bind :80
acl images_url path -i -m beg /images/

In this example:

  • The name assigned to the ACL is images_url.
  • The path argument returns the URL path that the client requested. Function like path are called fetch methods.
  • The -i flag performs a case-insensitive match of the requested URL path.
  • The -m beg flag means that the match type is begins with.
  • Note that an ACL on its own performs no action. Later, you will see how to pair it with an action.

In this case, you can also use a shorthand syntax path_beg instead of path:

haproxy
frontend www
bind :80
acl images_url path_beg -i /images/
haproxy
frontend www
bind :80
acl images_url path_beg -i /images/

To specify multiple values to match against, separate each value with a space. The ACL below matches URL paths that begin with /images/ or /photos/:

haproxy
frontend www
bind :80
acl images_url path_beg -i /images/ /photos/
haproxy
frontend www
bind :80
acl images_url path_beg -i /images/ /photos/

When an ACL is evaluated, it always returns true or false. You can then use the ACL on any line that allows a conditional if or unless statement. For instance, to route the request to a specific backend if the requested URL path begins with /images/, place the name of the ACL after an if statement at the end of a use_backend line:

haproxy
frontend www
bind :80
acl images_url path_beg -i /images/
use_backend static_assets if images_url
backend static_assets
server s1 192.168.50.20:80
haproxy
frontend www
bind :80
acl images_url path_beg -i /images/
use_backend static_assets if images_url
backend static_assets
server s1 192.168.50.20:80

Although you can define an ACL on its own by using the acl directive, in which case it can be referenced on another line by name, you can also declare the ACL inline by surrounding the expression with curly braces:

haproxy
frontend www
bind :80
use_backend static_assets if { path_beg -i /images/ }
haproxy
frontend www
bind :80
use_backend static_assets if { path_beg -i /images/ }

Logical operators in conditions Jump to heading

You can combine ACL expressions together to form complex conditions.

AND operator (implied) Jump to heading

To require that multiple conditions are true, list them one after the other. When you do this, a logical AND is implied. Below, we define two ACLs, images_url and is_get. The if statement then means that both of those ACLs must be true to perform the action:

haproxy
frontend www
bind :80
acl images_url path_beg /images/
acl is_get method GET
# The path must begin with /images/ and the method must be GET
use_backend static_assets if images_url is_get
backend static_assets
server s1 192.168.50.20:80
haproxy
frontend www
bind :80
acl images_url path_beg /images/
acl is_get method GET
# The path must begin with /images/ and the method must be GET
use_backend static_assets if images_url is_get
backend static_assets
server s1 192.168.50.20:80

Alternatively, use any of the following syntaxes to denote an AND operator:

  • Write ACL expressions inline with the if statement, surrounding them with curly braces:

    haproxy
    frontend www
    bind :80
    use_backend static_assets if { path_beg /images/ } { method POST }
    backend static_assets
    server s1 192.168.50.20:80
    haproxy
    frontend www
    bind :80
    use_backend static_assets if { path_beg /images/ } { method POST }
    backend static_assets
    server s1 192.168.50.20:80
  • Mix a named ACL, images_url, with an inline ACL expression:

    haproxy
    frontend www
    bind :80
    acl images_url path_beg /images/
    use_backend static_assets if images_url { method POST }
    backend static_assets
    server s1 192.168.50.20:80
    haproxy
    frontend www
    bind :80
    acl images_url path_beg /images/
    use_backend static_assets if images_url { method POST }
    backend static_assets
    server s1 192.168.50.20:80

OR operator Jump to heading

To require that at least one condition is true when several are present, use a || operator. Below, we route the request to the static_assets backend when either the requested URL path begins with /images/ or the URL path ends with .jpg:

haproxy
frontend www
bind :80
acl images_url path_beg /images/
acl is_jpeg path_end .jpg
use_backend static_assets if images_url || is_jpeg
backend static_assets
server s1 192.168.50.20:80
haproxy
frontend www
bind :80
acl images_url path_beg /images/
acl is_jpeg path_end .jpg
use_backend static_assets if images_url || is_jpeg
backend static_assets
server s1 192.168.50.20:80

You can also create an or statement by defining multiple ACLs with the same name. Below, the condition is again true if the requested URL path begins with /images/ or the URL path ends with .jpg:

haproxy
frontend www
bind :80
acl images_url path_beg /images/
acl images_url path_end .jpg
use_backend static_assets if images_url
backend static_assets
server s1 192.168.50.20:80
haproxy
frontend www
bind :80
acl images_url path_beg /images/
acl images_url path_end .jpg
use_backend static_assets if images_url
backend static_assets
server s1 192.168.50.20:80

Negate a condition Jump to heading

To negate a condition, add an exclamation mark in front of it. In the example below, we route to the static_assets backend if the requested URL path begins with /images/ and the method is not POST:

haproxy
frontend www
bind :80
acl images_url path_beg /images/
acl is_post method POST
use_backend static_assets if images_url !is_post
backend static_assets
server s1 192.168.50.20:80
haproxy
frontend www
bind :80
acl images_url path_beg /images/
acl is_post method POST
use_backend static_assets if images_url !is_post
backend static_assets
server s1 192.168.50.20:80

You can also use the unless operator. Below, we use unless to route to the static_assets backend unless the request is for a PHP file:

haproxy
frontend www
bind :80
acl is_php path_end .php
use_backend static_assets unless is_php
backend static_assets
server s1 192.168.50.20:80
haproxy
frontend www
bind :80
acl is_php path_end .php
use_backend static_assets unless is_php
backend static_assets
server s1 192.168.50.20:80

ACL flags Jump to heading

ACLs support the following flags:

-f, load a file Jump to heading

The -f flag loads a file that contains values to match against. It must be followed by the name of a file from which the load balancer reads all lines as individual patterns. It is possible to pass multiple -f arguments if the patterns come from multiple files.

Consider that we have a file named patterns.txt that contains the following lines:

patterns.txt
txt
/images/
/photos/
patterns.txt
txt
/images/
/photos/

Your ACL statement could then check if the requested URL path begins with any of the paths from the file:

haproxy
frontend www
bind :80
acl images_url path -i -m beg -f /patterns.txt
haproxy
frontend www
bind :80
acl images_url path -i -m beg -f /patterns.txt

The format of the file follows these rules:

  • Empty lines will be ignored.
  • Commented lines starting with the pound sign (#) will be ignored.
  • All leading spaces and tabs will be automatically stripped out.
  • If you use -f in conjunction with -m, the -m must come first.

-M, load a map file Jump to heading

The -M flag, used with -f, loads a map file. A map file contains two columns, where the first column is a key and the second is a value. An ACL line reads only the first column, but you can use the second column later, such as by the map converter on an http-request line.

Consider that we have a file named redirects.map that contains the following lines:

redirects.map
txt
docs.test.com www.test.com/docs
blog.test.com www.test.com/blog
redirects.map
txt
docs.test.com www.test.com/docs
blog.test.com www.test.com/blog

The ACL only considers the first column, in the same way as the -f flag with a single-column file. However, the http-request redirect line that follows finds the matching Host header from the key column, and fills in the redirect URL from the value column:

haproxy
frontend www
bind :80
# Does the Host header match a key in the map file?
acl requires_redirect req.hdr(Host) -i -M -f /redirects.map
# Use the correct redirect URL based on the Host header
http-request redirect prefix https://%[req.hdr(Host),lower,map(/redirects.map)] code 301 if requires_redirect
haproxy
frontend www
bind :80
# Does the Host header match a key in the map file?
acl requires_redirect req.hdr(Host) -i -M -f /redirects.map
# Use the correct redirect URL based on the Host header
http-request redirect prefix https://%[req.hdr(Host),lower,map(/redirects.map)] code 301 if requires_redirect

-u, set a unique ID Jump to heading

The -u flag lets you set the integer ID for the ACL, which otherwise is set automatically.

haproxy
frontend www
bind :80
acl images_url path_beg -i -u 50 /images/
haproxy
frontend www
bind :80
acl images_url path_beg -i -u 50 /images/

-m, set the match type Jump to heading

The -m flag sets a specific match type to use when comparing the ACL against the input sample.

All fetches imply a matching type and generally do not need this flag. However, it is useful with generic fetches to make the match type explicit or to override the default match type.

  • If you use -f in conjunction with -m, the -m must come first.
  • Not all match types can work with all fetch methods.

The match type must be one of the following:

Type Description
found Only checks for the existence of the requested sample in the stream. For example, use this to check whether a URL parameter exists, without concern for its value.
bool Matches the sample as a Boolean. This method only applies to fetches that return a boolean or integer value. Value zero or false does not match, all other values match.
int Matches the sample as an integer. It can apply to integer and boolean samples. Boolean false is integer 0, true is integer 1.
ip Matches the sample as an IPv4 or IPv6 address. It is compatible with IP address samples only.
bin Matches the sample against a hexadecimal string representing a binary sequence. It can apply to binary or string samples.
len Matches the sample’s length as an integer. It can apply to binary or string samples.
str Exact string match. It can apply to binary or string samples.
sub Substring match: checks that the sample contains at least one of the provided string patterns. It can apply to binary or string samples.
reg Regex match: matches the sample against a list of regular expressions. This can work with binary or string samples.
beg Prefix match: checks that the sample begins like any of the provided patterns. It can apply to binary or string samples.
end Suffix match: checks that the sample finishes like any of the provided patterns. It can apply to binary or string samples.
dir Subdir match: checks that a slash-delimited portion of the sample exactly matches one of the provided patterns. It can apply to binary or string samples.
dom Domain match: checks that a dot-delimited portion of the sample exactly matches one of the provided patterns. It can apply to binary or string samples.

-n, disable DNS resolution Jump to heading

The -n flag, used with -f, disables DNS resolution when loading IP addresses from a file.

When the parser can not parse an IP address, it considers that the parsed string is a domain name and tries to resolve it using DNS. If the DNS server is not reachable, then parsing the configuration can take several minutes while waiting for DNS to timeout. During this time, no error messages display. Therefore, this flag avoids this scenario entirely.

An IP address file, safelist.txt, contains a line with a domain name, example.com:

safelist.txt
text
192.168.0.10
example.com
safelist.txt
text
192.168.0.10
example.com

Configure the ACL to use the -n flag:

haproxy
frontend www
bind :80
acl safe_ip src -n -f /safelist.txt
tcp-request content reject unless safe_ip
haproxy
frontend www
bind :80
acl safe_ip src -n -f /safelist.txt
tcp-request content reject unless safe_ip

When reloading the configuration, the following error displays:

output
bash
error detected while parsing ACL 'safe_ip' : 'example.com' is not a valid IPv4 or IPv6 address at line 2
output
bash
error detected while parsing ACL 'safe_ip' : 'example.com' is not a valid IPv4 or IPv6 address at line 2

In this way, only IP addresses are allowed.

Special matching Jump to heading

Some ACL expressions go beyond the simple matching rules described so far. For example, you can try to match a range of integers or a range of IP addresses.

Match integer ranges Jump to heading

To express a range of integers, set lower and upper bound numbers separated by a colon. Below, we test whether the response status code from the server is between 500 and 511:

haproxy
frontend www
bind :80
acl is_5xx status 500:511
haproxy
frontend www
bind :80
acl is_5xx status 500:511

When one of the bounds is missing, it indicates that the range has either no start or no end.

Below, we test for destination ports 1024 and higher:

haproxy
frontend www
bind *:80
acl high_port dst_port 1024:
haproxy
frontend www
bind *:80
acl high_port dst_port 1024:

Next, we test for destination ports 1023 and lower:

haproxy
frontend www
bind *:80
acl low_port dst_port :1023
haproxy
frontend www
bind *:80
acl low_port dst_port :1023

Match integer operators Jump to heading

To compare two integers, use the comparison operators. Available operators for integer matching are:

Operator Description
eq True if the sample equals at least one pattern.
ge True if the sample is greater than or equal to at least one pattern.
gt True if the sample is greater than at least one pattern.
le True if the sample is less than or equal to at least one pattern.
lt True if the sample is less than at least one pattern.

In the example below, we test whether the HTTP response body is greater than 10000 bytes:

haproxy
frontend www
bind :80
acl 10kb_response res.body_len gt 10000
haproxy
frontend www
bind :80
acl 10kb_response res.body_len gt 10000

Match decimal numbers Jump to heading

Some fetch methods return decimal numbers, which are two integers separated by a period. In the example below, the two ACLs, tlsv1 and ssl3_or_tlsv1 call the req.ssl_ver fetch method, which returns a decimal number that indicates the version of SSL/TLS used. We then compare that value with the literal value 3.1 and the range 3:3.1:

haproxy
frontend www
bind :80
# Match TLV v1.0
acl tlsv1 req.ssl_ver 3.1
# Match SSL 3.0 or TLS 1.0
acl ssl3_or_tlsv1 req.ssl_ver 3:3.1
haproxy
frontend www
bind :80
# Match TLV v1.0
acl tlsv1 req.ssl_ver 3.1
# Match SSL 3.0 or TLS 1.0
acl ssl3_or_tlsv1 req.ssl_ver 3:3.1

All integer properties apply to decimal numbers, including ranges and operators.

Match strings Jump to heading

To compare the result of a fetch method to a string, use the -m flag to indicate the match type. String matching applies to string and binary fetch methods and converters.

Type Description
-m str Matches the string exactly.
-m sub Matches a portion of the string.
-m beg Matches the beginning of the string.
-m end Matches the end of the string.
-m dir Matches part of a URL or file path, delimited with forward slashes.
-m dom Matches part of a domain, delimited with periods.
  • String matching applies verbatim to strings as they pass, with the exception of the backslash (\). This enables you to avoid characters such as the space.
  • When the flag -i passes before the first string, the matching is case-insensitive.
  • To match the pattern -i, you can either set it after, or pass the specific flag -- before the first pattern. The same applies to match the pattern -.

Match regular expressions Jump to heading

Use the -m reg match type to compare a fetch method’s returned value with a regular expression. You can escape backslashes by prefixing them with another backslash \\.

Below, we check whether the URL path matches the regular expression ca+t (matches cat, caat, caaat, etc.).

haproxy
frontend www
bind :80
acl contains_cat path -m reg ca+t
haproxy
frontend www
bind :80
acl contains_cat path -m reg ca+t
  • When the flag -i is passed before the first regex, the matching is case-insensitive.
  • To match the literal -i, you can either set it after, or pass the specific flag -- before the first pattern. The same applies to match the pattern -.

Match arbitrary data blocks Jump to heading

To match samples against a binary block when you cannot safely represent it as a string, use the match type -m bin. To do this, the patterns must be passed as a series of hexadecimal digits in an even number. Each sequence of two digits represents a byte. The hexadecimal digits can be in either upper or lowercase.

haproxy
# Match the string Hello at the beginning of the input stream
# (Hexadecimal values: x48 x65 x6c x6c x6f x0a)
acl hello payload(0,6) -m bin 48656c6c6f0a
haproxy
# Match the string Hello at the beginning of the input stream
# (Hexadecimal values: x48 x65 x6c x6c x6f x0a)
acl hello payload(0,6) -m bin 48656c6c6f0a

Match IPv4 and IPv6 addresses Jump to heading

To match against IPv4 and IPv6 addresses with or without an appended netmask, use the usual form. When you use a netmask, the address matches whenever it is within the network. Only bit counts are accepted for IPv6 netmasks.

haproxy
frontend www
bind :80
# Is the client's IP address localhost?
acl is_localhost src 127.0.0.1
# Is the client's IP address in the IPv4 range?
acl allowed_ipv4 src 192.168.0.0/16
# Is the client's IP address in the given IPv6 range?
acl allowed_ipv6 src 2001:db8::/48
haproxy
frontend www
bind :80
# Is the client's IP address localhost?
acl is_localhost src 127.0.0.1
# Is the client's IP address in the IPv4 range?
acl allowed_ipv4 src 192.168.0.0/16
# Is the client's IP address in the given IPv6 range?
acl allowed_ipv6 src 2001:db8::/48

If the input you’re trying to match isn’t already an IP address, such as what the src fetch returns, but is instead a string, then you can explicitly cast it to an IP address by using the -m ip flag. For example:

haproxy
frontend www
bind :80
http-request set-var(txn.myip) str(127.0.0.1)
acl is_localhost var(txn.myip) -m ip 127.0.0.0/8
http-request deny if is_localhost
haproxy
frontend www
bind :80
http-request set-var(txn.myip) str(127.0.0.1)
acl is_localhost var(txn.myip) -m ip 127.0.0.0/8
http-request deny if is_localhost

In this example:

  • we set the variable txn.myip to a string value of 127.0.0.1
  • we use the -m ip flag in our acl to cast the contents of the txn.myip variable to an IP address and compare it against the IP address with netmask 127.0.0.0/8.

Because this will result in a match, the request will be denied. Note that for this example, we are setting the txn.myip variable manually via str() with a hard-coded value, but your IP addresses (as strings) you are wanting to match might come from some other source or from a file.

ACL examples Jump to heading

Below are some common use-case examples that can be applied with ACLs.

Redirect a request Jump to heading

The example below redirects requests to the www subdomain. For example, it redirects example.com to www.example.com. Here, the ACL hdr_beg(host) -i www will ensure that the client gets redirected unless their Host HTTP header already begins with www:

haproxy
frontend example
bind :80
http-request redirect location http://www.%[hdr(host)]%[capture.req.uri] unless { hdr_beg(host) -i www }
haproxy
frontend example
bind :80
http-request redirect location http://www.%[hdr(host)]%[capture.req.uri] unless { hdr_beg(host) -i www }

In the next example, the command http-request redirect scheme changes the scheme of the request while leaving the rest alone. This allows for trivial HTTP-to-HTTPS redirect lines. The ACL !{ ssl_fc } checks whether the request did not come in over HTTPS.

haproxy
frontend example
bind :80
http-request redirect scheme https if !{ ssl_fc }
haproxy
frontend example
bind :80
http-request redirect scheme https if !{ ssl_fc }

Cache a response Jump to heading

Small object caching enables the caching of resources according to ACLs. To illustrate, suppose we have a cache named icons. The following sample configuration will cache responses from paths starting with /icons/ and utilize them for subsequent requests:

haproxy
frontend example
bind :80
http-request set-var(txn.path) path
acl is_icons_path var(txn.path) -m beg /icons/
http-request cache-use icons if is_icons_path
http-response cache-store icons if is_icons_path
haproxy
frontend example
bind :80
http-request set-var(txn.path) path
acl is_icons_path var(txn.path) -m beg /icons/
http-request cache-use icons if is_icons_path
http-response cache-store icons if is_icons_path

In this example:

  • The http-request cache-use directive specifies that requests matching the is_icons_path ACL condition will be served from the cache.
  • The http-response cache-store directive indicates that responses matching the is_icons_path ACL condition should be stored in the cache.

Variable scope

txn.path is a variable that uses the transaction scope, making it available to both the http-request and http-response directives, which run in the request and response phases, respectively.

Block requests Jump to heading

The http-request deny command returns an error response to the client and immediately terminates the request processing. This feature is commonly utilized for DDoS/Bot mitigation, as the load balancer can efficiently handle a significant number of requests without impacting the web server.

Consider the following examples that deny a request when an ACL evaluates to true:

  • Inspect the user-agent header and deny if it matches a specified string:

    haproxy
    frontend example
    bind :80
    http-request deny if { req.hdr(user-agent) -m sub evil }
    haproxy
    frontend example
    bind :80
    http-request deny if { req.hdr(user-agent) -m sub evil }
  • Inspect the length of the user-agent header and deny if it is exactly 32 characters long. Attackers may try to evade detection by utilizing a random MD5 checksum as their user-agent string, which has a predictable length of 32 characters. However, such attempts can be identified and promptly blocked based on length:

    haproxy
    frontend example
    bind :80
    http-request deny if { req.hdr(user-agent) -m len 32 }
    haproxy
    frontend example
    bind :80
    http-request deny if { req.hdr(user-agent) -m len 32 }
  • Similarly, you can rely on the fact that legitimate user agent strings are usually longer than 32 characters. The following ACL will block any requests that have a user-agent header less than or equal to 32 characters:

    haproxy
    frontend example
    bind :80
    http-request deny if { req.hdr(user-agent) -m len le 32 }
    haproxy
    frontend example
    bind :80
    http-request deny if { req.hdr(user-agent) -m len le 32 }
  • Attackers often attempt to access administrative areas of your website. You can block based on path. For example, if your application does not use WordPress, you could block all requests that target WordPress:

    haproxy
    frontend example
    bind :80
    http-request deny if { path_beg /wp-admin/ }
    haproxy
    frontend example
    bind :80
    http-request deny if { path_beg /wp-admin/ }
  • You can also prevent an attacker from accessing hidden files or folders, such as the .htaccess file, by denying requests where the path has the substring /..

    haproxy
    frontend example
    bind :80
    http-request deny if { path -m sub /. }
    haproxy
    frontend example
    bind :80
    http-request deny if { path -m sub /. }

See also Jump to heading

If this page was useful, please, Leave the feedback.