# fail2ban for Jellyfin

> Protect Jellyfin with fail2ban: a tested filter for denied logins, log paths for native and Docker installs, the daily log file trap, DOCKER-USER bans and proxies.
>
> Source: https://fail2ban.dev/guides/jellyfin/ · Updated 2026-10-25 · Checked against fail2ban 1.1.1
> fail2ban.dev is an independent guide, not the official fail2ban project.

To protect Jellyfin with fail2ban, create a filter that matches `Authentication request for … has been denied (IP: "<ADDR>")`, then enable a jail that reads Jellyfin's log — either the daily files `/var/log/jellyfin/log_*.log` (native) or `/config/log/log_*.log` (Docker), or better the systemd journal, which avoids missing the new log file Jellyfin starts every day. For Docker, ban in the `DOCKER-USER` chain.

```ini
[Definition]
failregex = ^.*Authentication request (?:for .+ |without username )has been denied \(IP: "?<ADDR>"?\)\.\s*$
```
[jellyfin]
enabled      = true
backend      = systemd
journalmatch = _SYSTEMD_UNIT=jellyfin.service
filter       = jellyfin
port         = 8096,8920
maxretry     = 5
findtime     = 1h
bantime      = 1d
```

## What Jellyfin logs on a failed login

Jellyfin's user manager logs rejected logins at the `Information` level. In the log **files** the user name and IP are quoted:

```log
[2026-10-25 18:36:11.002 +00:00] [INF] [57] Jellyfin.Server.Implementations.Users.UserManager: Authentication request without username has been denied (IP: "2001:db8::25").
```

```log
```

The log level must stay at `Information` (the default in `logging.json`); at `Warning` or `Error` these lines are not written.

## The daily log file trap

Jellyfin writes to `log_YYYYMMDD.log` and starts a new file every day (keeping three by default). A jail with `logpath = /var/log/jellyfin/log_*.log` looks right, but fail2ban expands the wildcard **only when the jail starts**. The next day Jellyfin writes to a file fail2ban isn't watching, and the jail silently stops counting. Three ways around it:

1. **Read the journal** (native installs): the jail at the top of this page. No files, no rotation problem. Needs `python3-systemd` (`sudo apt install python3-systemd`). See [backends and the systemd journal](/config/backend/).
2. **Docker with the journald log driver**: shown below.
3. **Log files plus a daily reload**: keep the file-based jail and reload it shortly after midnight so the new file is picked up:

```ini
[jellyfin]
enabled  = true
backend  = auto
filter   = jellyfin
logpath  = /var/log/jellyfin/log_*.log
port     = 8096,8920
maxretry = 5
findtime = 1h
bantime  = 1d
```
5 0 * * * root /usr/bin/fail2ban-client reload jellyfin >/dev/null
```

## Jellyfin in Docker

In the official `jellyfin/jellyfin` and the LinuxServer images, logs are in `/config/log/` inside the container, so on the host they're under whatever directory you mounted at `/config`. The cleanest option is again to skip the files and send the container's console output to the journal:

```yaml
services:
  jellyfin:
    image: jellyfin/jellyfin
    container_name: jellyfin
    restart: unless-stopped
    ports:
      - "8096:8096"
    volumes:
      - ./config:/config
      - ./cache:/cache
      - /srv/media:/media:ro
    logging:
      driver: journald
```
[jellyfin]
enabled      = true
backend      = systemd
journalmatch = CONTAINER_NAME=jellyfin
filter       = jellyfin
banaction    = iptables-allports
chain        = DOCKER-USER
maxretry     = 5
findtime     = 1h
bantime      = 1d
```

## Jellyfin behind a reverse proxy

Behind nginx, Caddy, Traefik or Nginx Proxy Manager, Jellyfin logs the proxy's address unless you tell it to trust the proxy. In the Jellyfin dashboard, go to **Networking** and add the proxy's IP (or the Docker network) to **Known proxies**; Jellyfin then uses the `X-Forwarded-For` header the proxy sends. Check a new denied-login line: it must show a public address, not `127.0.0.1` or `172.x.x.x`. More on this in [fail2ban bans the proxy IP](/troubleshooting/reverse-proxy-ip/).

When the proxy terminates TLS, the ban must cover the proxy's ports (80 and 443) rather than 8096. With `iptables-allports` that's automatic; with a port-based action set `port = http,https`. Jails on the proxy's own access log (like the 4xx filter in the [nginx guide](/guides/nginx/)) can complement this, but Jellyfin's log is the precise source for failed logins.

## Test it

```bash
sudo fail2ban-regex /var/log/jellyfin/log_$(date +%Y%m%d).log /etc/fail2ban/filter.d/jellyfin.conf

# journal setup (native or Docker)
sudo fail2ban-regex systemd-journal /etc/fail2ban/filter.d/jellyfin.conf -m 'CONTAINER_NAME=jellyfin'

sudo fail2ban-client status jellyfin
```

Jellyfin itself can also disable a user account after a number of failed logins (a per-user setting). That protects the password but lets an attacker lock real users out; fail2ban blocks the attacker's address before it gets that far.

## FAQ

### What is the fail2ban filter for Jellyfin?

fail2ban doesn't ship one. Create `/etc/fail2ban/filter.d/jellyfin.conf` with `failregex = ^.*Authentication request (?:for .+ |without username )has been denied \(IP: "?<ADDR>"?\)\.\s*$`. It matches Jellyfin's denied-login lines in both the log files (quoted IP) and console output (unquoted).

### Where are the Jellyfin logs?

Native Linux installs write daily files to `/var/log/jellyfin/` (named `log_YYYYMMDD.log`); Docker images use `/config/log/` inside the container. Console output also goes to the systemd journal (`journalctl -u jellyfin`) or to `docker logs`, which fail2ban can read with the systemd backend.

### Why does my Jellyfin jail stop banning after a day?

Jellyfin starts a new log file every day, and fail2ban expands `log_*.log` only when the jail starts. Read the systemd journal instead (`backend = systemd`, `journalmatch = _SYSTEMD_UNIT=jellyfin.service` or `CONTAINER_NAME=jellyfin`), or reload the jail daily with `fail2ban-client reload jellyfin`.

### How do I use fail2ban with Jellyfin in Docker?

Run fail2ban on the host, give the container the journald log driver and use `journalmatch = CONTAINER_NAME=jellyfin`, or mount `/config` and point `logpath` at `config/log/log_*.log`. Ban with `banaction = iptables-allports` and `chain = DOCKER-USER`, because Docker-forwarded traffic bypasses the `INPUT` chain.

### Why does Jellyfin log 127.0.0.1 or a Docker IP for failed logins?

A reverse proxy is in front and Jellyfin doesn't trust it. Add the proxy's IP or network under Dashboard → Networking → Known proxies so Jellyfin reads the client address from `X-Forwarded-For`. Otherwise fail2ban would ban your own proxy.
