# 2. Configuring HAProxy

> File syntax, quoting, variables, conditions, time and size formats, addresses, and examples

---

LLMS index: [llms.txt](/llms.txt)

---

<!-- Generated by scripts/generate-haproxy-docs.py from pinned upstream text. -->

## 2.1. Configuration file format {#section-2-1}

HAProxy's configuration process involves 3 major sources of parameters:

- the arguments from the command-line, which always take precedence
- the configuration file(s), whose format is described here
- the running process's environment, in case some environment variables are explicitly referenced

The configuration file follows a fairly simple hierarchical format which obey a few basic rules:

```text
1. a configuration file is an ordered sequence of statements

2. a statement is a single non-empty line before any unprotected "#" (hash)

3. a line is a series of tokens or "words" delimited by unprotected spaces or
   tab characters

4. the first word or sequence of words of a line is one of the keywords or
   keyword sequences listed in this document

5. all other words are all arguments of the first one, some being well-known
   keywords listed in this document, others being values, references to other
   parts of the configuration, or expressions

6. certain keywords delimit a section inside which only a subset of keywords
   are supported

7. a section ends at the end of a file or on a special keyword starting a new
   section
```

This is all that is needed to know to write a simple but reliable configuration generator, but this
is not enough to reliably parse any configuration nor to figure how to deal with certain corner
cases.

First, there are a few consequences of the rules above. Rule 6 and 7 imply that the keywords used to
define a new section are valid everywhere and cannot have a different meaning in a specific section.
These keywords are always a single word (as opposed to a sequence of words), and traditionally the
section that follows them is designated using the same name. For example when speaking about the
"global section", it designates the section of configuration that follows the "global" keyword. This
usage is used a lot in error messages to help locate the parts that need to be addressed.

A number of sections create an internal object or configuration space, which requires to be
distinguished from other ones. In this case they will take an extra word which will set the name of
this particular section. For some of them the section name is mandatory. For example "frontend foo"
will create a new section of type "frontend" named "foo". Usually a name is specific to its section
and two sections of different types may use the same name, but this is not recommended as it tends
to complexify configuration management.

A direct consequence of rule 7 is that when multiple files are read at once, each of them must start
with a new section, and the end of each file will end a section. A file cannot contain sub-sections
nor end an existing section and start a new one.

Rule 1 mentioned that ordering matters. Indeed, some keywords create directives that can be repeated
multiple times to create ordered sequences of rules to be applied in a certain order. For example
"tcp-request" can be used to alternate "accept" and "reject" rules on varying criteria. As such, a
configuration file processor must always preserve a section's ordering when editing a file. The
ordering of sections usually does not matter except for the global section which must be placed
before other sections, but it may be repeated if needed. In addition, some automatic identifiers may
automatically be assigned to some of the created objects (e.g. proxies), and by reordering sections,
their identifiers will change. These ones appear in the statistics for example. As such, the
configuration below will assign "foo" an ID number smaller than its "bar" counterpart. This will be
swapped if the two sections are reversed:

```text
listen foo
    bind:80

listen bar
    bind:81
```

Another important point is that according to rules 2 and 3 above, empty lines, spaces, tabs, and
comments following and unprotected "#" character are not part of the configuration as they are just
used as delimiters. This implies that the following configurations are strictly equivalent:

```text
    global#this is the global section
daemon#daemonize
    frontend         foo
mode             http   # or tcp
```

and:

```shell
global
    daemon

# this is the public web frontend
frontend foo
    mode http
```

The common practice is to align to the left only the keyword that initiates a new section, and
indent (i.e. prepend a tab character or a few spaces) all other keywords so that it's instantly
visible that they belong to the same section (as done in the second example above). Placing comments
before a new section helps the reader decide if it's the desired one. Leaving a blank line at the
end of a section also visually helps spotting the end when editing it.

Tabs are very convenient for indent but they do not copy-paste well. If spaces are used instead, it
is recommended to avoid placing too many (2 to 4) so that editing in field doesn't become a burden
with limited editors that do not support automatic indent.

In the early days it used to be common to see arguments split at fixed tab positions because most
keywords would not take more than two arguments. With modern versions featuring complex expressions
this practice does not stand anymore, and is not recommended.

## 2.2. Quoting and escaping {#section-2-2}

In modern configurations, some arguments require the use of some characters that were previously
considered as pure delimiters. In order to make this possible, HAProxy supports character escaping
by prepending a backslash ('&#92;') in front of the character to be escaped,
weak quoting with double quotes ("") around a piece of text, and strong quoting with single quotes
('') around a piece of text.

This is pretty similar to what is done in a number of programming languages and very close to what
is commonly encountered in Bourne shell. The principle is the following: while the configuration
parser cuts the lines into words, it also takes care of quotes and backslashes to decide whether a
character is a delimiter or is the raw representation of this character within the current word. The
escape character is then removed, the quotes are removed, and the remaining word is used as-is as a
keyword or argument for example.

If a backslash is needed in a word, it must either be escaped using itself (i.e. double backslash)
or be strongly quoted.

Escaping outside quotes is achieved by preceding a special character by a backslash
('&#92;'):

```text
\    to mark a space and differentiate it from a delimiter
\#   to mark a hash and differentiate it from a comment
\\   to use a backslash
\'   to use a single quote and differentiate it from strong quoting
\"   to use a double quote and differentiate it from weak quoting
```

In addition, a few non-printable characters may be emitted using their usual C-language
representation:

```text
\n   to insert a line feed (LF, character \x0a or ASCII 10 decimal)
\r   to insert a carriage return (CR, character \x0d or ASCII 13 decimal)
\t   to insert a tab (character \x09 or ASCII 9 decimal)
\xNN to insert character having ASCII code hex NN (e.g \x0a for LF).
```

