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 wwwbind :80acl images_url path -i -m beg /images/
haproxy
frontend wwwbind :80acl 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 likepath
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 wwwbind :80acl images_url path_beg -i /images/
haproxy
frontend wwwbind :80acl 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 wwwbind :80acl images_url path_beg -i /images/ /photos/
haproxy
frontend wwwbind :80acl 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 wwwbind :80acl images_url path_beg -i /images/use_backend static_assets if images_urlbackend static_assetsserver s1 192.168.50.20:80
haproxy
frontend wwwbind :80acl images_url path_beg -i /images/use_backend static_assets if images_urlbackend static_assetsserver 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 wwwbind :80use_backend static_assets if { path_beg -i /images/ }
haproxy
frontend wwwbind :80use_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 wwwbind :80acl images_url path_beg /images/acl is_get method GET# The path must begin with /images/ and the method must be GETuse_backend static_assets if images_url is_getbackend static_assetsserver s1 192.168.50.20:80
haproxy
frontend wwwbind :80acl images_url path_beg /images/acl is_get method GET# The path must begin with /images/ and the method must be GETuse_backend static_assets if images_url is_getbackend static_assetsserver 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:haproxyfrontend wwwbind :80use_backend static_assets if { path_beg /images/ } { method POST }backend static_assetsserver s1 192.168.50.20:80haproxyfrontend wwwbind :80use_backend static_assets if { path_beg /images/ } { method POST }backend static_assetsserver s1 192.168.50.20:80 -
Mix a named ACL,
images_url
, with an inline ACL expression:haproxyfrontend wwwbind :80acl images_url path_beg /images/use_backend static_assets if images_url { method POST }backend static_assetsserver s1 192.168.50.20:80haproxyfrontend wwwbind :80acl images_url path_beg /images/use_backend static_assets if images_url { method POST }backend static_assetsserver 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 wwwbind :80acl images_url path_beg /images/acl is_jpeg path_end .jpguse_backend static_assets if images_url || is_jpegbackend static_assetsserver s1 192.168.50.20:80
haproxy
frontend wwwbind :80acl images_url path_beg /images/acl is_jpeg path_end .jpguse_backend static_assets if images_url || is_jpegbackend static_assetsserver 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 wwwbind :80acl images_url path_beg /images/acl images_url path_end .jpguse_backend static_assets if images_urlbackend static_assetsserver s1 192.168.50.20:80
haproxy
frontend wwwbind :80acl images_url path_beg /images/acl images_url path_end .jpguse_backend static_assets if images_urlbackend static_assetsserver 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 wwwbind :80acl images_url path_beg /images/acl is_post method POSTuse_backend static_assets if images_url !is_postbackend static_assetsserver s1 192.168.50.20:80
haproxy
frontend wwwbind :80acl images_url path_beg /images/acl is_post method POSTuse_backend static_assets if images_url !is_postbackend static_assetsserver 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 wwwbind :80acl is_php path_end .phpuse_backend static_assets unless is_phpbackend static_assetsserver s1 192.168.50.20:80
haproxy
frontend wwwbind :80acl is_php path_end .phpuse_backend static_assets unless is_phpbackend static_assetsserver 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.txttxt
/images//photos/
patterns.txttxt
/images//photos/
Your ACL statement could then check if the requested URL path begins with any of the paths from the file:
haproxy
frontend wwwbind :80acl images_url path -i -m beg -f /patterns.txt
haproxy
frontend wwwbind :80acl 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.maptxt
docs.test.com www.test.com/docsblog.test.com www.test.com/blog
redirects.maptxt
docs.test.com www.test.com/docsblog.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 wwwbind :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 headerhttp-request redirect prefix https://%[req.hdr(Host),lower,map(/redirects.map)] code 301 if requires_redirect
haproxy
frontend wwwbind :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 headerhttp-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 wwwbind :80acl images_url path_beg -i -u 50 /images/
haproxy
frontend wwwbind :80acl 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.txttext
192.168.0.10example.com
safelist.txttext
192.168.0.10example.com
Configure the ACL to use the -n
flag:
haproxy
frontend wwwbind :80acl safe_ip src -n -f /safelist.txttcp-request content reject unless safe_ip
haproxy
frontend wwwbind :80acl safe_ip src -n -f /safelist.txttcp-request content reject unless safe_ip
When reloading the configuration, the following error displays:
outputtext
error detected while parsing ACL 'safe_ip' : 'example.com' is not a valid IPv4 or IPv6 address at line 2
outputtext
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 wwwbind :80acl is_5xx status 500:511
haproxy
frontend wwwbind :80acl 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 wwwbind *:80acl high_port dst_port 1024:
haproxy
frontend wwwbind *:80acl high_port dst_port 1024:
Next, we test for destination ports 1023 and lower:
haproxy
frontend wwwbind *:80acl low_port dst_port :1023
haproxy
frontend wwwbind *:80acl 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 wwwbind :80acl 10kb_response res.body_len gt 10000
haproxy
frontend wwwbind :80acl 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 wwwbind :80# Match TLV v1.0acl tlsv1 req.ssl_ver 3.1# Match SSL 3.0 or TLS 1.0acl ssl3_or_tlsv1 req.ssl_ver 3:3.1
haproxy
frontend wwwbind :80# Match TLV v1.0acl tlsv1 req.ssl_ver 3.1# Match SSL 3.0 or TLS 1.0acl 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 wwwbind :80acl contains_cat path -m reg ca+t
haproxy
frontend wwwbind :80acl 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 wwwbind :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 wwwbind :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 wwwbind :80http-request set-var(txn.myip) str(127.0.0.1)acl is_localhost var(txn.myip) -m ip 127.0.0.0/8http-request deny if is_localhost
haproxy
frontend wwwbind :80http-request set-var(txn.myip) str(127.0.0.1)acl is_localhost var(txn.myip) -m ip 127.0.0.0/8http-request deny if is_localhost
In this example:
- we set the variable
txn.myip
to a string value of127.0.0.1
- we use the
-m ip
flag in our acl to cast the contents of thetxn.myip
variable to an IP address and compare it against the IP address with netmask127.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 examplebind :80http-request redirect location http://www.%[hdr(host)]%[capture.req.uri] unless { hdr_beg(host) -i www }
haproxy
frontend examplebind :80http-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 examplebind :80http-request redirect scheme https if !{ ssl_fc }
haproxy
frontend examplebind :80http-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 examplebind :80http-request set-var(txn.path) pathacl is_icons_path var(txn.path) -m beg /icons/http-request cache-use icons if is_icons_pathhttp-response cache-store icons if is_icons_path
haproxy
frontend examplebind :80http-request set-var(txn.path) pathacl is_icons_path var(txn.path) -m beg /icons/http-request cache-use icons if is_icons_pathhttp-response cache-store icons if is_icons_path
In this example:
- The
http-request cache-use
directive specifies that requests matching theis_icons_path
ACL condition will be served from the cache. - The
http-response cache-store
directive indicates that responses matching theis_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:haproxyfrontend examplebind :80http-request deny if { req.hdr(user-agent) -m sub evil }haproxyfrontend examplebind :80http-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 theiruser-agent
string, which has a predictable length of 32 characters. However, such attempts can be identified and promptly blocked based on length:haproxyfrontend examplebind :80http-request deny if { req.hdr(user-agent) -m len 32 }haproxyfrontend examplebind :80http-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:haproxyfrontend examplebind :80http-request deny if { req.hdr(user-agent) -m len le 32 }haproxyfrontend examplebind :80http-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:
haproxyfrontend examplebind :80http-request deny if { path_beg /wp-admin/ }haproxyfrontend examplebind :80http-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/.
.haproxyfrontend examplebind :80http-request deny if { path -m sub /. }haproxyfrontend examplebind :80http-request deny if { path -m sub /. }
See also Jump to heading
Do you have any suggestions on how we can improve the content of this page?