Guides

fail2ban for Nextcloud

Nextcloud slows down password guessing on its own, but it never blocks an address. fail2ban adds real bans: it reads the Login failed entries in nextcloud.log and firewalls the source. Here is a tested filter and the log path for every common install type.

Updated 5 min readChecked against fail2ban 1.1.1

To protect Nextcloud with fail2ban, create a filter that matches "remoteAddr":"<ADDR>" and "message":"Login failed: in Nextcloud's JSON log, then add a jail with logpath set to your nextcloud.log (by default <datadirectory>/nextcloud.log), port = http,https and backend = auto. Nextcloud must log at loglevel 2 or lower, because failed logins are written as warnings. For Docker and AIO, add banaction = iptables-allports and chain = DOCKER-USER.

/etc/fail2ban/filter.d/nextcloud.conf
[Definition]
failregex = ^\{.*?"remoteAddr":"<ADDR>".*?"message":"(?:Login failed|Two-factor challenge failed):
datepattern = "time":"%%Y-%%m-%%dT%%H:%%M:%%S%%z"
/etc/fail2ban/jail.d/nextcloud.local
[nextcloud]
enabled  = true
backend  = auto
filter   = nextcloud
logpath  = /var/www/nextcloud/data/nextcloud.log
port     = http,https
maxretry = 5
findtime = 1h
bantime  = 1d
bash
sudo fail2ban-client reload
sudo fail2ban-client status nextcloud

Nextcloud's brute-force protection vs fail2ban#

Nextcloud has built-in brute-force protection ('auth.bruteforce.protection.enabled' => true by default). It records failed attempts per IP and delays further requests from that address, so guessing gets very slow. It doesn't block the address, and the delayed requests still reach PHP. You can inspect and reset it with occ:

bash
sudo -u www-data php occ security:bruteforce:attempts 203.0.113.42
sudo -u www-data php occ security:bruteforce:reset 203.0.113.42

fail2ban works one layer lower: after a few failures the address is dropped at the firewall and can't reach the web server at all. Keep both on — they complement each other. The occ reset command is also what you need when a legitimate user is throttled; unbanning in fail2ban alone doesn't clear Nextcloud's delay.

What Nextcloud logs on a failed login#

Nextcloud writes one JSON object per line. A failed web login is logged at level 2 (warning):

json
{"reqId":"Zx3kq9Lw2mT8bA1cV7nR","level":2,"time":"2026-10-25T18:36:10+00:00","remoteAddr":"203.0.113.42","user":"--","app":"no app in context","method":"POST","url":"/login","scriptName":"/index.php","message":"Login failed: admin (Remote IP: 203.0.113.42)","userAgent":"Mozilla/5.0","version":"32.0.1.2","data":[]}

A failed second factor produces "message":"Two-factor challenge failed: alice (Remote IP: …)", also at level 2. Both messages come straight from Nextcloud's source (LoggedInCheckCommand.php and TwoFactorChallengeController.php). The log level must be 2 or lower to keep them:

bash
sudo -u www-data php occ config:system:get loglevel     # empty or 0-2 is fine
sudo -u www-data php occ config:system:set loglevel --value=2 --type=integer

How the filter works#

  • "remoteAddr":"<ADDR>" is the address Nextcloud determined for the request — the real client when trusted_proxies is set correctly (see below).
  • .*? skips the other fields. Values controlled by the client, such as the URL or user name, are JSON-escaped, so they can't contain an unescaped "message":" and fake a match.
  • The time field uses ISO 8601 with a UTC offset (2026-10-25T18:36:10+00:00), and %%z reads that offset. fail2ban therefore gets the right time whatever Nextcloud's logtimezone is. If you changed logdateformat to something without an offset, adjust datepattern and set the jail's logtimezone to Nextcloud's timezone.

We tested the filter with fail2ban-regex against a UTC line, an IPv6 line with a +02:00 offset and an unrelated entry: both failed logins matched with the correct time, the third line was missed. Nextcloud's own documentation has an equivalent filter that additionally catches Trusted domain error. messages.

Find your nextcloud.log#

The log lives in the data directory unless logfile is set in config.php:

bash
sudo -u www-data php occ config:system:get logfile
sudo -u www-data php occ config:system:get datadirectory
Install type Typical logpath on the host
Manual / package install <datadirectory>/nextcloud.log, e.g. /var/www/nextcloud/data/nextcloud.log
Snap /var/snap/nextcloud/current/logs/nextcloud.log
Docker, official nextcloud image <volume for /var/www/html>/data/nextcloud.log, e.g. /var/lib/docker/volumes/nextcloud_nextcloud/_data/data/nextcloud.log
Nextcloud AIO /var/lib/docker/volumes/nextcloud_aio_nextcloud/_data/data/nextcloud.log

For Docker named volumes, docker volume inspect <name> prints the Mountpoint. The AIO path is the one given in AIO's own README. The log must be readable by root (fail2ban) — it is by default.

Nextcloud in Docker and AIO#

Docker forwards published ports to the container, so bans in the INPUT chain don't apply. Use DOCKER-USER:

/etc/fail2ban/jail.d/nextcloud.local
[nextcloud]
enabled   = true
backend   = auto
filter    = nextcloud
logpath   = /var/lib/docker/volumes/nextcloud_aio_nextcloud/_data/data/nextcloud.log
banaction = iptables-allports
chain     = DOCKER-USER
maxretry  = 5
findtime  = 1h
bantime   = 1d

iptables-allports covers every AIO port (80, 443, 8080, 8443 and the Talk port 3478) without having to list them; note that 3478 also uses UDP, so add protocol = tcp,udp if you want bans to cover Talk's UDP traffic. AIO also offers a community fail2ban container that you can add to the stack instead. Background and the nftables variant: fail2ban with Docker containers.

Nextcloud behind a reverse proxy#

Behind nginx, Apache, Caddy, Traefik, NPM or Cloudflare, Nextcloud sees the proxy's address and logs it as remoteAddr — and fail2ban bans the proxy. Tell Nextcloud which proxies to trust and which header carries the client:

config/config.php
'trusted_proxies' => ['10.0.0.5', '172.18.0.0/16'],
'forwarded_for_headers' => ['HTTP_X_FORWARDED_FOR'],

forwarded_for_headers defaults to HTTP_X_FORWARDED_FOR, so trusted_proxies is usually all you add; for Docker, list the proxy container's network. Then check that new Login failed lines show a public address. If fail2ban runs on the proxy host and the proxy publishes the ports, the ban must still go where the traffic enters — on the proxy's host, in DOCKER-USER if the proxy is a container. More in fail2ban bans the proxy IP.

Test it#

bash
sudo fail2ban-regex /var/www/nextcloud/data/nextcloud.log /etc/fail2ban/filter.d/nextcloud.conf

The summary should list matches if the log contains failed logins (the fail2ban-regex guide explains the output). Then log in with a wrong password from a phone on mobile data until the ban:

bash
sudo tail -f /var/log/fail2ban.log
log
2026-10-25 18:36:11,027 fail2ban.actions        [1997]: NOTICE  [nextcloud] Ban 203.0.113.42

Unban with sudo fail2ban-client set nextcloud unbanip 203.0.113.42 and reset Nextcloud's throttle with occ security:bruteforce:reset 203.0.113.42. Nextcloud desktop and mobile clients with an old saved password are the classic source of self-inflicted bans; choose maxretry and findtime with that in mind (how they interact).

FAQ#

What is the fail2ban filter for Nextcloud?#

fail2ban ships none, so create /etc/fail2ban/filter.d/nextcloud.conf with failregex = ^\{.*?"remoteAddr":"<ADDR>".*?"message":"(?:Login failed|Two-factor challenge failed): and datepattern = "time":"%%Y-%%m-%%dT%%H:%%M:%%S%%z". It matches the JSON lines Nextcloud writes for failed logins and failed second factors.

Where is nextcloud.log?#

By default in the data directory: <datadirectory>/nextcloud.log, unless logfile is set in config.php. Snap installs use /var/snap/nextcloud/current/logs/nextcloud.log; AIO uses /var/lib/docker/volumes/nextcloud_aio_nextcloud/_data/data/nextcloud.log on the host.

Do I need fail2ban if Nextcloud has brute-force protection?#

Nextcloud's protection only slows requests from an address down; it never blocks it, and every request still costs PHP time. fail2ban drops the address at the firewall after a few failures. Running both is common; Nextcloud's own hardening guide describes a fail2ban setup on top of the built-in protection.

How do I use fail2ban with Nextcloud AIO?#

Run fail2ban on the host, point the jail's logpath at /var/lib/docker/volumes/nextcloud_aio_nextcloud/_data/data/nextcloud.log, and add banaction = iptables-allports with chain = DOCKER-USER, so bans apply to traffic Docker forwards to the AIO containers. Alternatively, add AIO's community fail2ban container.

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