Weak quoting is achieved by surrounding double quotes ("") around the character or sequence of
characters to protect. Weak quoting prevents the interpretation of:

```text
     space or tab as a word separator
'    single quote as a strong quoting delimiter
```

```haproxy
#    hash as a comment start
```

Weak quoting permits the interpretation of environment variables (which are not evaluated outside of
quotes) by preceding them with a dollar sign ('\$'). If a dollar character is needed inside double
quotes, it must be escaped using a backslash.

Strong quoting is achieved by surrounding single quotes ('') around the character or sequence of
characters to protect. Inside single quotes, nothing is interpreted, it's the efficient way to quote
regular expressions.

As a result, here is the matrix indicating how special characters can be entered in different
contexts (unprintable characters are replaced with their name within angle brackets). Note that some
characters that may only be represented escaped have no possible representation inside single
quotes, hence its absence there:

```text
  Character  |  Unquoted     |  Weakly quoted              |  Strongly quoted
  -----------+---------------+-----------------------------+-----------------
    <TAB>    |  \<TAB>, \x09 |  "<TAB>", "\<TAB>", "\x09"  |  '<TAB>'
  -----------+---------------+-----------------------------+-----------------
    <LF>     |  \n, \x0a     |  "\n", "\x0a"               |
  -----------+---------------+-----------------------------+-----------------
    <CR>     |  \r, \x0d     |  "\r", "\x0d"               |
  -----------+---------------+-----------------------------+-----------------
    <SPC>    |  \<SPC>, \x20 |  "<SPC>", "\<SPC>", "\x20"  |  '<SPC>'
  -----------+---------------+-----------------------------+-----------------
    "        |  \", \x22     |  "\"", "\x22"               |  '"'
  -----------+---------------+-----------------------------+-----------------
    #        |  \#, \x23     |  "#", "\#", "\x23"          |  '#'
  -----------+---------------+-----------------------------+-----------------
    $        |  $, \$, \x24  |  "\$", "\x24"               |  '$'
  -----------+---------------+-----------------------------+-----------------
    '        |  \', \x27     |  "'", "\'", "\x27"          |
  -----------+---------------+-----------------------------+-----------------
    \        |  \\, \x5c     |  "\\", "\x5c"               |  '\'
  -----------+---------------+-----------------------------+-----------------
```

Example:

```shell
# those are all strictly equivalent:
log-format %{+Q}o\ %t\ %s\ %{-Q}r
log-format "%{+Q}o %t %s %{-Q}r"
log-format '%{+Q}o %t %s %{-Q}r'
log-format "%{+Q}o %t"' %s %{-Q}r'
log-format "%{+Q}o %t"' %s'\ %{-Q}r
```

There is one particular case where a second level of quoting or escaping may be necessary. Some
keywords take arguments within parenthesis, sometimes delimited by commas. These arguments are
commonly integers or predefined words, but when they are arbitrary strings, it may be required to
perform a separate level of escaping to disambiguate the characters that belong to the argument from
the characters that are used to delimit the arguments themselves. A pretty common case is the
"regsub" converter. It takes a regular expression in argument, and if a closing parenthesis is
needed inside, this one will require to have its own quotes.

The keyword argument parser is exactly the same as the top-level one regarding quotes, except that
the &#92;#, &#92;\$, and &#92;xNN
escapes are not processed. But what is not always obvious is that the delimiters used inside must
first be escaped or quoted so that they are not resolved at the top level.

Let's take this example making use of the "regsub" converter which takes 3 arguments, one regular
expression, one replacement string and one set of flags:

```shell
# replace all occurrences of "foo" with "blah" in the path:
http-request set-path %[path,regsub(foo,blah,g)]
```

Here no special quoting was necessary. But if now we want to replace either "foo" or "bar" with
"blah", we'll need the regular expression "(foo\|bar)". We cannot write:

```text
http-request set-path %[path,regsub((foo|bar),blah,g)]
```

because we would like the string to cut like this:

```text
    http-request set-path %[path,regsub((foo|bar),blah,g)]
                                       |---------|----|-|
                                 arg1 _/         /    /
                                 arg2 __________/    /
                                 arg3 ______________/
```

but actually what is passed is a string between the opening and closing parenthesis then garbage:

```text
    http-request set-path %[path,regsub((foo|bar),blah,g)]
                                       |--------|--------|
                        arg1=(foo|bar _/        /
                    trailing garbage  _________/
```

The obvious solution here seems to be that the closing parenthesis needs to be quoted, but alone
this will not work, because as mentioned above, quotes are processed by the top-level parser which
will resolve them before processing this word:

```text
http-request set-path %[path,regsub("(foo|bar)",blah,g)]
------------ -------- ----------------------------------
   word1       word2    word3=%[path,regsub((foo|bar),blah,g)]
```

So we didn't change anything for the argument parser at the second level which still sees a
truncated regular expression as the only argument, and garbage at the end of the string. By escaping
the quotes they will be passed unmodified to the second level:

```text
    http-request set-path %[path,regsub(\"(foo|bar)\",blah,g)]
    ------------ -------- ------------------------------------
       word1       word2    word3=%[path,regsub("(foo|bar)",blah,g)]
                                                |---------||----|-|
                                arg1=(foo|bar) _/          /    /
                                    arg2=blah  ___________/    /
                                        arg3=g _______________/
```

Another approach consists in using single quotes outside the whole string and double quotes inside
(so that the double quotes are not stripped again):

```text
    http-request set-path '%[path,regsub("(foo|bar)",blah,g)]'
    ------------ --------  ----------------------------------
       word1       word2    word3=%[path,regsub("(foo|bar)",blah,g)]
                                                |---------||----|-|
                                arg1=(foo|bar) _/          /    /
                                          arg2 ___________/    /
                                          arg3 _______________/
```

But in this case it's important to note that delimiters embedded into the higher level string remain
pure characters and are not delimiters anymore. It particularly means that spaces and tabs around
commas are part of the string. The example below is wrong on multiple points:

```text
    http-request set-path '%[path, regsub("(foo|bar)", blah, g)]'
    ------------ --------  --------------------------------------
       word1       word2    word3=%[path, regsub("(foo|bar)", blah, g)]
                                        |--------|---------||-----|--|
                       converter=" regsub" _/        /         /   /
                                    arg1=(foo|bar) _/         /   /
                                     arg2=" blah" ___________/   /
                                        arg3=" g" ______________/
```

The single fact of surrounding commas with spaces resulted in the spaces being part of the field
itself, hence the converter " regsub" (starting with a space), which won't be found and will trigger
an error, but more subtly, the replacement string " blah" will insert a space in the output. A good
rule of thumb is to never insert unneeded spaces inside expressions.

When using regular expressions, it can happen that the dollar ('\$') character appears in the
expression or that a backslash ('&#92;') is used in the replacement string. In
this case these ones will also be processed inside the double quotes thus single quotes are
preferred (or double escaping). Example:

```text
    http-request set-path '%[path,regsub("^/(here)(/|$)","my/\1",g)]'
    ------------ --------  -----------------------------------------
       word1       word2    word3=%[path,regsub("^/(here)(/|$)","my/\1",g)]
                                                |-------------| |-----||-|
                              arg1=(here)(/|$) _/               /      /
                                    arg2=my/\1 ________________/      /
                                          arg3 ______________________/
```

Remember that backslashes are not escape characters within single quotes and that the whole word
above is already protected against them using the single quotes. Conversely, if double quotes had
been used around the whole expression, single the dollar character and the backslashes would have
been resolved at top level, breaking the argument contents at the second level.

Unfortunately, since single quotes can't be escaped inside of strong quoting, if you need to include
single quotes in your argument, you will need to escape or quote them twice. There are a few ways to
do this:

```text
http-request set-var(txn.foo) str("\\'foo\\'")
http-request set-var(txn.foo) str(\"\'foo\'\")
http-request set-var(txn.foo) str(\\\'foo\\\')
```

When in doubt, simply do not use quotes anywhere, and start to place single or double quotes around
arguments that require a comma or a closing parenthesis, and think about escaping these quotes using
a backslash if the string contains a dollar or a backslash. Again, this is pretty similar to what is
used under a Bourne shell when double-escaping a command passed to "eval". For API writers the best
is probably to place escaped quotes around each and every argument, regardless of their contents.
Users will probably find that using single quotes around the whole expression and double quotes
around each argument provides more readable configurations.

## 2.3. Environment variables {#section-2-3}

HAProxy's configuration supports environment variables. Those variables are interpreted only within
double quotes. Variables are expanded during the configuration parsing. Variable names must be
preceded by a dollar ("\$") and optionally enclosed with braces ("{}") similarly to what is done in
Bourne shell. Variable names can contain alphanumerical characters or the character underscore
("\_") but should not start with a digit. If the variable contains a list of several values
separated by spaces, it can be expanded as individual arguments by enclosing the variable with
braces and appending the suffix '[\*]' before the closing brace. It is also possible to specify a
default value to use when the variable is not set, by appending that value after a dash '-' next to
the variable name. Note that the default value only replaces non existing variables, not empty ones.

Example:

```text
bind "fd@${FD_APP1}"

log "${LOCAL_SYSLOG-127.0.0.1}:514" local0 notice  # send to local server

user "$HAPROXY_USER"
```

Some variables are defined by HAProxy, they can be used in the configuration file. These variables
are listed in the matrix below, and they are classified among four categories:

