# fail2ban configuration: files, load order and overrides

> Where fail2ban's config files live, the exact order .conf, .local and jail.d files load in, how interpolation works, and how to see the effective config.
>
> Source: https://fail2ban.dev/config/ · Updated 2026-10-25 · Checked against fail2ban 1.1.1
> fail2ban.dev is an independent guide, not the official fail2ban project.

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
sudo nano /etc/fail2ban/jail.local      # your overrides
sudo fail2ban-client -t                 # test the configuration
sudo fail2ban-client reload             # apply it
```

```text
├── 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)
```
|---|---|---|
| `fail2ban.conf` | Daemon: [log level and target](/config/fail2ban-conf/), 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`:

```ini
[DEFAULT]
bantime = 1h
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24

[sshd]
enabled = true
```
Old tutorials say `cp jail.conf jail.local`. That works, but it freezes a copy of every stock jail and default. When an upgrade fixes a log path or adds an option in `jail.conf`, your full copy silently overrides it. Keep `jail.local` short: only your changes.

## 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](/config/jail-local/).
- **`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.

```ini
[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
```

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](/config/jail-options/).

### 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](/config/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
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'"
```

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

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

```bash
sudo fail2ban-client reload sshd     # reload a single jail
sudo systemctl reload fail2ban       # same as fail2ban-client reload
```

## Where to go next


- [jail.local example](/config/jail-local/) — a complete, commented, production-ready jail.local
- [Jail options reference](/config/jail-options/) — every option with type, default and example
- [bantime, findtime, maxretry](/config/bantime-findtime-maxretry/) — how the ban window works
- [Whitelist IPs](/config/whitelist/) — ignoreip, ignoreself and ignorecommand
- [Ban actions](/config/banaction/) — nftables, iptables, ufw, firewalld
- [Backends](/config/backend/) — log files vs the systemd journal

If the service won't start after an edit, the [config and regex errors](/troubleshooting/config-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.
