keepalived.conf(5)
💖 keepalived.conf(5) — Keepalived Configuration's Manual 💖
okay so welcome to keepalived.conf(5), bestie — this is the manual for keepalived.conf, the configuration file for Keepalived. We're doing this in parts, and part 1 is right here. Buckle up 🎀
🌸 NAME 🌸
keepalived.conf — configuration file for Keepalived.
Note: This documentation MUST be considered as THE exhaustive source of information in order to configure Keepalived. This documentation is supported and maintained by Keepalived Core-Team.
🦋 DESCRIPTION 🦋
okay so keepalived.conf is the configuration file that describes all the Keepalived keywords. The keywords live in hierarchies of blocks and subblocks, and each layer is delimited by { and } pairs. It's giving nested-lore energy, fr fr.
Comments start with # or ! and run to the end of the line — and yes, they can start anywhere in a line. Lowkey handy.
The keyword include and its variants let you pull other configuration files into the main configuration file, or into files that were themselves included. The format of the include directive is:
include FILENAME
FILENAME can be a fully qualified or relative pathname, and it can include wildcards, including csh style brace expressions such as {foo/{,cat,dog},bar} if glob() supports them.
After opening an included file, the current directory is set to the directory of the file itself, so any relative paths included from a file are relative to the directory of the including file itself. directory glow-up, basically ✨
The include variants add additional include checks to the current include_check level (see below). The variants are:
includer FILENAME— same asinclude_check readableincludem FILENAME— same asinclude_check matchincludew FILENAME— same asinclude_check wildcard_matchincludeb FILENAME— same asinclude_check brace_matchincludea FILENAME— allinclude_checkchecks
NOTE: If the libcglob()function does not supportGLOB_ALTDIRFUNC(e.g. Musl libc as on Alpine Linux etc.), then onlyincludea,includerandincludewof the above options will work.
Why do we want to allow errors? Suppose a configuration has optional files in /etc/keepalived/conf.d, then include_/etc/keepalived/conf.d/* could be specified, but it should not error if there are no files in the directory; in this case includer should be used. Otherwise it is sensible to use includea.
include handling will not work if the include line uses conditional configuration or parameter substitution, since the detection of the include keywords is done before the processing of conditional configuration and parameter substitution. iykyk 🙈
The basic include keyword is retained for backward compatibility, since it does not produce config errors if files could not be opened etc.
🎀 PARAMETER SYNTAX 🎀
<BOOL>is one ofon|off|true|false|yes|no<TIMER>is a time value in seconds, including fractional seconds, e.g.2.71828or3; resolution of timer is micro-seconds.
💅 SCRIPTS 💅
There are three classes of scripts can be configured to be executed.
- (a) Notify scripts that are run when a vrrp instance or vrrp group changes state, or a virtual server quorum changes between up and down.
- (b) vrrp tracking scripts that will cause vrrp instances to go down if they exit a non-zero exist status, or if a weight is specified will add or subtract the weight to/from the priority of that vrrp instance.
- (c) LVS checker misc scripts that will cause a real server to be configured down if they exit with a non-zero status.
By default the scripts will be executed by user keepalived_script if that user exists, or if not by root, but for each script the user/group under which it is to be executed can be specified.
There are significant security implications if scripts are executed with root privileges, especially if the scripts themselves are modifiable or replaceable by a non root user. Consequently, security checks are made at startup to ensure that if a script is executed by root, then it cannot be modified or replaced by a non root user. do not skip this part, bestie 😭
All scripts should be written so that they will terminate on receipt of a SIGTERM signal. Scripts will be sent SIGTERM if their parent terminates, or it is a script that keepalived is awaiting its exit status and it has run for too long.
🌸 Quoted strings 🌸
Quoted strings are specified between " or ' characters and strings are delimited by whitespace. In the examples below the ´ characters are not part of the strings and should not be specified:
´abcd" efg h jkl "mnop´
will be the single string:
´abcd efg h jkl mnop´
whereas:
´abcd "efg h jkl" mnop´
will be the three strings:
´abcd´, ´efg h jkl´ and ´mnop´
i.e. the " and ' characters are removed and any intervening whitespace is retained. slay, quoting rules ✨
Quoted strings can also have escaped characters, like the shell. \a, \b, \E, \f, \n, \r, \t, \v, \nnn and \xXX (where nnn is up to 3 octal digits, and XX is up to two hex digits) and \cC (which produces the control version of character C) are all supported. \C for any other character C is just treated as an escaped version of character C, so \\ is a \ character and \" will be a " character, but it won't start or terminate a quoted string.
For specifying scripts with parameters, unquoted spaces will separate the parameters. If it is required for a parameter to contain a space, it should be enclosed in single quotes ('). For example:
$SG_NAME=SG1
$INST=low
$USER=user
notify_master "/etc/keepalived/notify_event.sh ' spaces\\x20f\x69le ' '\"s p a c e \"' ${SG_NAME}.$INST master" $USER group
specifies a notify_master script /etc/keepalived/notify_event.sh that will be executed as user:group with parameters:
´ spaces\x20file ´, ´"s p a c e "´, ´SG1.low´ and ´master´
🌈 CONFIGURATION PARSER 🌈
Traditionally the configuration file parser has not been one of the strengths of keepalived. Lot of efforts have been put to correct this even if this is not the primal goal of the project. character development arc, honestly 💀
🦋 TOP HIERACHY 🦋
Keepalived configuration file is articulated around a set of configuration blocks. Each block is focusing and targetting a specific daemon family feature. These features are:
GLOBAL CONFIGURATIONBFD CONFIGURATIONVRRPD CONFIGURATIONLVS CONFIGURATION
🌈 GLOBAL CONFIGURATION 🌈
contains subblocks of Global definitions, Linkbeat interfaces, Interface up/down transition delays, Static track groups, Static addresses, Static routes, and Static rules.
🌟 Global definitions 🌟
okay so here's the big global-facilities block, bestie — the sample config is doing a lot, so here it is exactly:
# Following are global daemon facilities for running
# keepalived in a separate network namespace:
# --
# Set the network namespace to run in.
# The directory /run/keepalived will be created as an
# unshared mount point, for example for pid files.
# syslog entries will have _NAME appended to the ident.
# Note: the namespace cannot be changed on a configuration reload.
net_namespace NAME
# Add the IPVS configuration in the specified net namespace. It allows to easily
# split the VIP traffic on a given namespace and keep the healthchecks traffic
# in another namespace. If NAME is not specified, then the default namespace
# will be used.
net_namespace_ipvs NAME
# ipsets wasn't network namespace aware until Linux 3.13, and so
# if running with an earlier version of the kernel, by default
# use of ipsets is disabled if using a namespace and vrrp_ipsets
# has not been specified. This options overrides the default and
# allows ipsets to be used with a namespace on kernels prior to 3.13.
namespace_with_ipsets
# If multiple instances of keepalived are run in the same namespace,
# this will create pid files with NAME as part of the file names,
# in /run/keepalived.
# Note: the instance name cannot be changed on a configuration reload
instance NAME
# Create pid files in /run/keepalived
use_pid_dir
# Poll to detect media link failure using ETHTOOL, MII or ioctl interface
# otherwise uses netlink interface.
linkbeat_use_polling
# Time for main process to allow for child processes to exit on termination
# in seconds. This can be needed for very large configurations.
# (default: 5)
child_wait_time SECS
Note: All processes/scripts run by keepalived are run with parent death signal set toSIGTERM. All such processes/scripts should either not change the action forSIGTERM, or ensure that the process/script terminates onceSIGTERMis received, possibly following any cleanup actions needed.
Then the global_defs block looks like this:
# Global definitions configuration block
global_defs {
# In order to ensure that all processes read exactly the same configuration,
# while the config is first read it is written, by default, to a memory based
# file (or to an anonymous file in /tmp/ if memfd_create() is not supported).
# If your configuration is very large, you may not want the copy to be
# held in memory, in which case specifing the tmp_config_directory causes the
# configuration to be written to an anonymous file on the filesystem on which
# the specified directory resides, which must be writeable by keepalived.
# This setting cannot be changed on a reload, and it should be specified as
# early as possible in the configuration.
tmp_config_directory DIRECTORY
# config_save_dir causes keepalived to save configuration state and
# configuration files before and after each reload. This is used for debugging
# purposes if there appear to be problems related to repeated reloads.
# The directory will be created if it does not exist, but all parent
# directories must exist.
config_save_dir DIRECTORY
# Set the process names of the keepalived processes to the default values:
# keepalived, keepalived_vrrp, keepalived_ipvs, keepalived_bfd
process_names
# Specify the individual process names
process_name NAME
vrrp_process_name NAME
checker_process_name NAME
bfd_process_name NAME
# keepalived by default resolves script path names to remove symlinks.
# To keep symlinks in pathnames, specify use_syslink_paths.
use_symlink_paths [<BOOL>]
}
vibe check on each global directive: 🫶
net_namespace NAME— sets the network namespace to run in. The directory/run/keepalivedwill be created as an unshared mount point, for example for pid files. syslog entries will have_NAMEappended to the ident. Note: the namespace cannot be changed on a configuration reload.net_namespace_ipvs NAME— adds the IPVS configuration in the specified net namespace. It allows you to easily split the VIP traffic on a given namespace and keep the healthchecks traffic in another namespace. IfNAMEis not specified, then the default namespace will be used.namespace_with_ipsets— ipsets wasn't network namespace aware until Linux 3.13, and so if running with an earlier version of the kernel, by default use of ipsets is disabled if using a namespace andvrrp_ipsetshas not been specified. This options overrides the default and allows ipsets to be used with a namespace on kernels prior to 3.13.instance NAME— if multiple instances of keepalived are run in the same namespace, this will create pid files withNAMEas part of the file names, in/run/keepalived. Note: the instance name cannot be changed on a configuration reload.use_pid_dir— create pid files in/run/keepalived.linkbeat_use_polling— poll to detect media link failure usingETHTOOL,MIIorioctlinterface; otherwise uses netlink interface.child_wait_time SECS— time for main process to allow for child processes to exit on termination, in seconds. This can be needed for very large configurations. Default:5.tmp_config_directory DIRECTORY— in order to ensure that all processes read exactly the same configuration, while the config is first read it is written, by default, to a memory based file (or to an anonymous file in/tmp/ifmemfd_create()is not supported). If your configuration is very large, you may not want the copy to be held in memory, in which case specifyingtmp_config_directorycauses the configuration to be written to an anonymous file on the filesystem on which the specified directory resides, which must be writeable by keepalived. This setting cannot be changed on a reload, and it should be specified as early as possible in the configuration.config_save_dir DIRECTORY— causes keepalived to save configuration state and configuration files before and after each reload. This is used for debugging purposes if there appear to be problems related to repeated reloads. The directory will be created if it does not exist, but all parent directories must exist.process_names— set the process names of the keepalived processes to the default values:keepalived,keepalived_vrrp,keepalived_ipvs,keepalived_bfd.process_name NAME— specify the individual process name.vrrp_process_name NAME— specify the individual process name.checker_process_name NAME— specify the individual process name.bfd_process_name NAME— specify the individual process name.use_symlink_paths [<BOOL>]— keepalived by default resolves script path names to remove symlinks. To keep symlinks in pathnames, specify this option.
and that's where part 1 lands, bestie — part 2 is pulling up next with the rest, so don't wander off 💅✨
Okay so boom — scripts time. 💅
startup_script and shutdown_script each run once: the startup one fires when keepalived starts, before any child processes are run, and the shutdown one fires when keepalived stops, after all child processes have terminated. That's the whole vibe.
Why does this exist? The original motivation was that even though keepalived can set up IPVS configuration using firewall marks, there was literally no mechanism for adding configuration to set the firewall marks (or removing them afterwards). Iconic gap, honestly. 🫶
It's also lowkey the go-to for setting up the iptables framework required if you're using iptables (peep the vrrp_iptables option below), modifying interface settings, or literally anything else you can do from a script or program.
Only one startup script and one shutdown script can be specified — no stacking, bestie.
The timeouts (in seconds, default 10 seconds) are the time allowed for scripts to run; if the timeout expires, the scripts will be killed (this is to stop keepalived hanging waiting for the scripts to terminate). Very "we're leaving without you" of it. 💀
startup_script SCRIPT_NAME [username [groupname]]
startup_script_timeout SECONDS # range [1,1000]
shutdown_script SCRIPT_NAME [username [groupname]]
shutdown_script_timeout SECONDS # range [1,1000]
💌 the email girlies
Set of email To: to notify. To include a display name, the whole email address must be included in double quotes ("). Yes, whole address in the quotes, not just the name. 🌸
notification_email {
admin@example1.com "My admin <admin@example2.com>"
...
}
notification_email_from admin@example.com — the email from address that will be in the header (see the comment above for including a display name). Default: keepalived@local_host_name. 🎀
smtp_server 127.0.0.1 [<PORT>] — the remote SMTP server used to send notification email. IP address or domain name, with optional port number. Default port number: 25. ✨
smtp_helo_name <STRING> — the name to use in HELO messages. Default: local host name.
smtp_connect_timeout 30 — SMTP server connection timeout, in seconds.
smtp_alert <BOOL> — sets the default state for all smtp_alerts.
smtp_alert_vrrp <BOOL> — sets the default state for vrrp smtp_alerts.
smtp_alert_checker <BOOL> — sets the default state for checker smtp_alerts.
checker_log_all_failures <BOOL> — logs every failed real server check in syslog. But heads up: nevertheless, the SMTP alert is only sent when all retry checks failed and the real server transitions to DOWN state. So it's not spam-city in your inbox. 😌
no_email_faults — don't send smtp alerts for fault conditions. Periodt. ✋
🏷️ identity + multicast
router_id <STRING> — a string identifying the machine (it doesn't have to be the hostname). Default: local host name.
vrrp_mcast_group4 224.0.0.18 — multicast group to use for IPv4 VRRP adverts. Defaults to the RFC5798 IANA assigned VRRP multicast address 224.0.0.18, which you typically do not want to change. fr fr, leave it alone. 🙈
vrrp_mcast_group6 ff02::12 — multicast group to use for IPv6 VRRP adverts. Default: ff02::12.
default_interface p33p1.3 — sets the default interface for static addresses. Default: eth0.
🔄 the LVS / sync daemon zone
The sync daemon as provided by the IPVS kernel code only supports one master and one backup daemon instance at a time to synchronize the IPVS connection table. That's a kernel limitation, not a keepalived vibe check. See ipvsadm(8) man page for more details of the sync daemon.
Parameters are the binding interface, and optionally:
inst VRRP_INSTANCE—instcan be omitted for backward compatibilitysyncid—(0 to 255)for lvs syncd; default is the VRID of the vrrp instance, or0if no vrrp instancemaxlen—(1..65507)maximum packet length (limit ismtu - 20 - 8)port—(1..65535)UDP port number to use, default 8848ttl—(1..255)group— multicast group address (IPv4 or IPv6), default224.0.0.81
If VRRP_INSTANCE is not specified, both the master and backup sync daemons will be run as long as keepalived is running. Otherwise the sync daemon master/backup state tracks the state of the specified vrrp instance: if the vrrp instance is in master state, only the master sync daemon will run; if the vrrp instance is not master, only the backup sync daemon will run. 🌈
NOTE:maxlen,port,ttlandgroupare only available on Linux 4.3 or later. Older kernels, you're on your own bestie.
See kernel source doc/Documentation/networking/ipvs-sysctl.txt for details of parameters controlling IPVS and the sync daemon. And /proc/net/ip_vs* provides some details about the state of IPVS. 🫶
lvs_sync_daemon <INTERFACE> [[inst] <VRRP_INSTANCE>] [id <SYNC_ID>] \
[maxlen <LEN>] [port <PORT>] [ttl <TTL>] [group <IP ADDR>]
lvs_timeouts [tcp SECS] [tcpfin SECS] [udp SECS] — specifies the tcp, tcp_fin and udp connection tracking timeouts in seconds. At least one value must be specified; not setting a value leaves it unchanged from when keepalived started. 💜
lvs_flush — flush any existing LVS configuration at startup. Clean slate energy. ✨
lvs_flush_on_stop [VS] — flush remaining LVS configuration at shutdown. For large configurations this is much faster than the default approach of deleting each RS and each VS individually. If VS is specified, remove each keepalived managed virtual server without explicitly removing the real servers (the kernel will remove them). Let the kernel do the cleanup, she's got it. 💅
🗣️ GARP / NA behaviour (the ARP girlies)
vrrp_garp_master_repeat 1 — number of gratuitous ARP messages to send at a time after transition to MASTER. Default: 5.
vrrp_garp_master_delay 10 — delay for the second set of gratuitous ARPs after transition to MASTER, in seconds. 0 means no second set. Default: 5.
vrrp_garp_lower_prio_repeat 1 — number of gratuitous ARP messages to send at a time after a lower priority advert is received when MASTER. Default: vrrp_garp_master_repeat.
vrrp_garp_lower_prio_delay 10 — delay for the second set of gratuitous ARPs after a lower priority advert is received when MASTER. Default: vrrp_garp_master_delay.
vrrp_garp_master_refresh 60 — minimum time interval for refreshing gratuitous ARPs while MASTER, in seconds (resolution: seconds). Default: 0 (no refreshing).
vrrp_garp_master_refresh_repeat 2 — number of gratuitous ARP messages to send at a time while MASTER. Default: 1.
vrrp_garp_interval 0.001 — delay between gratuitous ARP messages sent on an interface. Decimal, seconds (resolution: usecs). Default: 0.
vrrp_gna_interval 0.000001 — delay between unsolicited NA messages sent on an interface. Decimal, seconds (resolution: usecs). Default: 0. Look at all those zeroes, she's precise. 🎀
vrrp_min_garp [<BOOL>] — by default keepalived sends 5 gratuitous ARP/NA messages at a time, and after transitioning to MASTER sends a second block of 5 messages 5 seconds later. With modern switches this is unnecessary, so setting vrrp_min_garp causes only one ARP/NA message to be sent, with no repeat 5 seconds later. Less is more, slay. 💅
vrrp_garp_extra_if [all] 100 — the following option causes periodic GARP/NA messages to be sent on interfaces of VIPs/eVIPs that are not the interface of the VRRP instance, in order to ensure that switch MAC caches are maintained (specified in seconds).
Many switches have a default cache timeout of 300 seconds, and so a garp repeat rate of 1/3rd of that would be sensible. The maximum permitted value is 1 day (86400 seconds).
By default, it will only send on VMAC interfaces; specifying all will cause it to send GARP/NA on each interface used by the VRRP instance. 🌸
🎚️ VRRP tuning knobs
vrrp_down_timer_adverts [1:100] — default value for vrrp down_timer_adverts.
vrrp_lower_prio_no_advert [<BOOL>] — if a lower priority advert is received, don't send another advert. This causes adherence to the RFCs prior to RFC9568. Defaults to false, unless strict_mode is set.
vrrp_higher_prio_send_advert [<BOOL>] — if we are master and receive a higher priority advert, send an advert (which will be lower priority than the other master), before we transition to backup. This means that if the other master has garp_lower_priority_repeat set, it will resend garp messages. The whole point is to get around the problem of there having been two simultaneous masters, and the last GARP messages seen were from us. Awkward. 😭
vrrp_version <2 or 3> — set the default VRRP version to use. Default: 2, but IPv6 instances will use version 3.
v3_checksum_as_v2 [<BOOL>] — see the vrrp_instance description of V3_checksum_as_V2.
🔥 the firewall situation (nftables vs iptables)
keepalived uses a firewall (either nftables or iptables) for two purposes:
- To implement
no_acceptmode - To stop IGMP/MLD/Router-Solicit packets being sent on VMAC interfaces, and to move IGMP/MLD messages onto the underlying interface
If both vrrp_iptables and vrrp_nftables are specified, keepalived will use nftables and not iptables. Similarly, if the iptables command is generating nftables configuration, or there is no iptables command installed, keepalived will use nftables rather than iptables.
If neither vrrp_nftables or vrrp_iptables are specified but VMACs are in use or no_accept is specified, keepalived will use nftables if it is available. Resourceful queen. 👑
nftables [TABLENAME] — use nftables as the firewall.
TABLENAMEmust not exist, and must be different for each instance of keepalived running in the same network namespace.- Default tablename is
keepalived, and priority is -1. - keepalived will create base chains in the table.
countersmeans counters are added to the rules (primarily for debugging purposes).ifindexmeans create IPv6 link local sets using ifindex rather than ifnames. This is the default unless thevrrp_instancehas setdont_track_primary. The alternative is to use interface names as part of the set key, but thenftutility prior tov0.8.3will then not output interface names properly. (Rude of it, honestly.) 💀
nftables_priority PRIORITY — sets the priority.
nftables_counters — counters are added to the rules (debugging, bestie).
nftables_ifindex — create IPv6 link local sets using ifindex rather than ifnames.
nftables [TABLENAME]
nftables_priority PRIORITY
nftables_counters
nftables_ifindex
Firewall/fwmark time, bestie. 💅 This next chunk is all about how keepalived plays with nftables, iptables, ipsets, and then the process-tuning glow-up.
nftables_ipvs [TABLENAME]— sets the nftables IPVS table name, optionally asTABLENAME.nftables_ipvs_priority PRIORITY— sets the nftables IPVS priority toPRIORITY.nftables_ipvs_start_fwmark NUMBER— sets the first fwmark keepalived should use, and the default is1000. It gets incremented for each subsequent virtual server group.
Okay so for the IPVS fwmark vibe: keepalived will allocate a fwmark for each virtual server group, so that only one virtual server for each group needs to be configured in IPVS by using a fwmark. Then nftables is used to set the fwmark for each of the virtual server address/protocol/port combinations specified.
vrrp_iptables keepalived— uses iptables as the firewall, withkeepalivedas the chain. The default isINPUT.vrrp_iptables keepalived_in keepalived_out— uses iptables for outbound filtering too, withkeepalived_inandkeepalived_outas the chains.vrrp_iptables— no args, so it uses the default chainsINPUTandOUTPUT.
Some iptables tea ☕: it is necessary for the specified chain to exist in the iptables and/or ip6tables configuration, and for the chain to be called from an appropriate point in the iptables configuration. It will probably be necessary to have this filtering after accepting any ESTABLISHED,RELATED packets, because IPv4 might select the VIP as the source address for outgoing connections.
Also, although the default chains that are used are INPUT and OUTPUT, since those are the only chains that will always exist, it is not safe or sensible to use those chains. Specific chains should be created and called from appropriate points in the iptables configuration. The chains used for keepalived should not be used for any other purpose, and should have no rules configured other than the rules that keepalived manages. A startup_script (see above) can be used to create the chains and to add rules to call them. A shutdown_script can be used to remove the iptables configuration added by the startup_script.
And the second note, because of course there's a second note: if using ipsets, the iptables VIP rules are appended to the end of the specified chains. If not using ipsets, the VIP rules are inserted at the beginning of the chains. Any IGMP rules are always appended to the end of the chains.
Outbound filtering has a catch: it won't work with IPv4, since the VIP can be selected as the source address for an outgoing connection. With IPv6 this is unlikely since the addresses are deprecated.
vrrp_ipsets [keepalived [keepalived6 [keepalived_if6 [keepalived_igmp [keepalived_mld [keepalived_vmac_nd]]]]]]— Keepalived may have the option to use ipsets in conjunction with iptables. If so, the ipset names can be specified, with the defaults shown here. If no names are specified, ipsets will not be used. Otherwise any omitted names will be constructed by adding"_if"and/or"6"and_igmp/_mld/_ndto previously specified names.
The default ipset names are keepalived, keepalived6, keepalived_if6, keepalived_igmp, keepalived_mld, and keepalived_vmac_nd.
disable_local_igmp— an alternative to moving IGMP messages from VMACs to their parent interfaces is to disable them altogether in the kernel by settingigmp_link_local_mcast_reports false. This stops IGMP join etc messages for224.0.0.0/24, since they should always be forwarded to all interfaces (see RFC4541). This is available from Linux 4.3 onwards.vrrp_check_unicast_src— enables checking that, when in unicast mode, the source address of a VRRP packet is one of our unicast peers.vrrp_skip_check_adv_addr— checking all the addresses in a received VRRP advert can be time consuming. Setting this flag means the check won't be carried out if the advert is from the same master router as the previous advert received. The default is don't skip.vrrp_strict— enforces strict VRRP protocol compliance. This currently includes enforcing the following, and please note other checks may be added in the future if they are found to be missing:0VIPs not allowed- unicast peers not allowed
- IPv6 addresses not allowed in VRRP version 2
- First IPv6 VIP is link local
- State
MASTERcan be configured if and only if priority is255 - Authentication is not supported
- Preempt delay is not supported
- Accept mode cannot be set for VRRPv2
- If accept/no accept is not specified, accept is set if priority is
255and cleared otherwise - Gratuitous ARP repeats cannot be enabled
- Cannot clear
lower_prio_no_advert - Cannot set
higher_prio_send_advert - Cannot use
vmac_xmit_base - Cannot have no VIPs with VRRPv3
- If priority is
255, ignore received adverts
vrrp_notify_priority_changes <BOOL>— send vrrp instance priority notifications on notify FIFOs.
The following options can be used if vrrp, checker or bfd processes are timing out. You can see this by a backup vrrp instance becoming master even when the master is still running, because the master or backup system is too busy to process vrrp packets. 😭
Keepalived can, if it detects that it is not running sufficiently soon after a timer should expire, increase its priority. First of all it switches to realtime scheduling, and if that is not sufficient, it will then increase its realtime priority by one each time it detects a further delay in running. If realtime scheduling is enabled, RLIMIT_RTTIME will be set using the values for {bfd,checker,vrrp}_rlimit_rttime (see below). These values may need to be increased for slower processors.
max_auto_priority [<-1 to 99>]— limits the maximum increased automatic priority.0doesn't use automatic priority increases, and is the default.-1disables the warning message at startup. Omitting the priority sets the maximum value. And yes,99is reallysched_get_priority_max(SCHED_RR).min_auto_priority_delay <delay in usecs>— minimum delay in microseconds after the timer expires before keepalived is scheduled, after which the process priority will be auto incremented. The default is1000000usecs (1 second), maximum is10000000(10 seconds).vrrp_priority <-20 to 19>— set the vrrp child process priority. Negative values increase priority.checker_priority <-20 to 19>— set the checker child process priority.bfd_priority <-20 to 19>— set the BFD child process priority.vrrp_no_swap— set the vrrp child process non swappable.checker_no_swap— set the checker child process non swappable.bfd_no_swap— set the BFD child process non swappable.
The following options can force vrrp, checker and bfd processes to run on a restricted CPU set. You can either bind processes to a single CPU or define a set of CPUs. In that last case the Linux kernel will be restricted to that CPU set during scheduling. Forcing process binding to a single CPU can increase performance on a heavy-loaded box. The INTEGER following each configuration keyword represents cpu_id as shown in /proc/cpuinfo on line "processor:".
vrrp_cpu_affinity <INTEGER> [<INTERGER>]...[<INTEGER>]— set CPU affinity for the vrrp child process.checker_cpu_affinity <INTEGER> [<INTERGER>]...[<INTEGER>]— set CPU affinity for the checker child process.bfd_cpu_affinity <INTEGER> [<INTERGER>]...[<INTEGER>]— set CPU affinity for the bfd child process.vrrp_rt_priority <1..99>— set the vrrp child process to use real-time scheduling at the specified priority.checker_rt_priority <1..99>— set the checker child process to use real-time scheduling at the specified priority.bfd_rt_priority <1..99>— set the BFD child process to use real-time scheduling at the specified priority.vrrp_rlimit_rttime >=2— set the limit on CPU time between blocking system calls, in microseconds. Default is10000.checker_rlimit_rttime >=2— same limit for the checker process, in microseconds. Default is10000.bfd_rlimit_rttime >=2— same limit for the BFD process, in microseconds. Default is10000.
If Keepalived has been built with SNMP support, these keywords are available. Note that Keepalived, checker and RFC support can be individually enabled/disabled. ✨
snmp_socket udp:1.2.3.4:705— specify the socket to use for connecting to the SNMP master agent (see source modulekeepalived/vrrp/vrrp_snmp.cfor more details). The default isunix:/var/agentx/master.enable_snmp_vrrp— enable SNMP handling of the vrrp element of the KEEPALIVED MIB.enable_snmp_checker— enable SNMP handling of the checker element of the KEEPALIVED MIB.enable_snmp_rfc— enable SNMP handling of RFC2787 and RFC6527 VRRP MIBs.enable_snmp_rfcv2— enable SNMP handling of RFC2787 VRRP MIB.enable_snmp_rfcv3— enable SNMP handling of RFC6527 VRRP MIB.enable_traps— enable SNMP traps.snmp_vs_stats_update_interval <TIMER>— when SNMP requests are made, the checker process only updates the virtual and real server stats from the kernel if the last time the stats for that virtual server were read was more than this configured interval (in seconds). The default interval is5seconds, and the valid range is0.001(1 milli-second) to30seconds.snmp_rs_stats_update_interval <TIMER>— likesnmp_vs_stats_update_intervalbut for real servers. Stats for real servers are only read if there is an SNMP request for real server stats.
If Keepalived has been built with DBus support, these keywords are available.
enable_dbus— enable the DBus interface.dbus_service_name SERVICE_NAME— name of the DBus service. Useful if you want to run multiple keepalived processes with DBus enabled. The default isorg.keepalived.Vrrp1.dbus_no_interface_name NAME— string to use for the DBus path when a VRRP instance has no interface configured. Useful if your system has an interface named"none"! The default is"none".script_user username [groupname]— specify the default username/groupname to run scripts under. If this option is not specified, the user defaults tokeepalived_scriptif that user exists, otherwise the uid/gid under which keepalived is running. Ifgroupnameis not specified, it defaults to the user's group.enable_script_security— the safety girlie of the script world 🛡️. Turn this on and keepalived straight-up refuses to run any script configured to run as root if any part of the path is writable by a non-root user. It also enforces that the defaultscript_useriskeepalived_script, and stops defaulting to the user keepalived is running as (which is, like, usually root). No cap, it's giving responsible.
the fifo stuff 🎀
ok so before we go off — the FIFO names must all be different. Literally all of them. Not me having to say this twice 💀.
notify_fifo FIFO_NAME [username [groupname]]— instead of using notify scripts, you can give keepalived a FIFO to write notify events to. It's way more efficient at processing them, and it guarantees they get delivered in the correct sequence. Slay. ✨ For the format of what gets written, go look atvrrp_notify_fifoandlvs_notify_fifo; the full deets live undervrrp_sync_group. Also there'sdoc/samples/sample_notify_fifo.shif you want a sample to copy from 🫶.notify_fifo_script STRING|QUOTED-STRING [username [groupname]]— a script that keepalived runs to process the notify events for you. The FIFO name gets passed to the script as its last parameter 🧚.vrrp_notify_fifo FIFO_NAME [username [groupname]]— a FIFO just for vrrp notify events. What gets written is a line like this:
INSTANCE "VI_1" MASTER 100
and it's terminated with a new line character. For the full breakdown of the output, check the description under vrrp_sync_group, plus doc/samples/sample_notify_fifo.sh for a sample. 👑
vrrp_notify_fifo_script STRING|QUOTED-STRING [username [groupname]]— the script keepalived runs to handle those vrrp notify events. FIFO name goes in as the last parameter, as always 💅.lvs_notify_fifo FIFO_NAME [username [groupname]]— a FIFO for healthchecker notify events. The strings written look like:
VS [192.168.201.15]:tcp:80 {UP|DOWN}
RS [1.2.3.4]:tcp:80 [192.168.201.15]:tcp:80 {UP|DOWN}
each terminated with a new line character ✨.
lvs_notify_fifo_script STRING|QUOTED-STRING [username [groupname]]— the script that processes healthchecker notify events. FIFO name = last parameter, you know the vibe 🦋.fifo_write_vrrp_states_on_reload— lowkey important one 👀. By default, when keepalived reloads, the vrrp instance and sync group states are not written to the relevant FIFOs. Set this and the states do get sent to the FIFO(s) on reload. Periodt.
dynamic_interfaces [allow_if_changes]— lets your config include interfaces that don't exist at startup yet. This means keepalived can work with interfaces that get deleted and restored, and it also lets you put virtual and static routes and rules on VMAC interfaces 🔥. The optionalallow_if_changeslets an interface be deleted and recreated with a different type or underlying interface — like changing fromvlantomacvlan, or moving amacvlanfrometh1toeth2. It's predominantly used for reporting duplicate VRID errors at startup ifallow_if_changesisn't set 🧚.
netlink buffer sizes (aka the big-config girlies) 💜
these next ones are only needed for large configurations — either keepalived creates a huge number of interfaces, or the system has a huge number of interfaces. You only need them if you're seeing"Netlink: Receive buffer overrun"messages in the system logs. And if the buffer size you need exceeds the value in/proc/sys/net/core/rmem_max, you'll have to set the correspondingforceoption too 💀.
vrrp_netlink_cmd_rcv_bufs BYTES— set the netlink receive buffer size. Useful for very large configs where a big pile of interfaces exist and the initial read of all of them causes a netlink buffer overrun.vrrp_netlink_cmd_rcv_bufs_force <BOOL>— the force flag for the above.vrrp_netlink_monitor_rcv_bufs BYTES— same deal, for the monitor socket.vrrp_netlink_monitor_rcv_bufs_force <BOOL>— force flag for the monitor socket.
So, the vrrp netlink command socket, the monitor socket, the checker command socket, the checker monitor socket, and the process monitor buffer sizes can each be set independently ✨. The force flag means "use SO_RCVBUFFORCE", which is what lets the buffer size go above /proc/sys/net/core/rmem_max 👑
lvs_netlink_cmd_rcv_bufs BYTES— checker command socket buffer size.lvs_netlink_cmd_rcv_bufs_force <BOOL>— force it abovermem_max.lvs_netlink_monitor_rcv_bufs BYTES— checker monitor socket buffer size.lvs_netlink_monitor_rcv_bufs_force <BOOL>— force it.process_monitor_rcv_bufs BYTES— process monitor buffer size. As a guide: for 1400 processes terminating simultaneously,212992(the default on some systems) is not enough, whereas500000is sufficient 🫶.process_monitor_rcv_bufs_force <BOOL>— force it abovermem_max.
vrrp_rx_bufs_policy [MTU|ADVERT|NUMBER]— ok so when a socket is opened, the kernel sets the max rx buffer size for it to/proc/sys/net/core/rmem_default. On some systems that can be very large, and even generally it can be way bigger than necessary 😭. Not a problem as long as keepalived is reading all queued data from its sockets — but ifrmem_defaultwas set sufficiently large and keepalived for some reason stopped reading, it could legitimately eat all your system memory. So this option lets you configure the rx buf size when the sockets are opened:- if the policy is
MTU, the rx buf size becomes the total of the interface's MTU ×vrrp_rx_bufs_multiplierfor each vrrp instance using that socket; - if the policy is
ADVERT, it's the total of each vrrp instance's advert packet size × the multiplier; - or just give it a
NUMBER. Default: use the system default ⭐.
- if the policy is
vrrp_rx_bufs_multiplier NUMBER— the multiplier for the above. Default:3💅.
rs_init_notifies— send notifies at startup for real servers that are starting up 🌈.no_checker_emails— don't spam an email every time a real server checker changes state; only send email when a real server is added or removed. Your inbox will thank you 🎀.umask [NUMBER|BITS]— the umask used when creating files. The number can be given in hex, octal or decimal.BITSareI{R|W|X}{USR|GRP|OTH}, e.g.IRGRP, separated by'|'s. You can also just sayIRWX{U|G|O}. The default umask isIXUSR | IRWXG | IRWXO. Heads up: this option cannot override the command-line option 👀.
vrrp_startup_delay 5.5— on some systems, when bond interfaces are created they can start passing traffic and then have a several-second gap where they stop passing traffic inbound 😭. That means if keepalived starts at boot time (i.e. right as the bonds are being created), it won't receive adverts and can become master even though an instance with a higher priority is out there sending adverts. This option sets a delay in seconds before vrrp instances start up after keepalived starts. See alsovrrp_delay_after_bootbelow.vrrp_delay_after_boot 15.23— probably a better alternative tovrrp_startup_delay, ngl 🫶. This one ensures vrrp instances are delayed from starting until at least the specified number of seconds after the system boots. So when keepalived starts at boot, it can hold off the VRRP instances until that time has elapsed — but if keepalived is stopped and restarted later, there's no delay at all. keepalived will use the longer ofvrrp_startup_delayandvrrp_delay_after_boot✨.log_unknown_vrids— causes logging of receipt of VRRP adverts for VRIDs that aren't configured on the interface they're received on 👑.vmac_prefix STRING— set the prefix for generated VMAC names. Default:"vrrp"🦋.vmac_addr_prefix STRING— set the prefix for generated VMAC names for VIPs that use a VMAC but aren't on the VRRP instance's interface. Default: thevmac_prefixvalue 💅.random_seed UNSIGNED_INT— set the random seed for${_RANDOM}so your configs are repeatable. Default is to use a time-based seed, which means every time you get a different generated configuration 🌸.reload_check_config [LOG_FILE]— real talk: if you try to reload with an updated config file that has errors, keepalived may terminate, and possibly loop forever restarting and terminating 💀. With this set, keepalived validates the configuration before starting the reload, and only actually reloads if it's valid. You can optionally pass aLOG_FILE🔥.
include_check [OPTIONS]— treat any missing include file as an error. TheOPTIONScan be any combination of:readable— error if a match is not a readable filematch— error if no file matches (unless a wildcard is specified)wildcard_match— error if no file matches (even if a wildcard is specified)brace_match— error if a brace expansion does not match a file
Note: match, wildcard_match and brace_match all include the readable check 🫶.
The setting of include_check is saved when a new include file is opened, and restored when that file is closed. Which means: the include_check setting while reading a file cannot be changed by a file that gets included after it 👀. If you want to change the setting for all included files, set include_check at the beginning of the configuration file specified on the command line (default /etc/keepalived/keepalived.conf).
Note2: if the libc glob() function doesn't support GLOB_ALTDIRFUNC (e.g. Musl libc, as on Alpine Linux etc.), then only readable and wildcard_match of the above options will work 😭.
You can add or remove individual settings — '+' means add the following checks, '-' means remove the following checks. Like:
include_check +match -wildcard_match
which adds the requirement that there's a matching file, and removes the requirement for wildcard matches 💖.
If no option is specified, it's the same as specifying all options. Periodt ✨.
# reload_time_file allows a reload of keepalived to be scheduled in the future. This is
# particularly useful if there is a master keepalived and one or more backup keepalived
# instances and the new configuration is incompatible with the previous configuration,
# e.g. adding or removing VIPs which would cause adverts to be rejected.
# All the instances can be scheduled to reload at the same time, thereby ensuring that
# no mismatching adverts are received by the backup instances.
# The configuration specifies a file which keepalived will monitor. The first line of
# the file must contain a valid time or date/time exactly in the formats specified below.
# When keepalived starts up, it reads the file if it exists, and schedules a reload at
# the specified time. If the file does not exist, then when it is subsequently created
# a reload will be scheduled. If the file is updated, the reload time will be modified
# accordingly. If the file is deleted, the reload is cancelled.
# Normally when the reload occurs the specified file is deleted, since the reload has
# been done; if the file included a date then the reload will be in the past and so
# ignored. However, if there is no date, then if the file were reread following the
# reload, a reload would be scheduled for 24 hours time. In order to stop this, the
# file is deleted (unlinked) by default. If reload_repeat is specified, then the
# file is not deleted, and if the file contains a time only with no date, then
# keepalived will keep reloading at that time every day until the file is removed or
# modified.
# If the directory containing the file does not exist at startup/reload, or if the
# directory is removed or renamed, then no future scheduled reloads will occur until
# a manual (SIGHUP) reload is done or keepalived restarts.
# The permitted formats of the entry in the timer file are precisely:
# HH:MM:SS
# YY-MM-DD HH:MM:SS
# YYYY-MM-DD HH:MM:SS
# each with an optional 'Z' at the end.
# There must be no leading or trailing whitespace, and only one space between the date
# and the time.
# If there is a 'Z' at the end of the time, the time is parsed as UTC, otherwise the
# time is the localtime for the environment in which keepalived is running. If the
# systems which are being reloaded are in different timezones, it is probably safer to
# use UTC.
# If using local time with daylight savings, beware that some times don't exist and
# some times are duplicated and hence ambiguous.
reload_time_file ABSOLUTE-PATHNAME-OF-FILE
reload_repeat
# Some users frequently update their configurations and reload keepalived. reload_file
# provides a mechanism that allows the configuration update processes not to update the
# configuration files while keepalived is reading them.
# The reload file will be created by keepalived before it starts reading configuration
# files, unless the file exists. If the file already exists, it will be truncated. Once
# keepalived has completed reading the files it will remove the reload file.
# If reload_file with no file name is specified, the default filename keepalived.reload
# in the PID directory will be used.
# The best way to use the reload file is for the configuration update process to touch
# the reload file before it signals keepalived to reload, and then wait for the file
# to be deleted, which indicates that keepalived has finished reading the config files.
# When keepalived starts reading the configuration files, since it truncates the reload
# file, if update process creates the reload_file with non-zero size, it can detect
# the reloading starting by the reload_file becoming zero length.
reload_file [ABSOLUTE-PATHNAME-OF-FILE]
# Sending SIGUSR1 to keepalived causes it to dump its data structures
# for debugging purposes, although some users use this feature and
# process the output. Please note that the format of the .data files
# produced is not guaranteed to maintain backward compatibility.
# The standard file names are keepalived_parent.data, keepalived.data,
# keepalived_check.data and keepalived_bfd.data. This causes a problem
# if more than one keepalived instance is running on a system.
# In order to alleviate this, enabling data_use_instance includes the
# instance name and network namespace in the file name of the .data files.
# This also applies to SIGUSR2 for outputting stats.
data_use_instance [<BOOL>]
# If the files produced by SIGUSR1 and SIGUSR2 are on slow storage, then writing to
# them may cause keepalived processes to block. This option allows specifying the
# location to which the files should be written, and should ideally be a tmpfs rather
# than a real disc, and definitely not network attached storage or other storage that
# can block for more than a few microseconds.
# If path ends in a / it is considered to be a directory and the normal filenames
# in that directory will be used, otherwise it will be used as the full path name
# of the file to be written. Note that data_use_instance may also modify the
# file name.
# For the state files, these are template names and "_checker" will be added
# for the checker process, "_bfd" will be added for the BFD process and
# "_parent" will be added for the parent process.
state_file_location path
stats_file_location path
json_file_location path
# json_version 2 puts the VRRP data in a named array and adds
# track_process details. Default is version 1.
json_version {1|2}
# iproute can use two directories for its configuration files, with files in
# /etc/iproute2 overriding files in /usr/share/iproute2.
# ip (the package that provides ip route functionality) has configure options
# for the location of these directories. keepalived's configure will attempt
# to find the locations either from the ip-route man page or the ip utility
# itself. In case it cannot do so, the locations may need to be specified here.
iproute_usr_dir path
iproute_etc_dir path
}
okay so bestie, let's talk about reload_time_file because it is actually iconic 💅. It lets you schedule a future reload of keepalived — yes, a reload, in the future, on purpose, at a time you pick. Why would you want that? Picture it: you've got a master keepalived and one or more backup keepalived instances, and your shiny new configuration is not compatible with the old one (adding or removing VIPs, for example, which would make adverts get rejected 😭). With this you can schedule all the instances to reload at the same exact time, and that guarantees no mismatching adverts land on the backup instances. coordinated queen behaviour ✨.
The way it works is you point it at a file and keepalived monitors that file. The first line of the file must hold a valid time, or a date/time, exactly in the formats listed below (no creative formatting, bestie, keepalived is not reading your mind). On startup keepalived reads the file if it exists and schedules a reload at the time it found. If the file does not exist, then whenever it gets created later, a reload gets scheduled. If you update the file, the reload time updates with it. If you delete the file, the reload is cancelled. clean, right? 🎀
Now here's the part everyone gets confused about. Normally when the reload happens, that file gets deleted, because the reload has been done — and if the file contained a date, the reload time would be in the past anyway so it'd just be ignored. But if the file has no date, then rereading it after the reload would schedule a reload for 24 hours from then, forever, like a cursed little loop 💀. So to stop that, the file is deleted (unlinked) by default. If you specify reload_repeat, the file is not deleted, and if it holds a time only with no date, keepalived just keeps reloading at that time every single day until you remove or modify the file. it's giving alarm clock ⏰.
heads up: if the directory containing the file doesn't exist at startup/reload, or if that directory gets removed or renamed, then no future scheduled reloads will happen until you do a manual (SIGHUP) reload or restart keepalived. no cap, don't move directories out from under it 🙈.
The permitted formats in the timer file are, precisely:
HH:MM:SS
YY-MM-DD HH:MM:SS
YYYY-MM-DD HH:MM:SS
each with an optional Z at the end. There must be no leading or trailing whitespace, and only one space between the date and the time. if you add a sneaky extra space bestie I cannot help you 😭.
If there's a Z at the end of the time, it's parsed as UTC; otherwise it's the localtime for the environment keepalived is running in. If the systems being reloaded live in different timezones it is probably safer to use UTC 🌍. And if you're using local time with daylight savings — beware, because some times don't exist and some times are duplicated and therefore ambiguous. timezones stay unserious 🫠.
reload_time_file ABSOLUTE-PATHNAME-OF-FILE
reload_repeat
Next up: reload_file. Some users update their configs and reload keepalived constantly (we respect the hustle), so this gives your configuration update processes a way to not write config files while keepalived is mid-read. no stepping on each other's toes 💖.
How it goes: keepalived creates the reload file before it starts reading configuration files — unless the file already exists, in which case it gets truncated. Once keepalived has finished reading the files, it removes the reload file. If you say reload_file with no file name, the default filename keepalived.reload in the PID directory gets used. convenient!
The best way to use it: your configuration update process touches the reload file before it signals keepalived to reload, then waits for the file to be deleted — which tells it keepalived has finished reading the config files. and when keepalived starts reading, since it truncates the reload file, an update process that created the reload file with non-zero size can detect that reloading has started by the reload file becoming zero length. little size-based vibe check, iykyk ⭐.
reload_file [ABSOLUTE-PATHNAME-OF-FILE]
🌈 some Global definitions, continued 🌈
data_use_instance time! Sending SIGUSR1 to keepalived makes it dump its data structures for debugging purposes, although some users use this feature and process the output — fair enough. Important though: the format of the .data files produced is not guaranteed to maintain backward compatibility, so don't build your whole personality on parsing them 😭.
The standard file names are keepalived_parent.data, keepalived.data, keepalived_check.data and keepalived_bfd.data. Which is a whole problem if more than one keepalived instance is running on a system — they'd all clash. To alleviate that, enabling data_use_instance includes the instance name and network namespace in the file name of the .data files. And this also applies to SIGUSR2 for outputting stats. slay 💅.
data_use_instance [<BOOL>]
Now, if the files produced by SIGUSR1 and SIGUSR2 are on slow storage, writing to them may cause keepalived processes to block — and nobody wants that. These location options let you specify where the files should be written. Ideally a tmpfs rather than a real disc, and definitely not network attached storage or any other storage that can block for more than a few microseconds 🙈.
If the path ends in a / it's considered to be a directory, and the normal filenames in that directory will be used; otherwise the path is used as the full path name of the file to be written. Note that data_use_instance may also modify the file name. For the state files, these are template names — "_checker" gets added for the checker process, "_bfd" gets added for the BFD process, and "_parent" gets added for the parent process 👑
state_file_location path
stats_file_location path
json_file_location path
json_version 2 puts the VRRP data in a named array and adds track_process details. Default is version 1 ✨.
json_version {1|2}
Last global bit: iproute can use two directories for its configuration files, with files in /etc/iproute2 overriding files in /usr/share/iproute2. The ip package (that's the package providing ip route functionality) has configure options for where those directories live, and keepalived's configure will attempt to find the locations either from the ip-route man page or from the ip utility itself. In case it can't do that, you can specify the locations here 🫶.
iproute_usr_dir path
iproute_etc_dir path
and that's the closing } on the global definitions block, bestie. we did it 🎀
🦋 Linkbeat interfaces 🦋
okay so the linkbeat_interfaces block lets you say which interfaces should use polling via MII, Ethtool or ioctl status instead of relying on netlink status updates. It's the granular-control bestie of the global definition linkbeat_use_polling ✨.
This option is preferred over the deprecated use of linkbeat_use_polling inside a vrrp_instance block — because that deprecated one only lets you use linkbeat on the interface of the vrrp_instance itself, whereas track_interface and virtual_ipaddresses and virtual_iproutes may need to monitor other interfaces, which may need linkbeat polling too. see the problem? 👀
The default polling type is MII, unless that isn't supported, in which case ETHTOOL is used, and if that isn't supported then ioctl polling. You can state your preferred polling type with MII or ETHTOOL or IOCTL after the interface name — but if that type isn't supported, a supported type will be used instead. keepalived always finds a way 💅
The syntax for linkbeat_interfaces is:
linkbeat_interfaces {
eth2
enp2s0 ETHTOOL
}
🌸 Static track groups 🌸
Static track groups let vrrp instances track static addresses, routes and rules. So if a static address/route/rule specifies a track group, and that address/route/rule gets deleted and cannot be restored, the vrrp instance transitions to fault state. dramatic but correct 😭🔥
The syntax for a track group is:
track_group GROUP1 {
group {
VI_1
VI_2
}
}
✨ Static routes/addresses/rules ✨
Okay bestie, so here's the thing — keepalived can literally configure static addresses, routes, and rules for you. But listen: these addresses, routes and rules are NOT moved by vrrpd, they stay on the machine. Periodt. If you already have IPs and routes on your machines and your machines can ping each other, you don't need this section at all, skip it, run free 🦋.
The syntax for rules and routes is the same as for ip rule add/ip route add — except shortened option names are not supported, because ambiguity said no 💀.
The track_group specification refers to a named track_group which lists the vrrp instances which will track the address, i.e. if the address is deleted the vrrp instances will transition to backup. Iconic behaviour honestly.
NOTE: since rules without preferences can be added in different orders due to vrrp instances transitioning from master to backup etc, rules need to have a preference. If a preference is not specified, keepalived will assign one, but it will probably not be what you want 😭.
The syntax is the same for virtual addresses and virtual routes. If no dev element is specified, it defaults to default_interface (default eth0). Note: the broadcast address may be specified as - or + to clear or set the host bits of the address. Cute.
If a route or rule could apply to either IPv4 or IPv6 it will default to IPv4. To force a route/rule to be IPv6, add the keyword "inet6" 💅.
By default keepalived prepends routes (the kernel's default) which adds the route before any matching routes (this is the same behaviour as the (undocumented) as the ip route add command, which only adds the route if there is no matching route). If append is specified, the behaviour is the same as the ip route append command, i.e. the route is added after any matching route. Note: the rules for whether a route matches differ between IPv4 and IPv6; for example specifying a different proto means a matching route can be prepended/appended for IPv4 but not for IPv6. If in doubt, test it using the ip route add/prepend/append commands 🫶.
static_ipaddress {
<IPADDR>[/<MASK>] [brd <IPADDR>] [dev <STRING>] [scope <SCOPE>]
[label <LABEL>] [peer <IPADDR>] [home]
[-nodad] [mngtmpaddr] [noprefixroute]
[autojoin] [track_group GROUP] [preferred_lft nn|forever]
192.168.1.1/24 dev eth0 scope global
...
}
static_routes {
192.168.2.0/24 via 192.168.1.100 dev eth0 track_group GROUP1
192.168.100.0/24 table 6909 nexthop via 192.168.101.1 dev wlan0
onlink weight 1 nexthop via 192.168.101.2
dev wlan0 onlink weight 2
192.168.200.0/24 dev p33p1.2 table 6909 tos 0x04 protocol bird
scope link priority 12 mtu 1000 hoplimit 100
advmss 101 rtt 102 rttvar 103 reordering 104
window 105 cwnd 106 ssthresh lock 107 realms
PQA/0x14 rto_min 108 initcwnd 109 initrwnd 110
vrf blue features ecn add
2001:470:69e9:1:2::4 dev p33p1.2 table 6909 tos 0x04 protocol
bird scope link priority 12 mtu 1000
hoplimit 100 advmss 101 rtt 102 rttvar 103
reordering 104 window 105 cwnd 106 ssthresh
lock 107 rto_min 108 initcwnd 109 append
initrwnd 110 features ecn fastopen_no_cookie 1
...
}
static_rules {
from 192.168.2.0/24 table 1 track_group GROUP1
to 192.168.2.0/24 table 1
from 192.168.28.0/24 to 192.168.29.0/26 table small iif p33p1
oif wlan0 tos 22 fwmark 24/12
preference 39 realms 30/20 goto 40
to 1:2:3:4:5:6:7:0/112 from 7:6:5:4:3:2::/96 table 6908
uidrange 10000-19999
to 1:2:3:4:6:6:7:0/112 from 8:6:5:4:3:2::/96 l3mdev protocol 12
ip_proto UDP sport 10-20 dport 20-30
...
}
🌸 Track files 🌸
So this one adds a file to be monitored 👀. The file will be read whenever it is modified. The value in the file will be recorded for all VRRP instances, sync groups and real servers which monitor it. Note that the file will only be read if at least one VRRP instance, sync group or real server monitors it — no lurkers, no wasted reads.
A value will be read as a number in text from the file. If the weight configured against the track_file is 0, a non-zero value in the file will be treated as a failure status, and a zero value will be treated as an OK status, otherwise the value will be multiplied by the weight configured in the track_file statement. Math era 💅.
For VRRP instances, if the result is less than -253 anything monitoring the script will transition to the fault state (the weight can be 254 to allow for a negative value being read from the file).
If the vrrp instance or sync group is not the address owner and the result is between -253 and 253, the result will be added to the initial priority of the VRRP instance (a negative value will reduce the priority), although the effective priority will be limited to the range [1,254]. Likewise for real servers 🫶.
If a vrrp instance using a track_file is a member of a sync group, unless sync_group_tracking_weight is set on the group weight 0 must be set. Likewise, if the vrrp instance is the address owner, weight 0 must also be set. Rules are rules bestie ✨.
For real servers monitoring the file, the limits of values read from the track file are 2147483648 to -2147483648. The value, once multiplied by the weight, will be added to the real server's IPVS weight. If the result is <= than 2147483648 then the checker will be in the FAULT state.
NOTE: weights fortrack_filefor real servers are not fully implemented yet 😭. In particular allowing weight0, handling negative calculated values and reloading. Consider yourself warned fr fr.
The syntax for track file is:
track_file <STRING> { # vrrp_track_file is a deprecated synonym
# file to track (weight defaults to 1)
file <QUOTED-STRING>
# optional default weight
weight <-2147483647..2147483647> [reverse]
# create the file and/or initialise the value
# This causes VALUE (default 0) to be written to
# the specified file at startup if the file doesn't
# exist, unless overwrite is specified in which case
# any existing file contents will be overwritten with
# the specified value.
init_file [VALUE] [overwrite]
}
🌸 VRRP track processes 🌸
The block looks like this, bestie:
vrrp_track_process <STRING> {
# process to monitor (with optional parameters)
# A quoted string is treated as a single element, so if the first item
# after the process keyword is quoted, that will be the command name.
# For example:
# process "/tmp/a b" param1 "param 2"
# would mean a process named '/tmp/a b' (quotes removed) with 2 parameters
# 'param1' and 'param 2'.
process <STRING>|<QUOTED-STRING> [<STRING>|<QUOTED-STRING> ...]
# If matching parameters, this specifies a partial match (i.e. the first
# n parameters match exactly), or an initial match, i.e. the last
# parameter may be longer that the parameter configured.
# To specify that a command must have no parameters, don't specify
# any parameters, but specify param_match.
param_match {initial|partial}
# default weight (default is 0). For description of reverse, see track_process.
# 'weight 0 reverse' will cause the vrrp instance to be down when the
# quorum is up, and vice versa.
# A non-zero weight will adjust the VRRP priority of the tracking VRRP instance,
# whereas a 0 weight will cause the VRRP instance to enter FAULT state if the
# track process is in the failed state (see above for the effect of "reverse").
weight <-254..254> [reverse]
# minimum number of processes for success (default 1)
quorum NUM
# maximum number of processes for success. For example, setting
# this to 1 would cause a failure if two instances of the process
# were running (but beware forks - see fork_delay below).
# Setting this to 0 would mean failure if the matching process were
# running at all.
# Default is unlimited.
quorum_max NUM
# time to delay after process quorum gained after fork before
# consider process up (in fractions of second)
# This is to avoid up/down bounce for fork/exec
fork_delay SECS
# time to delay after process quorum lost before
# consider process down (in fractions of second)
# This is to avoid down/up bounce after terminate/parent refork.
terminate_delay SECS
# this sets fork_delay and terminate_delay
delay SECS
# Normally process string is matched against the process name,
# as shown on the Name: line in /proc/PID/status, unless
# parameters are specified.
# This option forces matching the full command line
full_command
}
So here's the whole ✨why✨ this exists: running a track_script over and over just to watch whether a process is alive is so much work — especially when that process is haproxy or nginx — and vrrp_track_process does that job for you instead. It watches whether other processes are running. Iconic, low-effort, chef's kiss.
One difference from pgrep is that track_process doesn't do a regular expression match of the command string — it does an exact match, fr fr. 'pgrep ssh' will match an sshd process, but this track_process will not (it is equivalent to pgrep "^ssh$"). Not the same vibe at all, no cap.
If full_command is used (equivalent to pgrep -f), /proc/PID/cmdline is used, but any updates to cmdline will not be detected (a process shouldn't normally change it, although it is possible with great care, for example systemd). 🙈
💀 Heads up: prior to Linux v3.2,track_processwill not support detection of changes to a process name, since the kernel did not notify changes of process name prior to 3.2. Most processes do not change their process name, but, for example, firefox forks processes that change their process name to"Web Content". The process name referred to here is the contents of/proc/PID/comm.
Quorum is the number of matching processes that must be run for an OK status.
Delay might be useful if it anticipated that a process may be reloaded (stopped and restarted), and it isn't desired to down and up a vrrp instance.
A positive weight means that an OK status will add <weight> to the priority of all VRRP instances which monitor it. On the opposite, a negative weight will be subtracted from the initial priority in case of insufficient processes.
If the vrrp instance or sync group is not the address owner and the result is between -253 and 253, the result will be added to the initial priority of the VRRP instance (a negative value will reduce the priority), although the effective priority will be limited to the range [1,254].
If a vrrp instance using a track_process is a member of a sync group, unless sync_group_tracking_weight is set on the group, weight 0 must be set. Likewise, if the vrrp instance is the address owner, weight 0 must also be set. 🫶
🍓 Rationale for not using pgrep/pidof/killall and the likes
Every time pgrep or its equivalent is run, it iterates though the /proc/[1-9][0-9]* directories, and opens the status and cmdline pseudo files in each directory. The cmdline pseudo file is mapped to the process's address space, and so if that part of the process is swapped out, it will have to be fetched from the swap space. pgrep etc also include zombie processes whereas keepalived does not, since they aren't running.
This implementation only iterates though /proc/[1-9][0-9]*/ directories at start up, and it won't even read the cmdline pseudo files if full_command is not specified for any of the vrrp_track_process entries. After startup, it uses the process_events kernel <-> userspace connector to receive notification of process changes. If full_command is specified for any track_process instance, the cmdline pseudo file will have to be read upon notification of the creation of the new process, but at that time it is very unlikely that it will have already been swapped out. Slay. 💅
On a busy system with a high number of process creations/terminations, using a track_script with pgrep/pidof/killall may be more efficient, although those processes are inefficient compared to the minimum that keepalived needs. Fair.
Using pgrep etc on a system that is swapping can have a significant detrimental impact on the performance of the system, due to having to fetch swapped memory from the swap space, thereby causing additional swapping. 😭 Do not recommend.
🦋 BFD CONFIGURATION 🦋
Okay so this is an implementation of RFC5880 (Bidirectional forwarding detection), and this can be configured to work between 2 keepalived instances — but using unweighted track_bfds between a master/backup pair of VRRP instances means that the VRRP instance will only be able to come up if both VRRP instances are running, which somewhat defeats the purpose of VRRP. 😬 Like, make it make sense.
This implementation has been tested with OpenBFDD (available at https://github.com/dyninc/OpenBFDD).
The syntax for a bfd instance is:
bfd_instance <STRING> {
# BFD Neighbor IP (synonym neighbour_ip)
neighbor_ip <IP ADDRESS>
# Source IP to use (optional, except in order to ensure that the
# local port is valid, it is required)
source_ip <IP ADDRESS>
# Required min RX interval, in ms (resolution is micro-seconds e.g. 3.312)
# (default is 10 ms)
min_rx <DECIMAL>
# Desired min TX interval, in ms (resolution is micro-seconds)
# (default is 10 ms)
min_tx <DECIMAL>
# Desired idle TX interval, in ms (resolution is micro-seconds)
# (default is 1000 ms)
idle_tx <DECIMAL>
# Number of missed packets after
# which the session is declared down
# (default is 5)
multiplier <INTEGER>
# Operate in passive mode (default is active)
passive
# outgoing IPv4 ttl to use (default 255)
ttl <INTEGER>
# outgoing IPv6 hoplimit to use (default 64)
hoplimit <INTEGER>
# maximum reduction of ttl/hoplimit
# in received packet (default 0)
# (255 disables hop count checking)
max_hops <INTEGER>
# RFC 5883 specifies port 4784 must be used for multihop bfd, rather than
# port 3784. Specifying multihop enables that option, but if multiple hops
# are in use, then max_hops (see above) will also need to be configured.
multihop [<BOOL>]
# Default tracking weight
# Normally, positive weights are added to the vrrp instance priority when
# the bfd instance is up, negative weights reduce the priority when it is down.
# However, if reverse is specified, a positive weight decreases the priority the
# script is up, and a negative weight increases the priority when the script is
# 'weight 0 reverse' will cause the vrrp instance to be down when the bfd
# instance is up, and vice versa.
# Weight Reverse Script up Script down
# +ve No prio + -
# -ve No - prio -
# +ve Yes prio - -
# -ve Yes - prio +
weight <-253:253> [reverse]
# Normally bfd event notifications are sent to both the VRRP and checker processes.
# Specifying vrrp or checker will cause event notifications for this bfd_instance
# only to be sent to the specified process
vrrp
checker
}
👑 VRRPD CONFIGURATION 👑
This one contains subblocks of VRRP script(s), VRRP synchronization group(s), VRRP gratuitous ARP and unsolicited neighbour advert delay group(s) and VRRP instance(s). Big main character energy — we'll get into all of it next. 🌈
🌸 VRRP script(s) 🌸
okay bestie, so a VRRP script is literally this little icon that gets run on a loop — every <interval> seconds, to be exact. Its exit code gets recorded for all the VRRP instances that are monitoring it. But here's the vibe check: the script only runs if at least one VRRP instance is monitoring it. No watchers, no execution. periodt.
The default weight equals 0, which means any VRRP instance monitoring the script will transition to the fault state after <fall> consecutive failures of the script. After that, <rise> consecutive successes will cause VRRP instances to leave the fault state — unless they're also in the fault state due to other scripts or interfaces that they're tracking. Not me explaining the whole fault-state lore 😭.
A positive weight means that <rise> successes will add <weight> to the priority of all VRRP instances which monitor it. On the opposite, a negative weight will be subtracted from the initial priority in case of <fall> failures.
The syntax for the vrrp script is:
# Adds a script to be executed periodically. Its exit code will be
# recorded for all VRRP instances and sync groups which are monitoring it.
vrrp_script <SCRIPT_NAME> {
# path of the script to execute
script <STRING>|<QUOTED-STRING>
# seconds between script invocations, (default: 1 second, resolution milliseconds)
interval <DECIMAL>
# seconds after which script is considered to have failed (default = interval, resolution milliseconds)
timeout <DECIMAL>
# adjust priority by this weight, (default: 0)
# For description of reverse, see track_script.
# 'weight 0 reverse' will cause the vrrp instance to be down when the
# script is up, and vice versa.
weight <INTEGER:-253..253> [reverse]
# required number of successes for OK transition
rise <INTEGER>
# required number of successes for KO transition
fall <INTEGER>
# user/group names to run script under.
# group default to group of user
user USERNAME [GROUPNAME]
# assume script initially is in failed state
init_fail
}
💖 VRRP synchronization group(s) 💖
VRRP Sync Group is an extension to VRRP protocol. The main goal is to define a bundle of VRRP instance to get synchronized together so that transition of one instance will be reflected to others group members. it's giving squad goals, but make it network redundancy 💅.
In addition there is an enhanced notify feature for fine state transition catching.
You can also define multiple track policy in order to force state transition according to a third party event such as interface, scripts, file, BFD.
Important: for a SYNC group to run reliably, it is vital that all instances in the group are MASTER or that they are all either BACKUP or FAULT. A situation where some instances have higher priority on machine A and others have higher priority on machine B will lead to constant re-elections. For this reason, when instances are grouped, any track scripts/files configured against member VRRP instances must have their tracking weights unset (i.e. equal to zero). Any trackers with a non-zero priority will be ignored.
The syntax for vrrp_sync_group is :
vrrp_sync_group <STRING> {
group {
# name of the vrrp_instance (see below)
# Set of VRRP_Instance string
<STRING>
<STRING>
...
}
# Synchronization group tracking interface, script, file & bfd will
# update the status/priority of all VRRP instances which are members
# of the sync group.
# 'weight 0 reverse' will cause the vrrp instance to be down when the
# interface is up, and vice versa.
track_interface {
eth0
eth1
eth2 weight <-253..253> [reverse]
...
}
# add a tracking script to the sync group (<SCRIPT_NAME> is the name
# of the vrrp_script entry) go to FAULT state if any of these go down
# if unweighted.
# reverse causes the direction of the adjustment of the priority to be reversed.
track_script {
<SCRIPT_NAME>
<SCRIPT_NAME> weight <-253..253> [reverse|noreverse]
}
# Files whose state we monitor, value is added to effective priority.
# <STRING> is the name of a track_file
# weight defaults to weight configured in track_file
track_file {
<STRING>
<STRING> weight <-254..254> [reverse|noreverse]
...
}
# Process to monitor, weight is added to effective priority.
# <STRING> is the name of a vrrp_track_process
# weight defaults to weight configured in vrrp_track_process.
# See vrrp_instance track_process for description of weight.
track_process {
<STRING>
<STRING> weight <-254..254> [reverse|noreverse]
...
}
# BFD instances we monitor, value is added to effective priority.
# <STRING> is the name of a BFD instance
track_bfd {
<STRING>
<STRING>
<STRING> weight <INTEGER: -253..253> [reverse|noreverse]
...
}
# notify scripts and alerts are optional
#
# filenames of scripts to run on transitions can be unquoted (if
# just filename) or quoted (if it has parameters)
# The username and groupname specify the user and group
# under which the scripts should be run. If username is
# specified, the group defaults to the group of the user.
# If username is not specified, they default to the
# global script_user and script_group
# to MASTER transition
notify_master /path/to_master.sh [username [groupname]]
# to BACKUP transition
notify_backup /path/to_backup.sh [username [groupname]]
# FAULT transition
notify_fault "/path/fault.sh VG_1" [username [groupname]]
# executed when stopping vrrp
notify_stop <STRING>|<QUOTED-STRING> [username [groupname]]
# notify_deleted causes DELETED to be sent to notifies rather
# than the default FAULT after a vrrp instance is deleted during a
# reload. If a script is specified, that script will be executed
# as well.
notify_deleted [<STRING>|<QUOTED-STRING> [username [groupname]]]
# for ANY state transition.
# "notify" script is called AFTER the notify_* script(s) and
# is executed with 4 additional arguments after the configured
# arguments provided by Keepalived:
# $(n-3) = "GROUP"|"INSTANCE"
# $(n-2) = name of the group or instance
# $(n-1) = target state of transition (stop only applies to instances)
# ("MASTER"|"BACKUP"|"FAULT"|"STOP"|"DELETED")
# $(n) = priority value
# $(n-3) and $(n-1) are ALWAYS sent in uppercase, and the possible
# strings sent are the same ones listed above
# ("GROUP"/"INSTANCE", "MASTER"/"BACKUP"/"FAULT"/"STOP"/"DELETED")
# (note: DELETED is only applicable to instances)
notify <STRING>|<QUOTED-STRING> [username [groupname]]
# The notify fifo output is the same as the last 4 parameters for the "notify"
# script, with the addition of "MASTER_RX_LOWER_PRI" instead of state for an
# instance, and also "MASTER_PRIORITY" and "BACKUP_PRIORITY" if the priority
# changes and notify_priority_changes is configured.
# MASTER_RX_LOWER_PRI is used if a master needs to set some external state, such
# as setting a secondary IP address when using Amazon AWS; if another keepalived
# has transitioned to master due to a communications break, the lower priority
# instance will have taken over the secondary IP address, and the proper master
# needs to be able to restore it.
# Send FIFO notifies for vrrp priority changes
notify_priority_changes <BOOL>
# Send email notification during state transition,
# using addresses in global_defs above (default no,
# unless global smtp_alert/smtp_alert_vrrp set)
smtp_alert <BOOL>
# DEPRECATED. Use track_interface, track_script and
# track_file on vrrp_sync_groups instead.
global_tracking
# allow sync groups to use differing weights.
# This probably WON'T WORK, but is a replacement for
# global_tracking in case different weights were used
# across different vrrp instances in the same sync group.
sync_group_tracking_weight
}
🦋 VRRP gratuitous ARP and unsolicited neighbour advert delay group(s) 🦋
specifies the setting of delays between sending gratuitous ARPs and unsolicited neighbour advertisements. This is intended for when an upstream switch is unable to handle being flooded with ARPs/NAs. like bestie, the switch is overwhelmed, give it a minute 😭.
Use interface when the limits apply on the single physical interface. Use interfaces when a group of interfaces are linked to the same switch and the limits apply to the switch as a whole.
Note: Only one ofinterfaceorinterfacesshould be used per block.
If the global vrrp_garp_interval and/or vrrp_gna_interval are set, any interfaces that aren't specified in a garp_group will inherit the global settings on a per interface basis.
The syntax for garp_group is :
garp_group {
# Sets the interval between Gratuitous ARP (in seconds, resolution microseconds)
garp_interval <DECIMAL>
# Sets the default interval between unsolicited NA (in seconds, resolution microseconds)
gna_interval <DECIMAL>
# The physical interface to which the intervals apply
interface <STRING>
# A list of interfaces across which the delays are aggregated.
interfaces {
<STRING>
<STRING>
...
}
}
👑 VRRP instance(s) 👑
A VRRP Instance is the VRRP protocol key feature. It defines and configures VRRP behaviour to run on a specific interface. Each VRRP Instance is related to a unique interface. main character energy fr fr.
The syntax for vrrp_instance is :
vrrp_instance <STRING> {
# Initial state, MASTER|BACKUP
# If the priority is 255, then the instance will transition immediately
# to MASTER if state MASTER is specified; otherwise the instance will
# wait between 3 and 4 advert intervals before it can transition,
# depending on the priority.
state MASTER
# interface for inside_network, bound by vrrp.
# Note: if using unicasting, the interface can be omitted as long
# as the unicast addresses are not IPv6 link local addresses (this is
# necessary, for example, if using asymmetric routing).
# If the interface is omitted, then all VIPs and eVIPs should specify
# the interface they are to be configured on, otherwise they will be
# added to the default interface.
interface eth0
# If using unicasting without specifying an interface, the VRF to operate
# in can be specified.
vrf VRF_IF
okay so this next cluster of knobs is all about the ✨interfaces✨ your VRRP instance lives on — macvlans, ipvlans, tracking, unicast peers, the whole vibe check. buckle up bestie, it's giving network engineering 💅
use_vmac [[name] <VMAC_INTERFACE_NAME>] [MAC_ADDRESS] [netlink_notify_msg] [group GROUP_ID]— use a VRRP virtual MAC (macvlan). the macvlan gets created on the interface you configured for the VRRP instance, and the VIPs + eVIPs of the matching address family that don't specify a different interface will get configured on the macvlan. the VRRP adverts will also be sent and received on the macvlan interface — unless you've gotvmac_xmit_baseconfigured, iykyk.
NOTE: if sysctl net.ipv4.conf.all.rp_filter is set, and this vrrp_instance is an IPv4 instance, using this option will cause the individual interfaces to be updated to the greater of their current setting and all.rp_filter, as will default.rp_filter, and all.rp_filter will be set to 0. the original settings are restored on termination. 🫶
NOTE 2: if you're using use_vmac with unicast peers, vmac_xmit_base must be set. no cap.
the MAC address can be specified with only 5 octets, in which case the virtual_router_id will be used as the last octet. if netlink_notify_msg is specified, when keepalived creates a macvlan interface it will force a netlink message to be sent for the base interface, since the kernel doesn't send one — even if the promiscuity of the base interface has been updated.
by default the VMAC is created in the same link group as the parent interface. specifying group GROUP_ID (where GROUP_ID is either a valid group number, or a name in /etc/iproute2/group) will create the interface in the specified group. and the name option can be specified if you want to use an interface name "group". iconic.
use_vmac_addr— used to create VMAC (macvlan) interfaces for each interface that's used by a VIP or eVIP where the interface isn't the same as the interface the VRRP instance is configured on, or where the eVIP's address family doesn't match the VRRP instance's. alternatively,use_vmaccan be specified against each VIP/eVIP that specifies an interface (dev).
NOTE: if use_vmac is specified and an eVIP isn't the same address family as the vrrp instance, unless use_vmac_addr is specified, or use_vmac is specified for the eVIP, the eVIP will be configured on the vrrp instance's VMAC — which will have the wrong MAC address for the address family of the eVIP. 💀 so like, don't do that.
vmac_xmit_base— send/recv VRRP messages from the base interface instead of the VMAC interface. simple as that bestie.use_ipvlan [[name] <INTERFACE_NAME>] [IP_ADDRESS] [bridge|private|vepa] [group GROUP_ID]— use an IPVLAN interface. keepalived will create a mode L2 ipvlan interface on top of the specified interface. for IPv4 instances an IP address is required; for IPv6 the address is optional, in which case the link local address will be used. the mode flags default tobridge.
NOTE: the mode flags must be the same for all ipvlans on the same underlying interface. also it's safer to configure an interface name, in case keepalived crashes and restarts — that way it can more reliably find a previously created interface. 🔥
the name option can be specified if you want to use a name that would cause a parsing error (e.g. "bridge"). for a description of the group option, see use_vmac above.
native_ipv6— force the instance to use IPv6. this option is deprecated since the virtual ip addresses determine whether IPv4 or IPv6 is used. she's retired, respect her legacy ✨dont_track_primary— ignore VRRP interface faults (default unset).
Note: when using IPv6, setting the interface administratively down, e.g. ip link set IF down, will by default cause all IPv6 addresses to be deleted from the interface, and consequently the VRRP instance will go to FAULT state due to the addresses being deleted. setting sysctl net.ipv6.conf.IF.keep_addr_on_down to 1 will allow non link-local addresses to remain when the interface is downed. 🙈
track_interface { ... }— optional, monitor these as well. go to FAULT state if any of these go down if unweighted. when a weight is specified intrack_interface, instead of setting the vrrp instance to the FAULT state in case of failure, its priority will be increased by the weight when the interface is up (for positive weights), or decreased by the weight's absolute value when the interface is down (for negative weights) — unlessreverseis specified, in which case the direction of adjustment of the priority is reversed. the weight must be comprised between-253and+253inclusive.0is the default behaviour, which means that a failure implies a FAULT state. the common practice is to use positive weights to count a limited number of good services so that the server with the highest count becomes master. negative weights are better to count unexpected failures among a high number of interfaces, as it will not saturate even with a high number of interfaces. usereverseto increase priority if an interface is down. 👑
track_interface {
eth0
eth1
eth2 weight <-253..253> [reverse]
...
}
track_script { ... }— add a tracking script to the interface (<SCRIPT_NAME> is the name of thevrrp_scriptentry). the same principle astrack_interfacecan be applied totrack_scriptentries, except that an unspecified weight means that the default weight declared in the script will be used (which itself defaults to0).reversecauses the direction of the adjustment of the priority to be reversed. 🦋
track_script {
<SCRIPT_NAME>
<SCRIPT_NAME> weight <-253..253> [reverse|noreverse]
}
track_file { ... }— files whose state we monitor, value is added to effective priority. <STRING> is the name of atrack_file. 🎀
track_file {
<STRING>
<STRING>
<STRING> weight <-254..254> [reverse|noreverse]
...
}
track_process { ... }— positive weights are added/subtracted when the process is running, negative weights are subtracted/added when it's not running. ifreverseis specified, the addition/subtraction is reversed. <STRING> is the name of avrrp_track_process; weight defaults to the weight configured invrrp_track_process. ⭐
track_process {
<STRING>
<STRING> weight <-254..254> [reverse|noreverse]
...
}
track_bfd { ... }— BFD instances we monitor, value is added to effective priority, unlessreverseis specified, in which case the value is subtracted. positive weights are add/subtracted when the bfd instance is up, negative weights are subtracted/added when the bfd instance is down. <STRING> is the name of a BFD instance. 🌈
track_bfd {
<STRING>
<STRING>
<STRING> weight <INTEGER: -253..253> [reverse|noreverse]
...
}
mcast_src_ip <IPADDR>— the default IP for binding vrrpd is the primary IP on the interface. if you want to hide the location of vrrpd, use this IP assrc_addrfor multicast or unicast vrrp packets (since it's multicast, vrrpd will get the reply packet no matter whatsrc_addris used). optional. 🕵️♀️unicast_src_ip <IPADDR>— same energy as above, for the unicast side of things. optional.mcast_dst_ip <MULTICAST_IPADDR>— specify an alternative multicast address to use as the destination of VRRP adverts and for listening for adverts. note: if you're using multiple VRRP instances with VMACs and different multicast addresses and the same VRID, you will have to specify alternative MAC addresses for at least all but one of the VMACs. 😭
IPv6 multicast addresses must be link-local, i.e. start ffX2:. using different multicast addresses with IPv6 on the same interface without using VMACs is only supported if the kernel supports IPV6_MULTICAST_ALL (from Linux v4.20). fr fr.
track_src_ip— if the configuredsrc_ipdoesn't exist or is removed, put the instance into fault state. 💀version <2 or 3>— the VRRP version to run on the interface. default is the global parametervrrp_version, but IPv6 instances will always use version3. periodt.check_unicast_src— enables checking that, when in unicast mode, the source address of a VRRP packet is one of our unicast peers. no randoms allowed in the group chat 💅unicast_peer { ... }— do not send VRRP adverts over a VRRP multicast group. instead it sends adverts to the following list of ip addresses using unicast. it can be cool to use the VRRP FSM and features in a networking environment where multicast isn't supported! IP addresses specified can be IPv4 as well as IPv6. 🦋
if min_ttl and/or max_ttl are specified, the TTL/hop limit of any received packet is checked against the specified TTL range, and is discarded if it's outside the range. specifying min_ttl or max_ttl turns on check_unicast_src.
unicast_peer {
<IPADDR> [min_ttl {0..255}] [max_ttl {0..255}]
...
}
unicast_fault_no_peer— it is not possible to operate in unicast mode without any peers. untilv2.2.4, keepalived would silently operate in multicast mode if no peers were specified but aunicastkeyword had been specified. using this keyword stops defaulting to multicast if no peers are specified, and puts the VRRP instance into fault state. the way that this option fixes a silent-failure vibe? obsessed. 🔥unicast_ttl {0..255}— specify the unicast TTL/HLIM for sending unicast adverts. ✨old_unicast_checksum [never]— the checksum calculation when using VRRPv3 changed afterv1.3.6. the reason for the change is that keepalived was calculating the checksum using the multicast address even when it was using unicast, whereas the checksum should be calculated using the actual address that is in the IPv4 header. setting this flag forces the old checksum algorithm to be used to maintain backward compatibility — although keepalived will attempt to maintain compatibility anyway if it sees an old version checksum. specifyingneverwill turn off auto detection of old checksums. [this option may not be enabled — check output ofkeepalived -vforOLD_CHKSUM_COMPAT.] 💜v3_checksum_as_v2 [<BOOL>]— okay so this one is a whole beef between vendors 💀 Some manufacturers (literally Cisco and Juniper) read RFC5798 5.2.8 as applying only to IPv6, because the pseudo-header in RFC2460 is specified only for IPv6 — even though most open source implementations, tcpdump/wireshark included, include the pseudo-header for IPv4 too. Keepalived by default uses a pseudo-header for VRRPv3 IPv4 as well. Setting this option turns off including the pseudo-header in the checksum calculation for VRRPv3 IPv4. And for compliance with RFC9568, this option should be set, no cap.
Next up, the interface-specific settings — same as the global parameters, and they default to the global parameters. Here's the whole lil family looking exactly like this:
# interface specific settings, same as global parameters.
# default to global parameters
garp_master_delay 10
garp_master_repeat 1
garp_lower_prio_delay 10
garp_lower_prio_repeat 1
garp_master_refresh 60
garp_master_refresh_repeat 2
garp_extra_if [all] 100 # specifying 0 disables feature
So that's garp_master_delay 10, garp_master_repeat 1, garp_lower_prio_delay 10, garp_lower_prio_repeat 1, garp_master_refresh 60, garp_master_refresh_repeat 2, and garp_extra_if [all] 100 — and heads up bestie, specifying 0 for that last one disables the feature. 🫶
down_timer_adverts [1-100]— the VRRP RFCs state that the master down timer is 3 advert intervals plus a skew time. Settingdown_timer_advertsmeans the master down timer will bedown_timer_advertsadvert intervals instead. The default is3, to conform with the VRRP RFCs. Setting it to literally any other value is a deviation from the VRRP protocol 👀 and all virtual routers for a given VRRP instance MUST use the same value.timer_expired_backup [HIGHEST_PRIORITY_OF_OTHER_INSTANCES]— so some users get these "thread timer expired" log messages, right? Those are caused by the kernel not scheduling keepalived quickly enough after a timer expired, which is always due to insufficient CPU resources being available (and if you're running keepalived in a VM it could be because the VM itself isn't being scheduled), or keepalived not being run at a high enough priority — see the realtime scheduling options above, bestie. Anyway: ifnopreemptis configured and another instance has become master, there are circumstances where this instance is required not to resume as master, but rather transition to backup. If you use this option (andnopreemptis configured), keepalived will calculate whether another instance may have taken over — based on the advert interval and the highest priority of the other instances, which defaults to254unless you specify it with this option — and if that time has expired since the last advert was sent, the VRRP instance will revert to backup state. And remember to include anytrack_scriptetc. weights when calculating the highest priority of other instances, iykyk. 🧮lower_prio_no_advert [<BOOL>]— if keepalived is late running by more than 2 advert intervals for a VRRP instance, it's possible that another instance has taken over as master. If a lower priority advert is received, don't send another advert. This causes adherence to the RFCs, and it defaults to the globalvrrp_lower_priority_dont_send_advert.higher_prio_send_advert [<BOOL>]— if we're master and we receive a higher priority advert, send an advert (which will be lower priority than the other master) before we transition to backup. That way, if the other master hasgarp_lower_prio_repeatset, it'll resend garp messages ✨ This is to get around the problem of there having been two simultaneous masters, and the last GARP messages seen were from us. Messy, but it works.
virtual_router_id 51— an arbitrary unique number from1to255, used to differentiate multiple instances ofvrrpdrunning on the same network interface and address family and multicast/unicast (and hence the same socket).
⚠️ Note time, bestie: using the samevirtual_router_idwith the same address family on different interfaces has been known to cause problems with some network switches. If you're experiencing problems using the samevirtual_router_idon different interfaces, but the problems are resolved by not duplicatingvirtual_router_ids, then your network switches are probably not functioning correctly. 💀
Whilst in general it's important not to duplicate a virtual_router_id on the same network interface, there's a special case when using unicasting — if the unicast peers for the vrrp instances with duplicated virtual_router_ids on the network interface do not overlap, then in that case virtual_router_ids can be duplicated. It's also possible to duplicate virtual_router_ids on an interface with multicasting if different multicast addresses are used (see mcast_dst_ip). 🌈
priority 100— for electing MASTER, the highest priority wins 👑 The valid range of values for priority is[1-255], with priority255meaning "address owner". To be MASTER, it's recommended to make this50more than on your other machines. All systems should have different priorities in order to make behaviour deterministic. If you want to stop a higher priority instance taking over as master when it starts, configureno_preemptrather than using equal priorities — fr fr, that's the move.
Ifno_acceptis configured (orvrrp_strict, which also setsno_acceptmode), then unless thevrrp_instancehas priority255, the system will not receive packets addressed to the VIPs/eVIPs, and the VIPs/eVIPs can only be used for routeing purposes. Further, if an instance has priority255configured, the priority cannot be reduced bytrack_scripts,track_processetc, and likewisetrack_scriptsetc cannot increase the priority to255if the configured priority is not255. That's just how it is. 💅
advert_int 1— VRRP Advert interval in seconds (e.g.0.92). Use the default, bestie.
Okay, authentication section — and the manpage has thoughts:
⚠️ Note: authentication was removed from the VRRPv2 specification by RFC3768 in 2004. Use of this option is non-compliant and can cause problems; avoid using it if possible, except when using unicast, where it can be helpful. So like… it's giving deprecated, but not deprecated enough to delete.
authentication {
# PASS|AH
# PASS - Simple password (suggested)
# AH - IPSEC (not recommended))
auth_type PASS
# Password for accessing vrrpd.
# should be the same on all machines.
# Only the first eight (8) characters are used.
auth_pass 1234
}
So inside that block: auth_type PASS|AH, where PASS is simple password (suggested ✨) and AH is IPSEC (not recommended). Then auth_pass is the password for accessing vrrpd, it should be the same on all machines, and only the first eight (8) characters are used — so auth_pass 1234 is the example, and yes, only 1234 counts. 🍓
auth_hmac— the authentication extension, and this one is keepalived specific 🔥 It protects adverts against injection and replay with an HMAC-SHA256 trailer and a time based sequence number. Works for VRRPv2 and v3, IPv4 and IPv6, unicast and multicast. Especially useful for unicast, where theTTL=255check does not apply. It's mutually exclusive with theauthenticationblock above. All nodes must share the keys, and keep their clocks in sync (NTP) whenanti_replayistime.
auth_hmac {
# A signing key, id 1 to 255, 32 to 64 bytes. A hex: prefix
# carries raw bytes, otherwise the value is an ASCII string.
# Use 32 bytes for a post quantum margin, a quantum search
# halves the effective key strength.
# A file: prefix reads the value from a file instead, keeping
# the secret out of the configuration, for example a systemd
# credential at file:${_ENV CREDENTIALS_DIRECTORY}/<name>
# (see the keepalived.service.d sample drop-in).
# Define several keys to allow zero downtime rotation.
key 1 hex:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
key 2 file:/etc/keepalived/keys/vrrp10
# Key id used when sending. Move it across nodes to rotate.
active_key 2
# Anti replay mode: time|monotonic (default time).
# time enforces a freshness window and needs synced clocks,
# monotonic only requires strictly growing sequences.
anti_replay time
# Freshness window in seconds, 1 to 300 (time mode only).
# Defaults to max(3 x advert_int, 5).
time_window 5
# enforce|permissive|receive-only (default enforce). The two
# migration modes also accept adverts with no trailer, and
# receive-only additionally sends none, so a group gains
# verification before anyone signs. Roll out in three sweeps,
# receive-only then permissive then enforce, each sweep is
# hitless with no timing constraint.
mode enforce
}
Breaking that down so you don't miss anything 🫶
key <id> <value>— a signing key, id1to255,32to64bytes. Ahex:prefix carries raw bytes, otherwise the value is an ASCII string. Use32bytes for a post quantum margin — a quantum search halves the effective key strength. Afile:prefix reads the value from a file instead, keeping the secret out of the configuration, for example a systemd credential atfile:${_ENV CREDENTIALS_DIRECTORY}/<name>(see thekeepalived.service.dsample drop-in). Define several keys to allow zero downtime rotation. ✨active_key <id>— the key id used when sending. Move it across nodes to rotate.anti_replay time|monotonic— anti replay mode, defaulttime.timeenforces a freshness window and needs synced clocks;monotoniconly requires strictly growing sequences.time_window <seconds>— freshness window in seconds,1to300(time mode only). Defaults tomax(3 x advert_int, 5).mode enforce|permissive|receive-only— defaultenforce. The two migration modes also accept adverts with no trailer, andreceive-onlyadditionally sends none, so a group gains verification before anyone signs. Roll out in three sweeps —receive-onlythenpermissivethenenforce— and each sweep is hitless with no timing constraint. Iconic rollout plan, ngl. 👑
The example keys, verbatim, because you're gonna want them:
key 1 hex:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
key 2 file:/etc/keepalived/keys/vrrp10
💖 virtual_ipaddress, virtual_ipaddress_excluded & friends 💖
ok so before we even get into the blocks, here's the deal on how these bad boys behave. They get added when you transition to MASTER, and deleted when you go to BACKUP. And bestie — if you've got the same entries on your other machines, the opposite transition is happening over there, which is literally the whole point. Chef's kiss. 💅
So for virtual_ipaddress, virtual_ipaddress_excluded, virtual_routes and virtual_rules, most of the options match up with the options of the command ip address/route/rule add. Iconic. But there's a couple of special snowflakes:
- the
track_groupoption only applies to static addresses/routes/rules no_trackis specific to keepalived, and what it means is: thevrrp_instancewill not transition out of master state if the address/route/rule gets deleted, and that address/route/rule will not be reinstated until the vrrp instance next transitions to master. So it just... vibes. Respect.
Then there's the label situation:
<LABEL>— optional, creates a name for the alias. For compatibility withifconfig, it should be of the form<realdev>:<anytext>— for exampleeth0:1for an alias oneth0.
And the scope:
<SCOPE>— one of"site","link","host","nowhere","global".
Oh and preferred_lft gets set to 0 to deprecate IPv6 addresses (that's the default if the address mask is /128). If you want a /128 address to not be deprecated, use preferred_lft forever. 🌈
🫶 NOTE, pls read this one: care needs to be taken ifdevis specified for an address and your network uses MAC learning switches. The VRRP protocol makes sure that the source MAC address of the interface sending adverts is maintained in the MAC cache of switches — but by default this won't work for the MACs of any VIPs/eVIPs that are configured on different interfaces from the interface the VRRP instance is configured on, since the interface (especially if it's a VMAC interface) will only send using the MAC address of the interface in response to ARP requests. Which means, yeah, the interface MAC addresses may time out in the MAC caches of switches 😭. To avoid this, use thegarp_extra_iforgarp_extra_if_vmacoptions to send periodic GARP/ND messages on those interfaces.
Now the actual block, look at her:
virtual_ipaddress {
<IPADDR>[/<MASK>] [brd <IPADDR>] [dev <STRING>] [use_vmac] [scope <SCOPE>]
[label <LABEL>] [peer <IPADDR>] [home]
[-nodad] [mngtmpaddr] [noprefixroute]
[autojoin] [no_track] [preferred_lft nn|forever]
192.168.200.17/24 dev eth1
192.168.200.18/24 dev eth2 label eth2:1
}
Slay. And then the excluded one:
# VRRP IP excluded from VRRP optional.
# For cases with large numbers (eg 200) of IPs
# on the same interface. To decrease the number
# of addresses sent in adverts, you can exclude
# most IPs from adverts.
# The IPs are add|del as for virtual_ipaddress.
# Can also be used if you want to be able to add
# a mixture of IPv4 and IPv6 addresses, since all
# addresses in virtual_ipaddress must be of the
# same family.
virtual_ipaddress_excluded {
<IPADDR>[/<MASK>] [brd <IPADDR>] [dev <STRING>] [scope <SCOPE>]
[label <LABEL>] [peer <IPADDR>] [home]
[-nodad] [mngtmpaddr] [noprefixroute]
[autojoin] [no_track]
<IPADDR>[/<MASK>] ...
...
}
literally the whole point of virtual_ipaddress_excluded is: when you've got a large number (like, eg 200) of IPs on the same interface, you exclude most of them from the adverts so you're not out here sending a novel in every advert packet. The IPs are still add|del'd exactly like virtual_ipaddress does it. And bonus ✨ — it also lets you mix IPv4 and IPv6 addresses, which you can't do in virtual_ipaddress because all addresses in there must be of the same family. No mixing allowed, she's strict.
🦋 no_virtual_ipaddress & promote_secondaries 🦋
# Specifying no virtual IP addresses is generally a configuration error
# and VRRP version 3 explicitly states that the minimum number of addresses
# is 1. Consequently keepalived warns if no VIPs are configured.
# There are, however, circumstances when it is useful to have no VIPs, for
# example when cloud servers, e.g. AWS, where floating IP addresses are
# managed administratively, and are not configured on the cloud virtual
# server. Specifying no_virtual_ipaddress supresses warnings for no VIPs,
# and allows VRRPv3 to be used with no VIPs.
# WARNING - when using this with VRRPv3 it causes a protocol violation and
# may not work with other VRRP implementations.
no_virtual_ipaddress
ok so having zero virtual IP addresses is generally a config error — VRRP version 3 is out here explicitly stating the minimum number of addresses is 1. So keepalived warns you if no VIPs are configured. But sometimes no VIPs is actually useful, like with cloud servers (e.g. AWS 🙈) where floating IP addresses are managed administratively and aren't configured on the cloud virtual server. no_virtual_ipaddress suppresses those warnings and lets you use VRRPv3 with no VIPs.
💀 WARNING: when you use this with VRRPv3 it causes a protocol violation and may not work with other VRRP implementations. Not a vibe if you're mixing vendors, bestie.
Then:
# Set the promote_secondaries flag on the interface to stop other
# addresses in the same CIDR being removed when 1 of them is removed
# For example if 10.1.1.2/24 and 10.1.1.3/24 are both configured on an
# interface, and one is removed, unless promote_secondaries is set on
# the interface the other address will also be removed.
promote_secondaries
so promote_secondaries sets that flag on the interface to stop other addresses in the same CIDR from getting clapped when one of them is removed. Like — if 10.1.1.2/24 and 10.1.1.3/24 are both on an interface and one gets removed, unless promote_secondaries is set on the interface, the other address will also get removed. Bye. 👋 That's the gotcha.
🔥 virtual_routes & virtual_rules 🔥
Routes get added/deleted when you change to MASTER, and deleted when you go to BACKUP. See static_routes for the full tea. 🍵
virtual_routes {
# src <IPADDR> [to] <IPADDR>/<MASK> via|gw <IPADDR>
# [or <IPADDR>] dev <STRING> scope <SCOPE> table <TABLE>
src 192.168.100.1 to 192.168.109.0/24 via 192.168.200.254 dev eth1
192.168.110.0/24 via 192.168.200.254 dev eth1
192.168.111.0/24 dev eth2 no_track
192.168.112.0/24 via 192.168.100.254
192.168.113.0/24 via 192.168.200.254 or 192.168.100.254 dev eth1
blackhole 192.168.114.0/24
0.0.0.0/0 gw 192.168.0.1 table 100 # To set a default gateway into table 100.
}
Rules get added/deleted when you change to MASTER, and deleted when you go to BACKUP. See static_rules for more.
virtual_rules {
from 192.168.2.0/24 table 1
to 192.168.2.0/24 table 1 no_track
}
Both of these? Obsessed. 🌸
👑 accept / no_accept 👑
VRRPv3 has an Accept Mode that lets the virtual router — when it's not the address owner — receive packets addressed to a VIP. That's the default setting unless strict mode is set. And as an extension, this also works for VRRPv2 (RFC 3768 doesn't define an accept mode, so keepalived is being extra 💅).
# Accept packets to non address-owner
accept
# Drop packets to non address-owner.
no_accept
⭐ nopreempt, preempt & preempt_delay ⭐
Normally a higher priority VRRP instance will preempt a lower priority one when it comes online. nopreempt stops the higher priority machine from taking over the master role, and lets the lower priority machine stay as master. 🫶
😭 NOTE: for this to work, the initial state must not be MASTER.
nopreempt
# for backwards compatibility
preempt
and preempt is there purely for backwards compatibility, iykyk.
Then preempt_delay — this is seconds of delay until preemption, after getting the advertisement timeout at startup or when seeing a lower priority master.
Important: since it is a delay, it cannot speed up taking over as master. So don't come at her expecting magic ⭐.
preempt_delay specifies the time in seconds to delay preempting compared to if preempt_delay is not specified. Advertisement timeout is 3 * advert_int + skew_time. Skew_time is defined by RFC3768 and RFC5798.
So — if advert_int is 1, and priority is 128, the instance would normally wait 3.5 seconds before taking over as master. If preempt_delay 2 is specified, then the delay before taking over as master would be approximately 5.5 seconds (if not disabled by nopreempt).
Range: 0 (default) to 1000 (e.g. 4.12)
🙈 NOTE: for this to work, the initial state must not be MASTER.
preempt_delay 300 # waits 5 minutes
five whole minutes, she's patient 💜
🎀 skip_check_adv_addr & strict_mode 🎀
skip_check_adv_addr [<BOOL>]
See the description of global vrrp_skip_check_adv_addr, which sets the default value. Defaults to vrrp_skip_check_adv_addr.
strict_mode [<BOOL>]
See the description of global vrrp_strict. If strict_mode is not specified, it takes the value of vrrp_strict. If strict_mode is specified without a parameter, it defaults to on. ✨
💅 debug 💅
debug <LEVEL>
Debug level — not implemented yet, bestie, so don't get your hopes up 😭. LEVEL is a number in the range 0 to 4.
🍓 notify scripts (alert as above) 🍓
notify_master <STRING>|<QUOTED-STRING> [username [groupname]]
notify_backup <STRING>|<QUOTED-STRING> [username [groupname]]
notify_fault <STRING>|<QUOTED-STRING> [username [groupname]]
# executed when stopping vrrp
notify_stop <STRING>|<QUOTED-STRING> [username [groupname]]
notify <STRING>|<QUOTED-STRING> [username [groupname]]
notify_stop runs when stopping vrrp, in case that wasn't obvious from the comment. 🎀
# The notify_master_rx_lower_pri script is executed if a master
# receives an advert with priority lower than the master's priority.
notify_master_rx_lower_pri <STRING>|<QUOTED-STRING> [username [groupname]]
so notify_master_rx_lower_pri fires if a master receives an advert with a priority lower than the master's own priority. Main character energy, honestly. 👑
# Send vrrp instance priority notifications on notify FIFOs.
notify_priority_changes <BOOL>
🌈 smtp_alert, kernel_rx_buf_size & linkbeat_use_polling 🌈
# Send SMTP alerts
smtp_alert <BOOL>
# Set socket receive buffer size (see global_defs
# vrrp_rx_bufs_policy for explanation)
kernel_rx_buf_size
# Set use of linkbeat for the interface of this VRRP instance. This option is
# deprecated - use linkbeat_interfaces block instead.
linkbeat_use_polling
💀 deprecated alert!linkbeat_use_pollingis deprecated — use thelinkbeat_interfacesblock instead. She's old news, but she still works.
}
and that's the closing brace on the vrrp_instance block, bestie 🫶
🧚 Interface up/down status change debouncing 🧚
ok so here's the situation. If an interface that's being used (or tracked) by a VRRP instance goes to down state, the VRRP instance(s) will, by default, immediately transition to FAULT state. And when all relevant interfaces are back up again, the VRRP instance(s) will immediately transition to BACKUP state. Immediate immediate. No chill. 😭
That can cause problems if interfaces are bouncing (we've all been there), so you can specify delays between the interface state change and the transition to FAULT/BACKUP state. And here's the cute part — if the interface returns to its original state before the delay expires, no associated VRRP instance state transition will occur at all. Nothing happens. She just pretends it never happened 🙈✨
interface_up_down_delays {
ifname down_delay [up_delay]
ifname2 down_delay [up_delay]
...
}
The delays are specified in seconds, with a resolution of microseconds — e.g. a delay of 0.00001 means 10 usecs. A delay of 0 means no delay in state change. The maximum delay you can specify is 255 seconds.
If up_delay is omitted, it's set to be the same as the down delay. 🫶
⭐ ok important bit: the down delay on an interface must be less than two (or, more precisely, one less thandown_timer_adverts(default3)) times the advert interval of any VRRP instance using that interface. Otherwise a backup instance, while not receiving adverts, may time out and become master before this instance transitions to FAULT state. Which is not the vibe. Consequently the up/down delays can be dynamically reduced if another instance is master with a shorter advert interval.
And last but not least — if the VRRP instance is using a VMAC, it will inherit the up/down debounce delays of its parent interface. Family business. 💜
🌈 LVS CONFIGURATION 🌈
Okay bestie, this is the big one — the LVS era of the file 💅 This block contains subblocks of Virtual server group(s) and Virtual server(s).
These subblocks hold the arguments for configuring the Linux IPVS (LVS) feature. Knowing your way around ipvsadm(8) will highkey help you here — like genuinely, go read it, iykyk. You configure LVS by defining virtual server groups, virtual servers, and optionally SSL configuration ✨ Every virtual server defines a set of real servers, and you can attach healthcheckers to each real server. Keepalived then leads the LVS operation by dynamically maintaining the topology 👑
For the deets on which configuration combinations are actually valid, see the ipvsadm(8) man page. No cap, it's the source of truth.
💡 Note: where an option can be configured for a virtual server, a real server, and possibly a checker — the virtual server setting is the default for real servers, and the real server setting is the default for checkers. So it cascades down, cute right? 🎀
💡 Note: tunnelled real/sorry servers can differ from the address family of the virtual server and non-tunnelled real/sorry servers, which all have to be the same. If a virtual server uses a fwmark, and all the real/sorry servers are tunnelled, the address family of the virtual server will be the same as the address family of the real/sorry servers if they're all the same — otherwise it defaults to IPv4 (use ip_family inet6 to override this). slay, but also: read that twice 💀
💡 Note: the port for the virtual server can only be omitted if the virtual service is persistent. That's the rule, periodt.
🦋 Virtual server group(s) 🦋
This feature is genuinely iconic — it simplifies your config by factorizing virtual server definitions 🌸 If you need to define a whole bunch of virtual servers with exactly the same real server topology, this makes your config way more readable, optimizes the duplication of IPVS virtual servers if nftables_ipvs is used, and optimizes healthchecking tasks by only spawning one healthchecker where multiple virtual server declarations would spawn a dedicated healthchecker for every real server — which genuinely just wastes system resources 😭
Any combination of IP addresses, IP address ranges and firewall marks can be used, provided that the family of the IP addresses of the virtual server group matches the IP address family of all the real servers of any virtual server using the virtual server group. The one exception to this: the virtual server group can be configured with both IPv4 and IPv6 addresses and fwmarks, provided that all the real servers (and sorry servers) of all virtual servers using the virtual server group use tunnel forwarding 🫶 If fwmarks are specified in this case, the address family must be specified — the one exception to that exception is if the virtual server group has no IP addresses (i.e. fwmarks only) and all the real/sorry servers are tunnelled, then it defaults to IPv4. But it's not good practice to rely on this, and the address families of the fwmarks should be configured. Don't be lazy bestie, just write it out ✨
This option is intended for very large LVSs, but heads up: this can create a huge number of virtual servers unless nftables_ipvs is used. The use of nftables_ipvs is stringly recommended (yes, that's how the man page spells it, we respect the original 💀) due to the very significant optimisations and efficiencies it provides 🔥
⚠️ NOTE: do not configure more than one TCP, one UDP and one SCTP virtual server with the same IP address family using the same virtual server group — or, to put it another way, do not have two virtual servers with the same protocol and address family using the same virtual server group. And if all the real servers are tunnelled, then you must not have both IPv4 and IPv6 virtual servers with the same protocol. That's the vibe check, fail it and nothing works 🙈
The syntax for virtual_server_group is:
virtual_server_group <STRING> {
# Virtual IP Address and Port
<IPADDR> [<PORT>]
<IPADDR> [<PORT>]
...
# <IPADDR RANGE> is any of the following forms (or their IPv6 equivalents)
# XXX.YYY.ZZZ.WWW-VVV eg 192.168.200.1-10 (includes both .1 and .10)
# AAA.BBB.CCC.DDD-EEE.FFF.GGG.HHH eg 192.168.200.250-192.168.201.10
# III.JJJ.KKK.LLL/nn eg 192.168.202.8/29
<IPADDR RANGE> [<PORT>] # VIP range [VPORT]
<IPADDR RANGE> [<PORT>]
...
# Firewall Mark (fwmark)
# inet/inet6 should only be specified for virtual server groups
# where all real servers of the virtual servers are tunnelled.
fwmark <INTEGER>
fwmark <INTEGER> [inet|inet6]
...
}
(the original block is indented with leading spaces, so preserve them exactly as above when you paste 💖)
💖 Virtual server(s) 💖
A virtual_server can be a declaration of one of <IPADDR> [<PORT>], fwmark <INTEGER>, or group <STRING> 🌟
The syntax for virtual_server is:
virtual_server <IPADDR> [<PORT>] |
virtual_server fwmark <INTEGER> |
virtual_server group <STRING> {
# LVS scheduler
lvs_sched rr|wrr|lc|wlc|lblc|sh|mh|dh|fo|ovf|lblcr|sed|nq|twos
# Enable flag-1 for scheduler (-b flag-1 in ipvsadm)
flag-1
# Enable flag-2 for scheduler (-b flag-2 in ipvsadm)
flag-2
# Enable flag-3 for scheduler (-b flag-3 in ipvsadm)
flag-3
# Enable sh-port for sh scheduler (-b sh-port in ipvsadm)
sh-port
# Enable sh-fallback for sh scheduler (-b sh-fallback in ipvsadm)
sh-fallback
# Enable mh-port for mh scheduler (-b mh-port in ipvsadm)
mh-port
# Enable mh-fallback for mh scheduler (-b mh-fallback in ipvsadm)
mh-fallback
# Enable One-Packet-Scheduling for UDP (-o in ipvsadm)
ops
# Override default LVS forwarding method (default is NAT).
# Default tunnel type is ipip. Since Linux 5.2 the GUE tunnel type can
# be specified. If using GUE, a port number is required. Since Linux 5.3
# if the tunnel type is GUE, the checksum option can also be specified.
# Since Linux 5.3, GRE tunnel type is also supported, but without the
# remcsum option.
lvs_method NAT|DR
or
lvs_method TUN [type {ipip|gue port NUM|gre} [nocsum|csum|remcsum]]
# LVS persistence engine name (currently only sip supported)
persistence_engine <STRING>
# LVS persistence timeout in seconds, default 6 minutes
persistence_timeout [<INTEGER>]
# LVS granularity mask (-M in ipvsadm)
persistence_granularity <NETMASK>
# L4 protocol
protocol TCP|UDP|SCTP
# If VS IP address is not set,
# suspend healthchecker's activity
ha_suspend
# Send email notification during quorum up/down transition,
# using addresses in global_defs above (default no,
# unless global smtp_alert/smtp_alert_checker set)
smtp_alert <BOOL>
# Default VirtualHost string for HTTP_GET or SSL_GET
# eg virtualhost www.firewall.loc
# Overridden by virtualhost config of real server or checker
virtualhost <STRING>
# snmp_name is a text string that is returned as part of the snmp
# data for this virtual server. It can be used to help identify the
# virtual server when parsing SNMP output.
snmp_name <STRING>
# On daemon startup assume that all RSs are down
# and healthchecks failed. This helps to prevent
# false positives on startup. Alpha mode is
# disabled by default.
alpha
# On daemon shutdown consider quorum and RS
# down notifiers for execution, where appropriate.
# Omega mode is disabled by default.
omega
# Minimum total weight of all live servers in
# the pool necessary to operate VS with no
# quality regression. Defaults to 1.
quorum <INTEGER>
# Tolerate this much weight units compared to the
# nominal quorum, when considering quorum gain
# or loss. A flap dampener. Defaults to 0.
hysteresis <INTEGER>
# Script to execute when quorum is gained.
quorum_up <STRING>|<QUOTED-STRING> [username [groupname]]
# Script to execute when quorum is lost.
quorum_down <STRING>|<QUOTED-STRING> [username [groupname]]
# IP family for a fwmark service (only needed if all real servers are tunnelled
# and persistence_granularity is not specified). Defaults to inet if not specified.
ip_family inet|inet6
# setup realserver(s)
# RS to add to LVS topology when the quorum isn't achieved.
# If a sorry server is configured, all real servers will
# be brought down when the quorum is not achieved and be
# replaced with the sorry server.
sorry_server <IPADDR> [<PORT>]
# applies inhibit_on_failure behaviour to the sorry_server
# It is very unlikely that you want to use this, since if you
# have a real server available, you almost certainly want to use
# it.
sorry_server_inhibit
# Sorry server LVS forwarding method. Default is the virtual
# server's default.
# For details of tunnel type, see virtual_server details.
sorry_server_lvs_method NAT|DR
or
sorry_server_lvs_method TUN [type {ipip|gue port NUM|gre} [nocsum|csum|remcsum]]
# Optional connection timeout in seconds.
# The default is 5 seconds
connect_timeout <TIMER>
# Retry count to make additional checks if check
# of an alive server fails. Default: 1 unless specified below
retry <INTEGER>
# delay before retry after failure. Defaults to delay_loop for DNS_CHECK,
# 3 seconds for HTTP_GET and SSL_GET, and 1 second otherwise.
delay_before_retry <TIMER>
# Optional random delay to start the initial check
# for maximum N seconds.
# Useful to scatter multiple simultaneous
# checks to the same RS. Enabled by default, with
# the maximum at delay_loop. Specify 0 to disable
warmup <TIMER>
# delay timer for checker polling (60 seconds if not specified)
delay_loop <TIMER>
# Set weight to 0 when healthchecker detects failure
inhibit_on_failure
# one entry for each realserver
real_server <IPADDR> [<PORT>] {
# relative weight to use, default: 1
weight <INTEGER>
# LVS forwarding method
# For details of tunnel type, see virtual_server details. The default
# setting is taken from the virtual_server's setting.
lvs_method NAT|DR
or
lvs_method TUN [type {ipip|gue port NUM|gre} [nocsum|csum|remcsum]]
# Script to execute when healthchecker
# considers service as up.
notify_up <STRING>|<QUOTED-STRING> [username [groupname]]
# Script to execute when healthchecker
# considers service as down.
notify_down <STRING>|<QUOTED-STRING> [username [groupname]]
# maximum number of connections to server
uthreshold <INTEGER>
# minimum number of connections to server
lthreshold <INTEGER>
# Send email notification during state transition,
# using addresses in global_defs above (default yes,
# unless global smtp_alert/smtp_alert_checker set)
smtp_alert <BOOL>
Okay so let me walk you through the good bits, because there's a lot going on in there 👑
🎀 The virtual_server top-level stuff 🎀
lvs_sched— picks your LVS scheduler, one ofrr,wrr,lc,wlc,lblc,sh,mh,dh,fo,ovf,lblcr,sed,nq,twos. Pick your fighter 💅flag-1— enable flag-1 for the scheduler (-b flag-1inipvsadm).flag-2— enable flag-2 for the scheduler (-b flag-2inipvsadm).flag-3— enable flag-3 for the scheduler (-b flag-3inipvsadm).sh-port— enable sh-port for the sh scheduler (-b sh-portinipvsadm).sh-fallback— enable sh-fallback for the sh scheduler (-b sh-fallbackinipvsadm).mh-port— enable mh-port for the mh scheduler (-b mh-portinipvsadm).mh-fallback— enable mh-fallback for the mh scheduler (-b mh-fallbackinipvsadm).ops— enable One-Packet-Scheduling for UDP (-oinipvsadm). Obsessed with this name ngl ✨
🌸 Forwarding method — lvs_method 🌸
This overrides the default LVS forwarding method — and jsyk, the default is NAT. The default tunnel type is ipip. Since Linux 5.2 the GUE tunnel type can be specified, and if you're using GUE, a port number is required. Since Linux 5.3, if the tunnel type is GUE, the checksum option can also be specified. Since Linux 5.3, GRE tunnel type is also supported, but without the remcsum option 🫶
So your options are:
lvs_method NAT|DR
or
lvs_method TUN [type {ipip|gue port NUM|gre} [nocsum|csum|remcsum]]
💜 Persistence, protocol & friends 💜
persistence_engine <STRING>— the LVS persistence engine name. Currently onlysipis supported, so like… one option, but make it fashion 🎀persistence_timeout [<INTEGER>]— LVS persistence timeout in seconds. Default is 6 minutes ⏰persistence_granularity <NETMASK>— the LVS granularity mask (-Minipvsadm).protocol TCP|UDP|SCTP— the L4 protocol. Pick one, bestie.ha_suspend— if the VS IP address is not set, suspend the healthchecker's activity 😴smtp_alert <BOOL>— send an email notification during the quorum up/down transition, using the addresses inglobal_defsabove. Default is no, unless globalsmtp_alert/smtp_alert_checkeris set 📧virtualhost <STRING>— the default VirtualHost string forHTTP_GETorSSL_GET, e.g.virtualhost www.firewall.loc. It gets overridden by thevirtualhostconfig of the real server or checker 🌈snmp_name <STRING>— a text string returned as part of the SNMP data for this virtual server. It can be used to help identify the virtual server when parsing SNMP output 👑alpha— on daemon startup, assume that all RSs are down and healthchecks failed. This helps prevent false positives on startup. Alpha mode is disabled by default 🙈omega— on daemon shutdown, consider quorum and RS down notifiers for execution, where appropriate. Omega mode is disabled by default 💀quorum <INTEGER>— the minimum total weight of all live servers in the pool necessary to operate the VS with no quality regression. Defaults to1.hysteresis <INTEGER>— tolerate this many weight units compared to the nominal quorum when considering quorum gain or loss. It's a flap dampener 🦋 Defaults to0.quorum_up <STRING>|<QUOTED-STRING> [username [groupname]]— script to execute when quorum is gained 🎉quorum_down <STRING>|<QUOTED-STRING> [username [groupname]]— script to execute when quorum is lost 😭ip_family inet|inet6— the IP family for a fwmark service. Only needed if all real servers are tunnelled andpersistence_granularityis not specified. Defaults toinetif not specified 🌟
🍓 Sorry server(s) 🍓
sorry_server <IPADDR> [<PORT>]— the RS to add to the LVS topology when the quorum isn't achieved 💔 If a sorry server is configured, all real servers will be brought down when the quorum is not achieved, and be replaced with the sorry server. It's giving "you tried your best" energy.sorry_server_inhibit— appliesinhibit_on_failurebehaviour to the sorry server. It is very unlikely that you want to use this, since if you have a real server available, you almost certainly want to use it. Like — genuinely, why would you 🙈sorry_server_lvs_method NAT|DRorsorry_server_lvs_method TUN [type {ipip|gue port NUM|gre} [nocsum|csum|remcsum]]— the sorry server's LVS forwarding method. Default is the virtual server's default. For details of tunnel type, see thevirtual_serverdetails above ✨
🧚 Checker-ish defaults on the virtual server 🧚
connect_timeout <TIMER>— optional connection timeout in seconds. The default is5seconds.retry <INTEGER>— retry count to make additional checks if a check of an alive server fails. Default:1, unless specified below.delay_before_retry <TIMER>— delay before retry after failure. Defaults todelay_loopforDNS_CHECK,3seconds forHTTP_GETandSSL_GET, and1second otherwise.warmup <TIMER>— optional random delay to start the initial check for a maximum of N seconds. Useful to scatter multiple simultaneous checks to the same RS. Enabled by default, with the maximum atdelay_loop. Specify0to disable 🫶delay_loop <TIMER>— delay timer for checker polling.60seconds if not specified.inhibit_on_failure— set weight to0when the healthchecker detects failure 🩹
👑 Inside real_server 👑
One entry for each real server, bestie:
weight <INTEGER>— relative weight to use. Default:1.lvs_method NAT|DRorlvs_method TUN [type {ipip|gue port NUM|gre} [nocsum|csum|remcsum]]— the LVS forwarding method. For details of tunnel type, see thevirtual_serverdetails above. The default setting is taken from the virtual server's setting 💖notify_up <STRING>|<QUOTED-STRING> [username [groupname]]— script to execute when the healthchecker considers the service as up 🎉notify_down <STRING>|<QUOTED-STRING> [username [groupname]]— script to execute when the healthchecker considers the service as down 😭uthreshold <INTEGER>— maximum number of connections to the server.lthreshold <INTEGER>— minimum number of connections to the server.smtp_alert <BOOL>— send an email notification during the state transition, using the addresses inglobal_defsabove. Default yes (note the flip! it'snoup at the virtual server level 💀), unless globalsmtp_alert/smtp_alert_checkeris set 📧
and we're literally still inside the real_server block — the checker config is where it gets really good, so don't you dare close that brace yet 💅
...so like, we're still inside the real_server block, bestie, and there's a whole lot more 💅 buckle up 🎀
# Default VirtualHost string for HTTP_GET or SSL_GET
# eg virtualhost www.firewall.loc
# Overridden by virtualhost config of a checker
virtualhost <STRING>
virtualhost <STRING>— the default VirtualHost string forHTTP_GETorSSL_GET, likewww.firewall.loc. A checker's ownvirtualhostconfig overrides it, so it's giving "default but you can betta it" energy ✨
# snmp_name is a text string that is returned as part of the snmp
# data for this real server. It can be used to help identify the
# real server when parsing SNMP output.
snmp_name <STRING>
snmp_name <STRING>— a text string that gets returned as part of the snmp data for this real server. Lowkey its whole job is helping you identify the real server when you're parsing SNMP output out here in the trenches 🫶
🩺 the generic real-server knobs
Okay so these next ones are literally marked # see above in the original, which is the manpage equivalent of "as previously discussed, bestie" 😭 they behave exactly like the versions described earlier in this page:
alpha <BOOL> # see above
connect_timeout <TIMER> # see above
retry <INTEGER> # see above
delay_before_retry <TIMER> # see above
warmup <TIMER> # see above
delay_loop <TIMER> # see above
inhibit_on_failure <BOOL> # see above
💖 healthcheckers (the fun part fr)
You can have multiple of each type, okay? Not a one-and-done situation:
# healthcheckers. Can be multiple of each type
# HTTP_GET|SSL_GET|TCP_CHECK|SMTP_CHECK|DNS_CHECK|MISC_CHECK|BFD_CHECK|UDP_CHECK|PING_CHECK|FILE_CHECK
All checkers have the following options, except:
MISC_CHECK— which only has options fromalphaonwardsBFD_CHECKandFILE_CHECK— which have none of the standard options at all 🙈
CHECKER_TYPE {
# ======== generic connection options
# Optional IP address to connect to.
# The default is the realserver IP
connect_ip <IPADDR>
# Optional port to connect to
# The default is the realserver port
connect_port <PORT>
# Optional address to use to
# originate the connection
bindto <IPADDR>
# Optional interface to use; needed if
# the bindto address is IPv6 link local
bind_if <IFNAME>
# Optional source port to
# originate the connection from
bind_port <PORT>
# Optional fwmark to mark all outgoing
# checker packets with
fwmark <INTEGER>
alpha <BOOL> # see above
connect_timeout <TIMER> # see above
retry <INTEGER> # see above
delay_before_retry <TIMER> # see above
warmup <TIMER> # see above
delay_loop <TIMER> # see above
log_all_failures <BOOL> # log all failures when checker up
}
Quick rundown of the generic connection options, since these are the ones you'll actually be typing at 2am 🌙
connect_ip <IPADDR>— optional IP address to connect to. Default is the realserver IP.connect_port <PORT>— optional port to connect to. Default is the realserver port.bindto <IPADDR>— optional address to use to originate the connection from.bind_if <IFNAME>— optional interface to use; needed if thebindtoaddress is IPv6 link local 👀bind_port <PORT>— optional source port to originate the connection from.fwmark <INTEGER>— optional fwmark to mark all outgoing checker packets with.log_all_failures <BOOL>— log all failures when the checker is up. no cap, this one is your bestie when you're debugging 🕵️♀️
And the rest (alpha, connect_timeout, retry, delay_before_retry, warmup, delay_loop) — same as above, same as above, same as above ✨
🌈 HTTP and SSL healthcheckers
HTTP_GET|SSL_GET {
# HTTP protocol version, one of 1.0, 1.0C, 1.1
# Protocol version 1.0C means version 1.0 with the addition
# of a "Connection: close" line, which is included in
# version 1.1 by default.
http_protocol <PROTOCOL>
# When alpha mode is set, or when recovering from a failure,
# each URL is checked, with a delay of <delay_loop> between
# each check. if there were 20 URLs, and the <delay_loop> were
# 3 seconds, it would take 1 minute before the RS would come up
# following startup, or recovery from a failure. Setting
# fast_recovery removes the delay, both at start up and after
# recovery from a failure, meaning that the RS will come up
# once all the URLs have been checked, with no delay between
# checking each URL.
fast_recovery [<BOOL>]
http_protocol <PROTOCOL>— HTTP protocol version, one of1.0,1.0C,1.1. And1.0Cmeans version 1.0 plus aConnection: closeline, which version1.1includes by default anyway. iykyk 🎀fast_recovery [<BOOL>]— okay so when alpha mode is set, or when recovering from a failure, each URL is checked with a delay of<delay_loop>between each check. If there were 20 URLs and<delay_loop>were 3 seconds, it would take 1 minute before the RS would come up following startup, or recovery from a failure 😭 Settingfast_recoveryremoves the delay, both at start up and after recovery from a failure — meaning the RS comes up once all the URLs have been checked, with no delay between checking each URL. she ate 💅
🔍 the url { } block
# An url to test
# can have multiple entries here
url {
#eg path / , or path /mrtg2/
path <STRING>
# healthcheck needs digest
# or status_code and digest
# Digest computed with genhash
# eg digest 9b3a0c85a887a256d6939da88aabd8cd
digest <STRING>
# status code returned in the HTTP header
# eg status_code 200 or status_code 200-299 400-499 503 505
# Default is 200-299
status_code <INTEGER|RANGE> [<INTEGER|RANGE>] ...
# VirtualHost string. eg virtualhost www.firewall.loc
# If not set, uses virtualhost from real or virtual server
virtualhost <STRING>
# Regular expression to search returned data against.
# A failure to match causes the check to fail.
regex <STRING>
# Reverse the sense of the match, so a match of the
# returned text causes the check to fail.
regex_no_match
# Space separated list of options for regex.
# See man pcre2api for a description of the options.
# The following option are supported:
# allow_empty_class alt_bsux auto_callout caseless
# dollar_endonly dotall dupnames extended firstline
# match_unset_backref multiline never_ucp never_utf
# no_auto_capture no_auto_possess no_dotstar_anchor
# no_start_optimize ucp ungreedy utf never_backslash_c
# alt_circumflex alt_verbnames use_offset_limit
regex_options <OPTIONS>
# For complicated regular expressions a larger stack
# may be needed, and this allows the start and maximum
# sizes in bytes to be specified. For more details see
# the documentation for pcre2_jit_stack_create()
regex_stack <START> <MAX>
# The minimum offset into the returned data to start
# checking for the regex pattern match. This can save
# processing time if the returned data is large.
regex_min_offset <OFFSET>
# The maximum offset into the returned data for the
# start of the subject match.
regex_max_offset <OFFSET>
# SSL_GET only - see SSL_GET below for description
tls_compliant
}
}
Breaking it down, url by url 🦋
path <STRING>— an url to test. Multipleurlentries are allowed here, so go off. Examples:path /, orpath /mrtg2/.digest <STRING>— healthcheck needs digest, or status_code and digest. Digest is computed withgenhash. Example:digest 9b3a0c85a887a256d6939da88aabd8cd.status_code <INTEGER|RANGE> [<INTEGER|RANGE>] ...— status code returned in the HTTP header. Examples:status_code 200, orstatus_code 200-299 400-499 503 505. Default is200-299.virtualhost <STRING>— VirtualHost string, e.g.virtualhost www.firewall.loc. If not set, it usesvirtualhostfrom the real or virtual server.regex <STRING>— regular expression to search returned data against. A failure to match causes the check to fail.regex_no_match— reverses the sense of the match, so a match of the returned text causes the check to fail. chaotic but valid 🌀regex_options <OPTIONS>— space separated list of options for regex. Seeman pcre2apifor a description of the options. Supported ones are:allow_empty_class alt_bsux auto_callout caseless dollar_endonly dotall dupnames extended firstline match_unset_backref multiline never_ucp never_utf no_auto_capture no_auto_possess no_dotstar_anchor no_start_optimize ucp ungreedy utf never_backslash_c alt_circumflex alt_verbnames use_offset_limit.regex_stack <START> <MAX>— for complicated regular expressions a larger stack may be needed, and this lets you specify the start and maximum sizes in bytes. For more details see the documentation forpcre2_jit_stack_create().regex_min_offset <OFFSET>— the minimum offset into the returned data to start checking for the regex pattern match. This can save processing time if the returned data is large. efficiency queen 👑regex_max_offset <OFFSET>— the maximum offset into the returned data for the start of the subject match.tls_compliant— SSL_GET only, seeSSL_GETbelow for description. (Yes it's sitting at the bottom of theurlblock in the original, we don't make the rules, we just relay them 🙈)
🔒 SSL_GET
SSL_GET {
# when provided, send Server Name Indicator during SSL handshake
enable_sni
# Comply with TLS protocol - send close_notify alert
# (see SSL_set_quiet_shutdown(3) man page)
tls_compliant
}
enable_sni— when provided, sends Server Name Indicator during the SSL handshake.tls_compliant— comply with TLS protocol, i.e. send aclose_notifyalert (see theSSL_set_quiet_shutdown(3)man page). polite and correct, we love to see it 🎀
🔌 TCP_CHECK
# TCP healthchecker
TCP_CHECK {
# No additional options
}
TCP_CHECK— no additional options. literally the low-maintenance friend of the checker family 😌
📧 SMTP_CHECK
# SMTP healthchecker
SMTP_CHECK {
# Optional string to use for the SMTP HELO request
helo_name <STRING>|<QUOTED-STRING>
}
helo_name <STRING>|<QUOTED-STRING>— optional string to use for the SMTPHELOrequest. quoted strings are valid, so go feral with it ✨
🌐 DNS_CHECK
# DNS healthchecker. Uses UDP protocol.
DNS_CHECK {
# The retry default is 3.
# DNS query type
# A|NS|CNAME|SOA|MX|TXT|AAAA
# The default is SOA
type <STRING>
# Domain name to use for the DNS query
# The default is . (dot)
name <STRING>
}
DNS_CHECK— uses UDP protocol. And heads up: theretrydefault is3.type <STRING>— DNS query type, one ofA,NS,CNAME,SOA,MX,TXT,AAAA. Default isSOA.name <STRING>— domain name to use for the DNS query. Default is.(dot). yes literally just a dot, it's giving minimalism 🫶
🛠 MISC_CHECK
# MISC healthchecker, run a program
MISC_CHECK {
# The retry default is 0.
# External script or program
misc_path <STRING>|<QUOTED-STRING>
# Script execution timeout
misc_timeout <INTEGER>
MISC_CHECK— runs a program. Theretrydefault is0.misc_path <STRING>|<QUOTED-STRING>— external script or program.misc_timeout <INTEGER>— script execution timeout.
# If misc_dynamic is set, the exit code from healthchecker
# is used to dynamically adjust the weight as follows:
# exit status 0: svc check success, weight
# unchanged.
# exit status 1: svc check failed.
# exit status 2-255: svc check success,
# then the RS weight is increased by
# (exit status - 2 - rs configured weight).
# An exit status of 10 will set the RS weight to 10. If
# the exit status subsequently changes to 20, the RS
# weight will become 20.
# If there is only one MISC_CHECK and no FILE_CHECKers
# the effect is to set the RS weight to two less than
# the exit status.
# (for example: an exit status of 255 would set
# weight to 253 if no other MISC_CHECKers or
# FILE_CHECKers where configured on the RS)
misc_dynamic
misc_dynamic— bestie, this one is the main character 👑 if it's set, the exit code from the healthchecker is used to dynamically adjust the weight, like this:- exit status
0— svc check success, weight unchanged. - exit status
1— svc check failed. - exit status
2-255— svc check success, then the RS weight is increased by(exit status - 2 - rs configured weight). An exit status of10will set the RS weight to10. If the exit status subsequently changes to20, the RS weight will become20. If there is only oneMISC_CHECKand noFILE_CHECKers, the effect is to set the RS weight to two less than the exit status. For example: an exit status of255would set weight to253if no otherMISC_CHECKers orFILE_CHECKers were configured on the RS.
- exit status
# Specify the username/groupname that the script should
# be run under.
# If GROUPNAME is not specified, the group of the user
# is used
user USERNAME [GROUPNAME]
}
user USERNAME [GROUPNAME]— specifies the username/groupname the script should be run under. IfGROUPNAMEis not specified, the group of the user is used. inclusivity win 🌈
🛰 BFD_CHECK
# BFD instance name to check
BFD_CHECK {
name <STRING>
}
name <STRING>— BFD instance name to check. that's it, that's the whole thing. (Remember:BFD_CHECKhas none of the standard options.)
📡 PING_CHECK
# PING healthchecker
# Note: using this checker may cause /proc/sys/net/ipv4/ping_group_range to be
# updated to allow root to use an IPPROTO_ICMP socket.
PING_CHECK {
# No additional options
}
PING_CHECK— no additional options.
⚠️ heads up: using this checker may cause/proc/sys/net/ipv4/ping_group_rangeto be updated to allow root to use anIPPROTO_ICMPsocket. so like, it's touching your sysctl, be aware 👀
📨 UDP_CHECK
# UDP healthchecker
# Note: for this checker to work properly, it relies on ICMP error messages such as
# HOST_UNREACH, NET_UNREACH, PORT_UNREACH. HOST_UNREACH relies on ARP requests
# timing out, and so connect_timeout should be long enough to allow for this (e.g.
# at least 4 seconds).
# If payload is specified, the HEX_STR will be sent as the UDP data, otherwise a
# random payload will be sent.
# If require_reply is specified, the received data length is checked to ensure that it
# lies between min_reply_length and max_reply_length.
# If require_reply without a hex string is specified, udp reply data must be received
# but the data content is not checked.
# If a require_reply HEX_STR is specified, the reply data will be checked against the
# HEX_STR, which must match up to the minimum of the received data length and the length
# of the require_reply HEX_STR.
# The format of HEX_STR is quite free format, for example:
# Ab12f 3 456 546443123
# would be interpreted as:
# AB 12 0F 03 45 06 54 64 43 12 03
# For the require_reply HEX_STR, a character can be specified as X or x, in which case
# the value of those 4 bits in the reply is ignored. This allows, for example, for
# some form of counter or otherwise.
# It may be that you will want to use PING_CHECK to the same server as well.
UDP_CHECK {
payload <HEX_STR>
require_reply [<HEX_STR>] # Require a reply packet for check to be successful
min_reply_length <INT> # default 0
max_reply_length <INT> # default is 255
}
Okay so for this one, take a seat, there's lore 📖
⚠️ for this checker to work properly, it relies on ICMP error messages such asHOST_UNREACH,NET_UNREACH,PORT_UNREACH.HOST_UNREACHrelies on ARP requests timing out, and soconnect_timeoutshould be long enough to allow for this (e.g. at least 4 seconds). don't skimp, bestie 😤
payload <HEX_STR>— ifpayloadis specified, theHEX_STRwill be sent as the UDP data, otherwise a random payload will be sent.require_reply [<HEX_STR>]— ifrequire_replyis specified, the received data length is checked to ensure that it lies betweenmin_reply_lengthandmax_reply_length. Ifrequire_replyis specified without a hex string, udp reply data must be received but the data content is not checked. If arequire_replyHEX_STRis specified, the reply data will be checked against theHEX_STR, which must match up to the minimum of the received data length and the length of therequire_replyHEX_STR. comment in the config says it best:# Require a reply packet for check to be successful.min_reply_length <INT>— default0.max_reply_length <INT>— default is255.
And the HEX_STR format? quite free format, fr fr. For example:
Ab12f 3 456 546443123
would be interpreted as:
AB 12 0F 03 45 06 54 64 43 12 03
For the require_reply HEX_STR, a character can be specified as X or x, in which case the value of those 4 bits in the reply is ignored. This allows, for example, for some form of counter or otherwise. flexible icon behavior 🌸
Also, it may be that you will want to use PING_CHECK to the same server as well. just a lil suggestion from the config, take it or leave it 🫶
# File checker
# This reads and monitors the contents of a file, where STRING is the name specified
# in the track_file configuration block (see above).
FILE_CHECK {
track_file <STRING>
# If dynamic is set, the value from the file is used
# to dynamically adjust the weight by adding the weight
# to the quorum and the LVS weight
dynamic
# The weight multiplier to apply to the value read from the file
weight <-2147483647..2147483647> [reverse]
}
}
}
okay so bestie, lemme translate 🫶 — this is the file checker. It reads a file and monitors what's inside it, and STRING is the name you already set up in the track_file config block (scroll up, it's right there). Then:
dynamic— when you set this, the value from the file gets used to dynamically adjust the weight, by adding the weight to the quorum and the LVS weight ✨weight<-2147483647..2147483647>[reverse]— the weight multiplier that gets applied to the value read from the file (and yeah,reverseis a thing you can tack on the end 💅)
# Parameters used for SSL_GET check.
# If none of the parameters are specified, the SSL context
# will be auto generated.
SSL {
# Password
password <STRING>
# CA file
ca <STRING>
# Certificate file
certificate <STRING>
# Key file
key <STRING>
}
These are the params for the SSL_GET check 💖 and lowkey the best part: if you don't specify any of them, the SSL context just gets auto generated. Iconic.
password <STRING>— the password 🎀ca <STRING>— the CA file 🌸certificate <STRING>— the certificate file 🦋key <STRING>— the key file 🔑
🌈 ADVANCED CONFIGURATION 🌈
Not gonna lie, this part is giving main character energy 👑. The configuration parser got extended to support advanced features like conditional configuration and parameter substitution. Why do you care? Because these are literally made for scripted environments where config templates get generated — think datacenters, bestie 🏢✨.
✨ Conditional configuration and configuration id ✨
So the config-id defaults to the first part of the node name as returned by uname, and you can override it with the -i or --config-id command line option 💅.
Here's the tea: any configuration line that starts with @ is a conditional configuration line 🎀. The word immediately following the @ — and yes, immediately, like no space between them — gets compared against the config-id, and if they don't match, the whole configuration line just gets ignored 🙈.
But wait, there's more: @^ is a negative comparison, so if the word immediately following does NOT match the config-id, then the configuration line IS included 👑. Flip the whole vibe.
The whole point of this? It lets a single configuration file be used across multiple systems, where the only differences are probably the router_id, the vrrp instance priorities, and maybe interface names and unicast addresses 🌸.
For example:
global_defs {
@main router_id main_router
@backup router_id backup_router
}
...
vrrp_instance VRRP {
...
@main unicast_src_ip 1.2.3.4
@backup unicast_src_ip 1.2.3.5
@backup2 unicast_src_ip 1.2.3.6
unicast_peer {
@^main 1.2.3.4
@^backup 1.2.3.5
@^backup2 1.2.3.6
}
...
}
So if keepalived gets invoked with -i main, the router_id will be set to main_router 💖. If it's invoked with -i backup, then it's backup_router. And if it's not invoked with -i, or it's invoked with -i and literally anything else, then the router_id will not be set at all 😭. The unicast peers for main will be 1.2.3.5 and 1.2.3.6. Slay.
🦋 Parameter substitution 🦋
Substitutable parameters are a whole thing, and honestly? Obsessed. The format for defining a parameter is:
$PARAMETER=VALUE
Rules of the game, bestie, don't skip these 📋:
- there must be no space before the
= - only whitespace may preceed to
$ - empty values are allowed 👌
- parameter names can be made up of any combination of
A-Za-z0-9and_, but cannot start with a digit - parameter names starting with an underscore should be considered reserved names that keepalived will define for various pre-defined options 👑
Once a parameter is defined, any occurrence of $PARAMETER followed by whitespace — or any occurrence of ${PARAMETER}, which need not be followed by whitespace — will be replaced by VALUE ✨.
And the replacement is recursive, which is the iconic part 💅: if a parameter value itself includes a replaceable parameter, then after the first substitution, the parameter in the value will then be replaced. And important — the substitution is done at replacement time, not at definition time. Like so:
$ADDRESS_BASE=10.2.${ADDRESS_BASE_SUB}
$ADDRESS_BASE_SUB=0
${ADDRESS_BASE}.100/32
$ADDRESS_BASE_SUB=10
${ADDRESS_BASE}.100/32
will produce:
10.2.0.100/32
10.2.10.100/32
Lowkey look at that glow-up 😭🔥
And note in the examples above the use of both ADDRESS_BASE and ADDRESS_BASE_SUB required braces ({}) since the parameters were not followed by whitespace — after the first substitution which produced 10.2.${ADDRESS_BASE_SUB}.100/32, the parameter is still not followed by whitespace 🫠.
Now, if a parameter is not defined, it will not be replaced at all. So for example ${UNDEF_PARAMETER} will remain in the configuration if it's undefined 👀. Which is actually good news: it means existing configuration that contains a $ character (say, in a script definition) will not be changed, as long as no new parameter definitions are added to the configuration 🎀.
Parameter substitution works in conjunction with conditional configuration, fr fr. For example:
@main $PRIORITY=240
@backup $PRIORITY=200
...
vrrp_instance VI_0 {
priority $PRIORITY
}
will produce:
...
vrrp_instance VI_0 {
priority 240
}
if the config_id is main.
$IF_MAIN=@main
$IF_MAIN priority 240
will produce:
priority 240
if the config_id is main and nothing if the config_id is not main,
although why anyone would want to use this rather than simply the
following is not known (but still possible):
@main priority 240
Honestly? Respect the chaos 💀 but like... just use @main priority 240.
Multiline definitions are also supported 💖 — BUT, and this is the catch, when you use them there must be nothing on the line after the parameter name 🚨. A multiline definition is specified by ending each line except the last with a \ character.
Example:
$INSTANCE= \
vrrp_instance VI_${NUM} { \
interface eth0.${NUM} \
use_vmac vrrp${NUM}.1 \
virtual_router_id 1 \
@high priority 130 \
@low priority 120 \
advert_int 1 \
virtual_ipaddress { \
10.0.${NUM}.254/24 \
} \
track_script { \
offset_instance_${NUM} \
} \
}
$NUM=0
$INSTANCE
$NUM=1
$INSTANCE
And the use of multiline definitions can be nested 🪆✨. Example:
$RS= \
real_server 192.168.${VS_NUM}.${RS_NUM} 80 { \
weight 1 \
inhibit_on_failure \
smtp_alert \
MISC_CHECK { \
misc_path "${_PWD}/scripts/vs.sh RS_misc.${INST}.${VS_NUM}.${RS_NUM}.0 10.0.${VS_NUM}.4:80->192.168.${VS_NUM}.${RS_NUM}:80" \
} \
MISC_CHECK { \
misc_path "${_PWD}/scripts/vs.sh RS_misc.${INST}.${VS_NUM}.${RS_NUM}.1 10.0.${VS_NUM}.4:80->192.168.${VS_NUM}.${RS_NUM}:80" \
} \
notify_up "${_PWD}/scripts/notify.sh RS_notify.${INST}.${VS_NUM}.${RS_NUM} UP 10.0.${VS_NUM}.4:80->192.168.${VS_NUM}.${RS_NUM}:80" \
notify_down "${_PWD}/scripts/notify.sh RS_notify.${INST}.${VS_NUM}.${RS_NUM} DOWN 10.0.${VS_NUM}.4:80->192.168.${VS_NUM}.${RS_NUM}:80" \
}
$VS= \
virtual_server 10.0.${VS_NUM}.4 80 { \
quorum 2 \
quorum_up "${_PWD}/scripts/notify.sh VS_notify.${INST} UP 10.0.${VS_NUM}.4:80" \
quorum_down "${_PWD}/scripts/notify.sh VS_notify.${INST} DOWN 10.0.${VS_NUM}.4:80" \
$RS_NUM=1 \
$RS \
$RS_NUM=2 \
$RS \
$RS_NUM=3 \
$RS \
}
$VS_NUM=0
$ALPHA=alpha
$VS
$VS_NUM=1
$ALPHA=
$VS
And the moment you've all been waiting for: the above will create 2 virtual servers, each with 3 real servers 🎉👑🔥. Template energy ATE.
🧚 Pre-defined definitions 🧚
These babies are already defined for you, no setup required 💜:
${_PWD}— the directory of the current configuration file (this can be changed if using theincludedirective) 🎀${_INSTANCE}— the instance name (as defined by the-ioption, defaults tohostname) ✨${_RANDOM [MIN [MAX]]}— replaced by a random integer in the range[MIN, MAX], whereMINandMAXare optional non-negative integers. Defaults areMIN=0andMAX=32767🎲${_ENV ENV_VAR}— replaced by the value of the environment variable$ENV_VAR🌸${_HASH}— replaced by a#character, which would otherwise start a comment 💅${_BANG}— replaced by a!character, which would otherwise start a comment 💀
Additional pre-defined definitions will be added as their need is identified 👀. It'll normally be quite straightforward to add additional pre-defined definitions, so if you need one, or you have a good idea for one, then raise an issue at https://github.com/acassen/keepalived/issues requesting it 🫶 — they're genuinely nice about it, iykyk.
🌀 Sequence blocks 🌀
Okay bestie, so a line that starts with ~SEQ(var, start, step, end) makes keepalived run the rest of that line over and over. It sets $var to start first, then bumps it by step each time, and stops when it’s greater than end. The step can be left out — then it defaults to 1 or -1, depending on whether end is greater or less than start. The start can also be left out — then it defaults to 1 if end > 0, or -1 if end < 0. And ~SEQx(...) is literally the same as ~SEQ(...), except the variable gets formatted in hexadecimal, which is cute for IPv6 addresses. ✨
Note: right now you have to use different variables for the~SEQblock from any variable that was already defined before — including a variable used in a previous~SEQblock. This may change in the future, so do not rely on a~SEQblock variable being defined after the end of the block. No cap. 🫶
Examples, because examples ate:
~SEQ(SUBNET, 0, 3) ip_address 10.0.${SUBNET}.1
would produce:
ip_address 10.0.0.1
ip_address 10.0.1.1
ip_address 10.0.2.1
ip_address 10.0.3.1
and:
~SEQx(SUBNET, 144, 16, 192) ip_address fe80::20:${SUBNET}:1
or better:
~SEQx(SUBNET, 0x90, 0x10, 0xc0) ip_address fe80::20:${SUBNET}:1
would produce:
ip_address fe80::20:90:1
ip_address fe80::20:a0:1
ip_address fe80::20:b0:1
ip_address fe80::20:c0:1
Another example:
virtual_ipaddress {
~SEQx(AD2, 0x90, 0x10, 0xc0) ~SEQx(AD1, 0x12, -1, 0x0c) fe81::10:${AD2}:${AD1}
}
You can absolutely have multiple ~SEQ elements on a line, so for example:
$VI4= \
track_file offset_instance_4.${IF}.${NUM}.${ID} { \
file "${_PWD}/679/track_files/4.${IF}.${NUM}.${ID}" \
weight -100 \
} \
vrrp_instance vrrp4.${IF}.${NUM}.${ID} { \
interface bond${IF}.${NUM} \
use_vmac vrrp4.${IF}.${NUM}.${ID} \
virtual_router_id ${ID} \
priority 130 \
virtual_ipaddress { \
10.${IF}.${NUM}.${ID}/24 \
} \
track_file { \
offset_instance_4.${IF}.${NUM}.${ID} \
} \
}
~SEQ(IF,0,7) ~SEQ(NUM,0,31) ~SEQ(ID,1,254) $VI4
will produce 65024 vrrp instances with names from vrrp4.0.0.1 through to vrrp4.7.31.254. Iconic. 👑
🌈 List blocks 🌈
List blocks are basically sequence blocks’ cousin, except the values you substitute into the variable are listed right there in the ~LST specification. 💅
A line starting with ~LST(var, val1, val2, val3) makes the rest of the line get processed multiple times, with $var set initially to val1, then val2, and finally val3. You can specify any number of values, as long as there’s at least one value — although only one value would be kinda pointless, ngl.
If you want to substitute more than one variable at a time, the variables and values need to be wrapped in {...} blocks. For example:
~LST({IP, IP1}, {10,1},{20,4},{5,6},{12,8}) 192.168.${IP}.${IP1}
would first set IP=10 and IP1=1, then IP=20 and IP1=4, etc., and produces:
192.168.10.1
192.168.20.4
192.168.5.6
192.168.12.8
List blocks can be nested, so:
~LST(IP, 1, 2, 3, 4) ~LST(IP1, 5,6,7) 192.169.${IP}.${IP1}
produces:
192.169.1.5
192.169.1.6
192.169.1.7
192.169.2.5
192.169.2.6
192.169.2.7
192.169.3.5
192.169.3.6
192.169.3.7
192.169.4.5
192.169.4.6
192.169.4.7
Finally, list blocks and sequence blocks can be combined, so:
~LST({IP, IP1}, {10,1},{20,4},{5,6},{12,8}) ~SEQ(IP2,168,2,172) 192.${IP2}.${IP}.${IP1}
produces:
192.168.10.1
192.170.10.1
192.172.10.1
192.168.20.4
192.170.20.4
192.172.20.4
192.168.5.6
192.170.5.6
192.172.5.6
192.168.12.8
192.170.12.8
192.172.12.8
🧚 KERNEL SETTINGS 🧚
So it’s been identified that if proxy_arp and proxy_arp_pvlan are enabled on an interface that has VIPs or eVIPs configured on it, it can cause incorrect replies to ARP requests — because the proxy replies to the ARP request as well as the keepalived host. 😭 Both need to be set to 0 to function properly. Periodt.
👑 AUTHORS 👑
Initial by Joseph Mack. Extensive updates by Alexandre Cassen & Quentin Armitage. Slay, honestly. 💖
🔥 SEE ALSO 🔥
ipvsadm(8)ip --help
That’s the whole page, bestie! Source/version: Keepalived · date: 2026-07-05 · page: keepalived.conf(5). Mwah 💋✨
📦 where this page comes from
- arch rolling: keepalived 2.4.3-1 · GPL-2.0-or-later
- 💖 girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms