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.
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.
# the usual workflow
sudo nano /etc/fail2ban/jail.local # your overrides
sudo fail2ban-client -t # test the configuration
sudo fail2ban-client reload # apply itThe /etc/fail2ban directory layout#
/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:
[DEFAULT]
bantime = 1h
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24
[sshd]
enabled = trueConfig 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:
jail.confjail.d/*.conf, in alphabetical orderjail.localjail.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/*.conflose tojail.local. Debian and Ubuntu ship/etc/fail2ban/jail.d/defaults-debian.conf(it enables[sshd]and, on Ubuntu 24.04 and Debian 13, setsbanaction = nftables). Anything you put injail.localoverrides it. jail.d/*.localbeatsjail.local. If a setting injail.localseems ignored, check whether a file likejail.d/custom.localsets 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.
[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 dayA 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
logpathfiles, severalactionlines, severalfailregexlines): 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:
# 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:
$ sudo fail2ban-client get sshd bantime
3600
$ sudo fail2ban-client get sshd findtime
600
$ sudo fail2ban-client get sshd maxretry
3Apply changes: reload#
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 reloadA 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.