Configuration

fail2ban configuration: files, load order and overrides

Everything lives under /etc/fail2ban. The one rule that matters: never edit the .conf files, put your changes in .local files. This page shows why, and in which order fail2ban reads everything.

Updated 7 min readChecked against fail2ban 1.1.1

fail2ban's configuration lives in /etc/fail2ban/. fail2ban.conf configures the daemon (logging, socket, database), jail.conf defines the jails, and filter.d/ and action.d/ hold the filters and ban actions. Don't edit the .conf files: put your changes in /etc/fail2ban/jail.local or a file in jail.d/, which load later and override the stock values. Then run sudo fail2ban-client reload.

bash
# the usual workflow
sudo nano /etc/fail2ban/jail.local      # your overrides
sudo fail2ban-client -t                 # test the configuration
sudo fail2ban-client reload             # apply it

The /etc/fail2ban directory layout#

text
/etc/fail2ban/
├── fail2ban.conf        # daemon settings: loglevel, logtarget, socket, database
├── fail2ban.d/          # drop-in overrides for fail2ban.conf
├── jail.conf            # stock [DEFAULT] values and every predefined jail (all disabled)
├── jail.d/              # drop-in jail files (distro packages put defaults here)
├── jail.local           # your jail overrides (you create this)
├── filter.d/            # filters: failregex / ignoreregex per service (sshd.conf, nginx-http-auth.conf…)
├── action.d/            # actions: firewall commands, mail, reporting (nftables.conf, ufw.conf…)
├── paths-common.conf    # default log paths and backends
└── paths-debian.conf    # distro-specific paths (also -fedora, -arch, -opensuse, -freebsd, -osx)
File What it controls Edit it?
fail2ban.conf Daemon: log level and target, socket, pid file, SQLite database No, use fail2ban.local
jail.conf [DEFAULT] values (bantime 10m, findtime 10m, maxretry 5) and nearly 100 predefined jails, all disabled No, use jail.local
jail.d/*.conf, jail.d/*.local Drop-in jail settings, one file per jail or per topic Yes (create your own files)
filter.d/*.conf The regexes that detect failures No, copy to a new name or add a .local
action.d/*.conf The commands that ban and unban No, add a .local
paths-*.conf Log file locations and default backends per distro; since 1.1.1 also the default banaction No

The [INCLUDES] section at the top of jail.conf pulls in a paths-*.conf file first. Upstream ships before = paths-debian.conf; distribution packages can point it at their own file (paths-fedora.conf, paths-arch.conf, paths-opensuse.conf…). Open the top of your jail.conf to see which one yours uses. That file defines variables such as %(sshd_log)s and %(apache_error_log)s, which is why a jail like [sshd] has no hard-coded log path.

.conf vs .local: why you never edit jail.conf#

The .conf files belong to the package. An upgrade can replace jail.conf with a new version, and your edits either vanish or leave you with a .dpkg-dist / .rpmnew file to merge by hand. A .local file is never shipped by the package, so it survives upgrades.

A .local file doesn't need to be a full copy. It only needs the sections and options you want to change, and everything else still comes from the .conf:

/etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24

[sshd]
enabled = true

Config load order#

fail2ban reads each configuration in this order (verified in fail2ban/client/configreader.py). Later files win when they set the same option in the same section:

  1. jail.conf
  2. jail.d/*.conf, in alphabetical order
  3. jail.local
  4. jail.d/*.local, in alphabetical order

The same pattern applies to fail2ban.conf (→ fail2ban.d/*.conf → fail2ban.local → fail2ban.d/*.local) and to every filter and action: filter.d/sshd.conf is followed by filter.d/sshd.local, and action.d/nftables.conf by action.d/nftables.local.

Two consequences catch people out:

  • Distro defaults in jail.d/*.conf lose to jail.local. Debian and Ubuntu ship /etc/fail2ban/jail.d/defaults-debian.conf (it enables [sshd] and, on Ubuntu 24.04 and Debian 13, sets banaction = nftables). Anything you put in jail.local overrides it.
  • jail.d/*.local beats jail.local. If a setting in jail.local seems ignored, check whether a file like jail.d/custom.local sets the same option for the same jail.

jail.local vs jail.d: which should you use?#

Both work; pick one style and stick to it.

  • One jail.local: easiest to read, everything in one place. Good for a single server you configure by hand. See the annotated jail.local example.
  • jail.d/ drop-ins (jail.d/sshd.local, jail.d/nginx.local…): better for automation (Ansible, Puppet, Docker images), because each tool or role owns one file and you can enable a jail by adding a file instead of editing a shared one.

Use the .local extension for your own files in jail.d/ too. Both extensions load, but .local files load after every .conf, so yours win over anything a package drops in.

Sections: [DEFAULT] vs per-jail settings#

jail.conf is an INI file. [DEFAULT] holds values every jail inherits; each other section ([sshd], [nginx-http-auth], [recidive]…) is a jail and can override any of them.

/etc/fail2ban/jail.local
[DEFAULT]
bantime  = 1h      ; every jail bans for 1 hour…
maxretry = 5

[sshd]
enabled  = true
maxretry = 3       ; …but sshd bans after 3 failures
bantime  = 1d      ; and for one day

A jail's own value replaces the default, it doesn't add to it. That matters for list options like ignoreip: setting ignoreip inside [sshd] drops the [DEFAULT] list for that jail unless you include it with %(default/ignoreip)s (details on the whitelist page).

The section name is the jail name you use with fail2ban-client (fail2ban-client status sshd). Every option and its default is listed in the jail options reference.

Syntax rules#

  • Comments: # or ; at the start of a line. Inline comments need a space then ; (bantime = 1h ; one hour). A # in the middle of a line is part of the value.
  • Multi-line values (several logpath files, several action lines, several failregex lines): put each value on its own line and indent the continuation lines.
  • Option names are case-insensitive; values are not.

Interpolation: %(name)s variables#

fail2ban uses Python-style interpolation, so one value can reference another:

Syntax Meaning Example from jail.conf
%(name)s Value of name in the current section, or [DEFAULT] maxmatches = %(maxretry)s
%(__name__)s The current section (jail) name filter = %(__name__)s[mode=%(mode)s]
%(default/name)s Value from [DEFAULT], even if the jail overrides name ignoreip = %(default/ignoreip)s 203.0.113.9
%(known/name)s The previously loaded value of the same option, so a .local can extend a .conf instead of replacing it failregex = %(known/failregex)s
<tag> Filter/action parameters, filled in at runtime <ip>, <name>, <port>

%(__name__)s is why most jails need no filter = line: the jail [sshd] automatically uses filter.d/sshd.conf. The action = %(action_)s default expands to %(banaction)s[port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"], which is how one banaction setting drives every jail (see banaction).

Because % starts an interpolation, a literal percent sign must be written %%. You'll see this in datepattern = %%Y-%%m-%%d %%H:%%M:%%S.

See the effective configuration#

You rarely need to guess which file won. Ask fail2ban:

bash
# test the configuration without touching the running server
sudo fail2ban-client -t

# dump the fully merged config as fail2ban will apply it
sudo fail2ban-client -d

# just one jail's settings
sudo fail2ban-client -d | grep "'sshd'"

-t prints OK: configuration test is successful or the parse error. -d prints one line per setting, as Python lists such as ['set', 'sshd', 'bantime', '1d'], with every interpolation already resolved. --dp prints the same thing more readably.

To read values from the running server instead of the files:

output
$ sudo fail2ban-client get sshd bantime
3600
$ sudo fail2ban-client get sshd findtime
600
$ sudo fail2ban-client get sshd maxretry
3

Apply changes: reload#

bash
sudo fail2ban-client reload          # re-read all config files
sudo fail2ban-client reload sshd     # reload a single jail
sudo systemctl reload fail2ban       # same as fail2ban-client reload

A reload keeps existing bans. systemctl restart fail2ban also works but takes longer; bans are restored from the database either way. More options, including --restart and --unban, are on restart and reload.

Where to go next#

If the service won't start after an edit, the config and regex errors page lists the common messages and fixes.

FAQ#

Where is the fail2ban config file located?#

In /etc/fail2ban/. The daemon settings are in fail2ban.conf, jails in jail.conf, filters in filter.d/ and actions in action.d/. Put your own changes in /etc/fail2ban/jail.local or /etc/fail2ban/jail.d/*.local, never in the .conf files, which package upgrades overwrite.

What is the difference between jail.conf and jail.local?#

jail.conf is the stock file shipped by the package, with the default values and every predefined jail. jail.local is your override file: fail2ban reads it after jail.conf and jail.d/*.conf, so its values win. It only needs the options you change, and upgrades never touch it.

Should I use jail.local or jail.d?#

Either. Both are read after jail.conf, and jail.d/*.local files are read after jail.local. Use a single jail.local for a hand-managed server; use one file per jail in jail.d/ when configuration management or containers generate the config. Avoid setting the same option in both.

Do I need to restart fail2ban after changing the config?#

No, a reload is enough: sudo fail2ban-client reload (or sudo systemctl reload fail2ban). It re-reads all files and keeps current bans. Use fail2ban-client reload <jail> to apply changes to one jail only.

Spotted something wrong or out of date? Tell us — every page is checked against the current fail2ban release.