HAProxy 3.4.4
2. Configuring HAProxy
Complete English Markdown edition of the HAProxy 3.4 Starter, Configuration, and Management manuals
2.1. Configuration file format
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:
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
sectionThis 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:
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:
and:
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
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 (’\’) 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 (’\’):
\ 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 quotingIn addition, a few non-printable characters may be emitted using their usual C-language representation:
\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:
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:
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:
# 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}rThere 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 \#, \$, and \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:
# 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:
because we would like the string to cut like this:
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:
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:
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:
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):
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:
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 (’\’) 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:
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:
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
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:
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 “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 “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 “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 “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:
+---------------------------+---------+------------+-----------+
| 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 “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 “HTTP log format”. It can be used to override the default log format without having to copy the whole original definition.
Example:
# 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 “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 “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 “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
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:
will test for the existence of variable " HAPROXY_MWORKER " (with spaces), and this one:
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:
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:
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":
.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:
-
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:
Example:
# 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
# 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
# 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:
.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 ABUSE2.5. Time format
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
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
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
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
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
‘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
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
‘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
# 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 32Assuming haproxy is in $PATH, test these configurations in a shell with:
Source and license
Documentation imported from pig.center · Upstream documentation
- Version
- 3.4.4
- License
- GPL-2.0-only
- Source revision
c88f04bf458bba252baf739fd65c4f81e9f4167aaf78c0d4d3960e5d416c8f7b