- usable: the variable is accessible from the configuration, either to be resolved as-is, or used
  within conditional blocks or predicates to enable or disable this some configuration fragments, as
  described in [section 2.4](/docs/haproxy/configuration-basics/#section-2-4) "Conditional blocks".

- modifiable: the variable can be redefined or unset in the configuration via "setenv"/"unsetenv"
  keywords.

- listed: the variable is listed in CLI's "show env" command output, described in [section 9.3](/docs/haproxy/filters/#section-9-3) "Unix
  Sockets commands" of the management guide.

There also two subcategories "master" and "worker", respectively marked 'M' and 'W' in the table
below, showing the differences between the two processes when HAProxy is launched in master-worker
mode.

- master: the variable is set and accessible from the master process. So, it will appear in the
  master CLI's "show env" output and it can be used in conditional blocks or directives to enable
  some special settings for the master (see examples in [section 2.4](/docs/haproxy/configuration-basics/#section-2-4) "Conditional blocks").

- worker: the variable is set and accessible from the worker process. It will appear in the worker
  CLI's "show env" (or the master CLI's "@1 show env") and it may as well condition some worker
  process parameters (see examples from [section 2.4](/docs/haproxy/configuration-basics/#section-2-4) "Conditional blocks").

In standalone mode (without "-W" option nor the "master-worker" keyword) the process behaves like a
worker, except for variables "HAPROXY_MASTER_CLI" and "HAPROXY_MWORKER" which are not defined.

Some variables are marked as not usable and not modifiable:

- HAPROXY_CFGFILES
- HAPROXY_MWORKER
- HAPROXY_CLI
- HAPROXY_MASTER_CLI
- HAPROXY_LOCALPEER

Their values are undefined during configuration parsing, they are set later during the
initialization. So, it's recommended not to use these variables within conditional blocks and not to
reference them in the global section's "setenv"/"resetenv"/"unsetenv" keywords.

The table below summaries the status of each variable for the different working modes:

```text
  +---------------------------+---------+------------+-----------+
  |          variable         | usable  | modifiable |  listed   |
  |                           +---------+------------+-----------+
  |                           |  M | W  |   M  |  W  |  M  |  W  |
  +---------------------------+----+----+------+-----+-----+-----+
  | HAPROXY_STARTUP_VERSION   |  X | X  |      |     |  X  |  X  |
  | HAPROXY_BRANCH            |  X | X  |      |     |  X  |  X  |
  | HAPROXY_CFGFILES          |    |    |      |     |  X  |  X  |
  | HAPROXY_MWORKER           |    |    |      |     |  X  |  X  |
  | HAPROXY_CLI               |    |    |      |     |     |  X  |
  | HAPROXY_MASTER_CLI        |    |    |      |     |  X  |     |
  | HAPROXY_LOCALPEER         |    | X  |      |     |     |  X  |
  | HAPROXY_HTTP_LOG_FMT      |    | X  |      |  X  |     |     |
  | HAPROXY_HTTP_CLF_LOG_FMT  |    | X  |      |  X  |     |     |
  | HAPROXY_HTTPS_LOG_FMT     |    | X  |      |  X  |     |     |
  | HAPROXY_TCP_LOG_FMT       |    | X  |      |  X  |     |     |
  | HAPROXY_TCP_CLF_LOG_FMT   |    | X  |      |  X  |     |     |
  | HAPROXY_KEYLOG_FC_LOG_FMT |    | X  |      |  X  |     |     |
  | HAPROXY_KEYLOG_BC_LOG_FMT |    | X  |      |  X  |     |     |
  +---------------------------+----+----+------+-----+-----+-----+
```

The variables in question are the following:

- HAPROXY_LOCALPEER: defined at the startup of the process which contains the name of the local
  peer. (See "-L" in the management guide.)

- HAPROXY_CFGFILES: list of the configuration files loaded by HAProxy, separated by semicolons. Can
  be useful in the case you specified a directory.

- HAPROXY_HTTP_LOG_FMT: contains the value of the default HTTP log format as defined in [section
  8.2.3](/docs/haproxy/configuration-logging/#section-8-2-3) "HTTP log format". It can be used to override the default log format without having to copy
  the whole original definition.

- HAPROXY_HTTP_CLF_LOG_FMT: contains the value of the default HTTP CLF log format as defined in
  [section 8.2.3](/docs/haproxy/configuration-logging/#section-8-2-3) "HTTP log format". It can be used to override the default log format without having
  to copy the whole original definition.

Example:

```shell
# Add the rule that gave the final verdict to the log
log-format "${HAPROXY_TCP_LOG_FMT} lr=%[last_rule_file]:%[last_rule_line]"
```

- HAPROXY_HTTPS_LOG_FMT: similar to HAPROXY_HTTP_LOG_FMT but for HTTPS log format as defined in
  [section 8.2.4](/docs/haproxy/configuration-logging/#section-8-2-4) "HTTPS log format".

- HAPROXY_TCP_LOG_FMT: similar to HAPROXY_HTTP_LOG_FMT but for TCP log format as defined in [section
  8.2.2](/docs/haproxy/configuration-logging/#section-8-2-2) "TCP log format".

- HAPROXY_TCP_CLF_LOG_FMT: similar to HAPROXY_HTTP_CLF_LOG_FMT but for TCP CLF log format as defined
  in [section 8.2.2](/docs/haproxy/configuration-logging/#section-8-2-2) "TCP log format".

- HAPROXY_KEYLOG_FC_LOG_FMT: contains the keylog format for the frontend (client-facing) TLS
  connection, with key entries separated by newlines so it might not be compatible with your syslog
  server. "tune.ssl.keylog on" is required.

- HAPROXY_KEYLOG_BC_LOG_FMT: similar to HAPROXY_KEYLOG_FC_LOG_FMT but for the backend
  (server-facing) TLS connection. Key entries are separated by newlines so it might not be
  compatible with your syslog server. "tune.ssl.keylog on" is required.

- HAPROXY_MWORKER: In master-worker mode, this variable is set to 1.

- HAPROXY_CLI: configured listeners addresses of the stats socket of every processe, these addresses
  are separated by semicolons.

- HAPROXY_MASTER_CLI: In master-worker mode, listeners addresses of the master CLI, separated by
  semicolons.

- HAPROXY_STARTUP_VERSION: contains the version used to start, in master-worker mode this is the
  version which was used to start the master, even after updating the binary and reloading.

- HAPROXY_BRANCH: contains the HAProxy branch version (such as "2.8"). It does not contain the full
  version number. It can be useful in case of migration if resources (such as maps or certificates)
  are in a path containing the branch number.

In addition, some pseudo-variables are internally resolved and may be used as regular variables.
Pseudo-variables always start with a dot ('.'), and are the only ones where the dot is permitted.
The current list of pseudo-variables is:

- .FILE: the name of the configuration file currently being parsed.

- .LINE: the line number of the configuration file currently being parsed, starting at one.

- .SECTION: the name of the section currently being parsed, or its type if the section doesn't have
  a name (e.g. "global"), or an empty string before the first section.

These variables are resolved at the location where they are parsed. For example if a ".LINE"
variable is used in a "log-format" directive located in a defaults section, its line number will be
resolved before parsing and compiling the "log-format" directive, so this same line number will be
reused by subsequent proxies.

This way it is possible to emit information to help locate a rule in variables, logs, error
statuses, health checks, header values, or even to use line numbers to name some config objects like
servers for example.

## 2.4. Conditional blocks {#section-2-4}

It may sometimes be convenient to be able to conditionally enable or disable some arbitrary parts of
the configuration, for example to enable/disable SSL or ciphers, enable or disable some
pre-production listeners without modifying the configuration, or adjust the configuration's syntax
to support two distinct versions of HAProxy during a migration.. HAProxy brings a set of nestable
preprocessor-like directives which allow to integrate or ignore some blocks of text. These
directives must be placed on their own line and they act on the lines that follow them. Two of them
support an expression, the other ones only switch to an alternate block or end a current level. The
4 following directives are defined to form conditional blocks:

- .if `<condition>`
- .elif `<condition>`
- .else
- .endif

The ".if" directive nests a new level, ".elif" stays at the same level, ".else" as well, and
".endif" closes a level. Each ".if" must be terminated by a matching ".endif". The ".elif" may only
be placed after ".if" or ".elif", and there is no limit to the number of ".elif" that may be
chained. There may be only one ".else" per ".if" and it must always be after the ".if" or the last
".elif" of a block.

Comments may be placed on the same line if needed after a '#', they will be ignored. The directives
are tokenized like other configuration directives, and as such it is possible to use environment
variables in conditions.

Conditions can also be evaluated on startup with the -cc parameter. See "3. Starting HAProxy" in the
management doc.

The conditions are either an empty string (which then returns false), or an expression made of any
combination of:

- the integer zero ('0'), always returns "false"
- a non-nul integer (e.g. '1'), always returns "true".
- a predicate optionally followed by argument(s) in parenthesis.
- a condition placed between a pair of parenthesis '(' and ')'
- an exclamation mark ('!') preceding any of the non-empty elements above, and which will negate its
  status.
- expressions combined with a logical AND ('&&'), which will be evaluated from left to right until
  one returns false
- expressions combined with a logical OR ('\|\|'), which will be evaluated from right to left until
  one returns true

The same line tokenizer and argument parser are used as for the rest of the configuration language.
Words are split around consecutive series of one or more unquoted spaces or tabs, and are
reassembled together using a single space to delimit them before evaluation, in order to save the
user from having to quote the entire line. But this also means that spaces surrounding commas or
parenthesis are definitely part of the value, which is not always expected. For example, the
expression below:

```text
.if defined( HAPROXY_MWORKER )
```

will test for the existence of variable " HAPROXY_MWORKER " (with spaces), and this one:

```text
.if streq("$ENABLE_SSL",     1)
```

will compare the environment variable "ENABLE_SSL" to the value " 1" (with a single leading space).
The reason is the line is first split into words like this:

```text
   .if streq("$ENABLE_SSL",     1)
  |---|--------------------|   |--|
    1           2               3
```

then the weak quoting is applied and environment variable "\$ENABLE_SSL" is resolved (let's say for
example that ENABLE_SSL=0), and finally the words are reassembled into a single string by placing a
single space between the words:

```text
   .if streq(0, 1)
  |---|-------|--|
    1     2     3
```

and only then it is parsed as a single expression. The space that was inserted between the comma and
"1" is still part of the argument value, making this argument " 1":

```text
   .if streq(0, 1)
  |---|-----|-|--|
    \    \    \  \_ argument2: " 1"
     \    \    \___ argument1: "0"
      \    \_______ function: "streq"
       \___________ directive: ".if"
```

It's visible here that even if ENABLE_SSL had been equal to "1", it wouldn't have matched " 1" since
the string would differ by one space.

Note: as explained in section "2.2. Quoting and escaping", a good rule of thumb is to never insert
unneeded spaces inside expressions.

Note that like in other languages, the AND operator has precedence over the OR operator, so that "A
&& B \|\| C && D" evalues as "(A && B) \|\| (C && D)".

The list of currently supported predicates is the following:

- awslc_api_atleast(`<ver>`): returns true if the current awslc API number is at least as recent as
  `<ver>` otherwise false. Example: awslc_api_atleast(35)

- awslc_api_before(`<ver>`): returns true if the current awslc API number is strictly older than
  `<ver>` otherwise false. Example: awslc_api_before(26)

- defined(`<name>`) : returns true if an environment variable `<name>` exists, regardless of its
  contents

- feature(`<name>`) : returns true if feature `<name>` is listed as present in the features list
  reported by "haproxy -vv" (which means a `<name>` appears after a '+')

- openssl_version_atleast(`<ver>`): returns true if the current openssl version is at least as
  recent as `<ver>` otherwise false. Libraries like LibreSSL, AWS-LC and WolfSSL also provide a
  pseudo OpenSSL version. Example:

```text
ssllib_name_startswith(OpenSSL) && openssl_version_atleast(1.1.1)
```

- openssl_version_before(`<ver>`): returns true if the current openssl version is strictly older
  than `<ver>` otherwise false. Libraries like LibreSSL, AWS-LC and WolfSSL also provide a pseudo
  OpenSSL version. Example: openssl_version_before(3.5.0)

- ssllib_name_startswith(`<name>`) : return true if the SSL library name HAProxy was linked with,
  starts with `<name>`. Example: ssllib_name_startswith(wolfSSL)

- streq(`<str1>`,`<str2>`) : returns true only if the two strings are equal

- strneq(`<str1>`,`<str2>`): returns true only if the two strings differ

- strstr(`<str1>`,`<str2>`): returns true only if the second string is found in the first one.

- version_atleast(`<ver>`): returns true if the current haproxy version is at least as recent as
  `<ver>` otherwise false. The version syntax is the same as shown by "haproxy -v" and missing
  components are assumed as being zero.

- version_before(`<ver>`): returns true if the current haproxy version is strictly older than
  `<ver>` otherwise false. The version syntax is the same as shown by "haproxy -v" and missing
  components are assumed as being zero.

- enabled(`<opt>`) : returns true if the option `<opt>` is enabled at run-time. Only a subset of
  options are supported:

```text
POLL, EPOLL, KQUEUE, EVPORTS, SPLICE,
GETADDRINFO, REUSEPORT, FAST-FORWARD,
SERVER-SSL-VERIFY-NONE
```

Example:

```haproxy
# 1. HAPROXY_MWORKER variable is set automatically by HAProxy in master and
# in worker process environments (see HAProxy variables matrix from
# 2.3. Environment variables). Its presence enables an additional listener.

global
  master-worker
```

.if defined(HAPROXY_MWORKER) listen mwcli_px bind:1111 ... .endif

```haproxy
# 2. HAPROXY_BRANCH is set automatically by HAProxy in master and in worker
# process environments (see HAProxy variables matrix from 2.3. Environment
# variables). We check HAPROXY_BRANCH value and conditionally enable
# mworker-max-reloads parameter.

global
  master-worker
```

.if streq("\$HAPROXY_BRANCH",3.1) mworker-max-reloads 5 .endif

```haproxy
# 3. Some arbitrary environment variables are set by user in the global
# section. If HAProxy is started in master-worker mode, they are presented in
# master and in worker process environments. We check values of these
# variables and conditionally enable ports 80 and 443. Environment variables
# checks can be mixed with features and version checks.

global
  setenv WITH_SSL yes
  unsetenv SSL_ONLY
```

.if strneq("\$SSL_ONLY",yes) bind:80 .endif

.if streq("\$WITH_SSL",yes) .if feature(OPENSSL) bind:443 ssl crt ... .endif .endif

.if feature(OPENSSL) && (streq("$`WITH_SSL",yes) || streq("`$SSL_ONLY",yes)) bind:443 ssl crt ...
.endif

.if version_atleast(2.4-dev19) profiling.memory on .endif

.if !feature(OPENSSL) .alert "SSL support is mandatory" .endif

Four other directives are provided to report some status:

- .diag "message" : emit this message only when in diagnostic mode (-dD)
- .notice "message" : emit this message at level NOTICE
- .warning "message": emit this message at level WARNING
- .alert "message" : emit this message at level ALERT

Messages emitted at level WARNING may cause the process to fail to start if "zero-warning" is
enabled. Messages emitted at level ALERT will always cause a fatal error. These can be used to
detect some inappropriate conditions and provide advice to the user.

Example:

```text
.if "${A}"
  .if "${B}"
     .notice "A=1, B=1"
  .elif "${C}"
     .notice "A=1, B=0, C=1"
  .elif "${D}"
     .warning "A=1, B=0, C=0, D=1"
  .else
     .alert "A=1, B=0, C=0, D=0"
  .endif
.else
     .notice "A=0"
.endif

.diag "WTA/2021-05-07: replace 'redirect' with 'return' after switch to 2.4"
      http-request redirect location /goaway if ABUSE
```

## 2.5. Time format {#section-2-5}

Some parameters involve values representing time, such as timeouts. These values are generally
expressed in milliseconds (unless explicitly stated otherwise) but may be expressed in any other
unit by suffixing the unit to the numeric value. It is important to consider this because it will
not be repeated for every keyword. Supported units are:

- us: microseconds. 1 microsecond = 1/1000000 second
- ms: milliseconds. 1 millisecond = 1/1000 second. This is the default.
- s : seconds. 1s = 1000ms
- m : minutes. 1m = 60s = 60000ms
- h : hours. 1h = 60m = 3600s = 3600000ms
- d : days. 1d = 24h = 1440m = 86400s = 86400000ms

## 2.6. Size format {#section-2-6}

Some parameters involve values representing size, such as bandwidth limits. These values are
generally expressed in bytes (unless explicitly stated otherwise) but may be expressed in any other
unit by suffixing the unit to the numeric value. It is important to consider this because it will
not be repeated for every keyword. Supported units are case insensitive:

- k: kilobytes. 1 kilobyte = 1024 bytes
- m: megabytes. 1 megabyte = 1048576 bytes
- g: gigabytes. 1 gigabyte = 1073741824 bytes

Both time and size formats require integers, decimal notation is not allowed.

## 2.7. Name format for maps and ACLs {#section-2-7}

It is possible to use a list of pattern for maps or ACLs. A list of pattern is identified by its
name and may be used at different places in the configuration. List of pattern are split on three
categories depending on the name format:

- Lists of pattern based on regular files: It is the default case. The filename, absolute or
  relative, is used as name. The file must exist otherwise an error is triggered. But it may be
  empty. The "file@" prefix may also be specified but it is not part of the name identifying the
  list. A filename, with or without the prefix, references the same list of pattern.

- Lists of pattern based on optional files: The filename must be preceded by "opt@" prefix. The file
  existence is optional. If the file exists, its content is loaded but no error is reported if not.
  The prefix is not part of the name identifying the list. It means, for a given filename, Optional
  files and regular files reference the same list of pattern.

- Lists of pattern based on virtual files: The name is just an identifier. It is not a reference to
  any file. "virt@" prefix must be used. It is part of the name. Thus it cannot be mixed with other
  kind of lists.

Virtual files are useful when patterns are fully dynamically managed with no patterns on startup and
on reload. Optional files may be used under the same conditions. But patterns can be dumped in the
file, via an external script based on the "show map" CLI command for instance. This way, it is
possible to keep patterns on reload.

Note: Even if it is unlikely, it means no regular file starting with "file@", "opt@" or "virt@" can
be loaded, except by adding "./" explicitly in front of the filename (for instance
"file@./virt@map").

## 2.8. Variables {#section-2-8}

In HAProxy configuration, variables can be used in sample fetch functions, converters, log-format
strings or TCP/HTTP actions. Process-wide variables can be defined, globally accessible for the
whole life of the process. Some others have a shorter lifespan. Variables are similar to those found
in shell scripts. It is a symbolic name for a chunk of memory. The variables size is not limited and
is dynamically allocated. So they must be used with caution, especially for an intensive usage.
However, it is possible to limit the maximum amount of memory used by the variables by setting
"tune.vars" global parameters.

Variables must be designated using the format "`<scope>`.`<name>`". The `<scope>` is a single word
indicating the life time of the variable. The `<name>` part, inside a scope, may only contain
characters 'a-z', 'A-Z', '0-9' and '\_'. It is unique in this scope but the same name in different
scopes can be used and refers to different variables. Supported scopes are:

- proc : for variables known during the whole process lifespan and globally accessible. "proc"
  variables can be manipulated from the CLI using "get var" and "set var" commands. They can also be
  set from "global" sections via "set-var" and "set-var-fmt" directives.

- sess : for variables known during the whole lifespan of a session. "sess" variables are private to
  a session, not visbile from outside it and not shared with other sessions.

- txn : for variables known during the whole lifespan of a transaction. "txn" variables are private
  to a stream, not visible from outside it and not shared with other streams.

- req : for variables known during the request processing for a specific stream. "req" variables are
  visible from the stream creation and until the first server connection attempt. They are private
  to a stream, not visible from outside it and not shared with other streams. There is no overlap at
  all between "req" and "res" variables.

- res : for variables known during the response processing for a specific stream. "res" variables
  are visible from the first server connection attempt and until the stream destruction. They are
  private to a stream, not visible from outside it and not shared with other streams. There is no
  overlap at all between "req" and "res" variables.

- check: for variables known during a health-check execution. "check" variables are private to a
  health-check, not visible from outside it and are not shared with other health-checks. They can be
  set using dedicated "tcp-check" or "http-check" directives.

Depending on the context, extra scopes referencing the parent of a current stream can be used:

- psess: same as "sess" but using the session of the parent stream, if any.

- ptxn : same as "txn" but using the transaction of the parent stream, if any.

- preq : same as "req" but using the parent stream, if any. "preq" variables are only accessible
  during request processing of the parent stream.

- pres : same as "res" but using the parent stream, if any. "pres" variables are only accessible
  during response processing of the parent stream.

Scopes referencing the parent stream are usable from the moment it is defined. Most of time, there
is no parent stream. But, if applicable, this will be explicitly specified. For now, it is only
possible to retrieve the value of variables defined in a scope of the parent stream. It is not
possible to set nor unset such variables. Usually a child stream performs some processing for the
parent at a precise moment and prevents it from making progress until the operation it does is
completed. This means that the parent may be stopped in the middle of a request processing or a
response processing for example. As such, certain scopes will not be available from the child
stream. For example if a request is subject to some analysis performed by a child stream, this child
stream will not find any variable in the "pres" scope since the parent is not processing a response,
hence doesn't have any variables in its "res" scope.

The content of a variable is the result of the evaluation of a sample fetch expression and it
inherits of the output type of this expression. It is important when the variable is used because
its type must be compatible with its usage. For instance a variable containing a string used in
"add()" converter must be convertible to a valid integer to succeed. It is especially true when
variables are compared to static value. The right matching method must be used.

## 2.9. Address formats {#section-2-9}

Several statements as "bind, "server", "nameserver" and "log" requires an address.

This address can be a host name, an IPv4 address, an IPv6 address, or '*'. The '*' is equal to the
special address "0.0.0.0" and can be used, in the case of "bind" or "dgram-bind" to listen on all
IPv4 of the system.The IPv6 equivalent is '::'.

Depending of the statement, a port or port range follows the IP address. This is mandatory on 'bind'
statement, optional on 'server'.

This address can also begin with a slash '/'. It is considered as the "unix" family, and '/' and
following characters must be present the path.

Default socket type or transport method "datagram" or "stream" depends on the configuration
statement showing the address. Indeed, 'bind' and 'server' will use a "stream" socket type by
default whereas 'log', 'nameserver' or 'dgram-bind' will use a "datagram".

Optionally, a prefix could be used to force the address family and/or the socket type and the
transport method.

### 2.9.1. Address family prefixes {#section-2-9-1}

'abns@`<name>`' following `<name>` is an abstract namespace (Linux only).

'abnsz@`<name>`' following `<name>` is a zero-terminated abstract namespace (Linux only).

'fd@`<n>`' following address is a file descriptor `<n>` inherited from the parent. The fd must be
bound and may or may not already be listening.

'ip@`<address>`[:port1[-port2]]' following `<address>` is considered as an IPv4 or IPv6 address
depending on the syntax. Depending on the statement using this address, a port or a port range may
or must be specified.

'ipv4@`<address>`[:port1[-port2]]' following `<address>` is always considered as an IPv4
address. Depending on the statement using this address, a port or a port range may or must be
specified.

'ipv6@`<address>`[:port1[-port2]]' following `<address>` is always considered as an IPv6
address. Depending on the statement using this address, a port or a port range may or must be
specified.

'sockpair@`<n>`' following address is the file descriptor of a connected unix socket or of a
socketpair. During a connection, the initiator creates a pair of connected sockets, and passes one
of them over the FD to the other end. The listener waits to receive the FD from the unix socket and
uses it as if it were the FD of an accept(). Should be used carefully.

               Bugs: This protocol is known to be unreliable on macOS because
               of an issue in the macOS sendmsg(2) implementation. The
               connection might not be accepted correctly.

'unix@`<path>`' following string is considered as a UNIX socket `<path>`. this prefix is useful to
declare an UNIX socket path which don't start by slash '/'.

### 2.9.2. Socket type prefixes {#section-2-9-2}

Previous "Address family prefixes" can also be prefixed to force the socket type and the transport
method. The default depends of the statement using this address but in some cases the user may force
it to a different one. This is the case for "log" statement where the default is syslog over UDP but
we could force to use syslog over TCP.

Those prefixes were designed for internal purpose and users should instead use use aliases of the
next section "2.9.3 Protocol prefixes". However these can sometimes be convenient, for example in
combination with inherited sockets known by their file descriptor number, in which case the address
family is "fd" and the socket type must be declared.

If users need one those prefixes to perform what they expect because they can not configure the same
using the protocol prefixes, they should report this to the maintainers.

'stream+`<family>`@`<address>`' forces socket type and transport method to "stream"

'dgram+`<family>`@`<address>`' forces socket type and transport method to "datagram".

'quic+`<family>`@`<address>`' forces socket type to "datagram" and transport method to "stream".

### 2.9.3. Protocol prefixes {#section-2-9-3}

'quic4@`<address>`[:port1[-port2]]' following `<address>` is always considered as an IPv4
address but socket type is forced to "datagram" and the transport method is forced to "stream".
Depending on the statement using this address, a UDP port or port range can or must be specified. It
is equivalent to "quic+ipv4@".

'quic6@`<address>`[:port1[-port2]]' following `<address>` is always considered as an IPv6
address but socket type is forced to "datagram" and the transport method is forced to "stream".
Depending on the statement using this address, a UDP port or port range can or must be specified. It
is equivalent to "quic+ipv6@".

'tcp@`<address>`[:port1[-port2]]' following `<address>` is considered as an IPv4 or IPv6 address
depending of the syntax but socket type and transport method is forced to "stream". Depending on the
statement using this address, a port or a port range can or must be specified. It is considered as
an alias of 'stream+ip@'.

'tcp4@`<address>`[:port1[-port2]]' following `<address>` is always considered as an IPv4 address
but socket type and transport method is forced to "stream". Depending on the statement using this
address, a port or port range can or must be specified. It is considered as an alias of
'stream+ipv4@'.

'tcp6@`<address>`[:port1[-port2]]' following `<address>` is always considered as an IPv6 address
but socket type and transport method is forced to "stream". Depending on the statement using this
address, a port or port range can or must be specified. It is considered as an alias of
'stream+ipv4@'.

'mptcp@`<address>`[:port1[-port2]]' following `<address>` is considered as an IPv4 or IPv6
address depending of the syntax but socket type and transport method is forced to "stream", with the
MPTCP protocol. Depending on the statement using this address, a port or a port range can or must be
specified.

'mptcp4@`<address>`[:port1[-port2]]' following `<address>` is always considered as an IPv4
address but socket type and transport method is forced to "stream", with the MPTCP protocol.
Depending on the statement using this address, a port or port range can or must be specified.

'mptcp6@`<address>`[:port1[-port2]]' following `<address>` is always considered as an IPv6
address but socket type and transport method is forced to "stream", with the MPTCP protocol.
Depending on the statement using this address, a port or port range can or must be specified.

'udp@`<address>`[:port1[-port2]]' following `<address>` is considered as an IPv4 or IPv6 address
depending of the syntax but socket type and transport method is forced to "datagram". Depending on
the statement using this address, a port or a port range can or must be specified. It is considered
as an alias of 'dgram+ip@'.

'udp4@`<address>`[:port1[-port2]]' following `<address>` is always considered as an IPv4 address
but socket type and transport method is forced to "datagram". Depending on the statement using this
address, a port or port range can or must be specified. It is considered as an alias of
'dgram+ipv4@'.

'udp6@`<address>`[:port1[-port2]]' following `<address>` is always considered as an IPv6 address
but socket type and transport method is forced to "datagram". Depending on the statement using this
address, a port or port range can or must be specified. It is considered as an alias of
'dgram+ipv4@'.

'uxdg@`<path>`' following string is considered as a unix socket `<path>` but transport method is
forced to "datagram". It is considered as an alias of 'dgram+unix@'.

'uxst@`<path>`' following string is considered as a unix socket `<path>` but transport method is
forced to "stream". It is considered as an alias of 'stream+unix@'.

In future versions, other prefixes could be used to specify protocols like QUIC which proposes
stream transport based on socket of type "datagram".

## 2.10. Examples {#section-2-10}

```haproxy
# Simple configuration for an HTTP proxy listening on port 80 on all
    # interfaces and forwarding requests to a single backend "servers" with a
    # single server "server1" listening on 127.0.0.1:8000
    global
        daemon
        maxconn 256

    defaults
        mode http
        timeout connect 5000ms
        timeout client 50000ms
        timeout server 50000ms

    frontend http-in
        bind *:80
        default_backend servers

    backend servers
        server server1 127.0.0.1:8000 maxconn 32


    # The same configuration defined with a single listen block. Shorter but
    # less expressive, especially in HTTP mode.
    global
        daemon
        maxconn 256

    defaults
        mode http
        timeout connect 5000ms
        timeout client 50000ms
        timeout server 50000ms

    listen http-in
        bind *:80
        server server1 127.0.0.1:8000 maxconn 32
```

Assuming haproxy is in \$PATH, test these configurations in a shell with:

```shell
$ sudo haproxy -f configuration.conf -c
```

---

Backlinks:

- [7. ACLs & Samples](/docs/haproxy/acls-and-samples/)
- [9. Filters](/docs/haproxy/filters/)
- [12. Other Sections](/docs/haproxy/other-sections/)
- [11. Stick Tables & Peers](/docs/haproxy/stick-tables-and-peers/)
