Guides

fail2ban for Vaultwarden

fail2ban 1.1.1 ships a Vaultwarden filter that catches wrong passwords, bad TOTP codes and wrong admin tokens. You only need to make Vaultwarden write a log file and put the ban where Docker traffic passes.

Updated 8 min readChecked against fail2ban 1.1.1

To protect Vaultwarden with fail2ban, set LOG_FILE=/data/vaultwarden.log on the container so failed logins are written to a file in the data volume, then enable the stock vaultwarden jail with logpath pointing to that file on the host, backend = auto, and — because Vaultwarden runs in Docker — banaction = iptables-allports and chain = DOCKER-USER. The vaultwarden filter is included in fail2ban 1.1.1 and later.

/etc/fail2ban/jail.d/vaultwarden.local
[vaultwarden]
enabled   = true
backend   = auto
logpath   = /opt/vaultwarden/vw-data/vaultwarden.log
banaction = iptables-allports
chain     = DOCKER-USER
maxretry  = 5
findtime  = 1h
bantime   = 4h
bash
sudo fail2ban-client reload
sudo fail2ban-client status vaultwarden

Step 1: make Vaultwarden write a log file#

Vaultwarden logs to stdout by default. Set LOG_FILE to a path inside the /data volume so the file appears on the host:

/opt/vaultwarden/compose.yaml
services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      DOMAIN: "https://vault.example.com"
      LOG_FILE: "/data/vaultwarden.log"
      TZ: "Europe/Berlin"
    volumes:
      - ./vw-data:/data
    ports:
      - "127.0.0.1:8080:80"
bash
cd /opt/vaultwarden && docker compose up -d
ls -l /opt/vaultwarden/vw-data/vaultwarden.log

Keep EXTENDED_LOGGING at its default (true): it adds the timestamp and module name the filter relies on. Keep LOG_LEVEL at info or more verbose (failed logins are logged as errors, so warn and error also work). Set TZ to the host's timezone: Vaultwarden writes local times without an offset, and fail2ban interprets them in the host's timezone. If the two differ, fresh failures look hours old and are ignored. Alternatively set logtimezone in the jail.

Rotate the file with a logrotate rule on the host (copytruncate avoids restarting the container).

Step 2: the vaultwarden filter#

fail2ban 1.1.1 includes filter.d/vaultwarden.conf and a disabled [vaultwarden] jail (port = http,https, logpath = /var/log/vaultwarden.log). The filter matches three events:

log
[2026-10-25 18:36:10.123][vaultwarden::api::identity][ERROR] Username or password is incorrect. Try again. IP: 203.0.113.42. Username: alice@example.com.
[2026-10-25 18:36:11.456][vaultwarden::api::admin][ERROR] Invalid admin token. IP: 203.0.113.42
[2026-10-25 18:36:12.789][vaultwarden::api::core::two_factor::authenticator][ERROR] Invalid TOTP code! Server time: 2026-10-25 18:36:12 UTC IP: 203.0.113.42

All three matched in our fail2ban-regex test. IPv6 addresses work as well.

Older fail2ban without the filter#

The filter is new in 1.1.1. Packages based on 1.1.0 or older (Debian 13, Ubuntu 24.04 and 26.04, EPEL) don't have it — ls /etc/fail2ban/filter.d/vaultwarden.conf tells you. Create it with the upstream definition:

/etc/fail2ban/filter.d/vaultwarden.conf
[Definition]
failregex = ^\s*(?:\[\]\s*)?\[vaultwarden::api::(?:identity|admin|core::two_factor::authenticator)?\]\[ERROR\] (?:Invalid admin token|Invalid TOTP code|Username or password is incorrect)[\.!](?:\s+(?!IP:)\S+)* IP: <ADDR>(?:\. Username: <F-USER>\S+</F-USER>)?
ignoreregex =

If an old fail2ban rejects the filter with No 'host' group, replace <ADDR> with <HOST>.

Step 3: ban where Docker traffic goes#

Vaultwarden's published port is DNATed to the container, so packets cross the FORWARD chain, not INPUT. A default fail2ban ban is recorded but doesn't block the container. chain = DOCKER-USER with iptables-allports fixes it; allports avoids the trap that, in DOCKER-USER, ports are already translated to the container port (80, not 8080). Make fail2ban start after Docker so the chain exists (sudo systemctl edit fail2ban → [Unit] After=docker.service). Details and the nftables variant: fail2ban with Docker containers.

Behind a reverse proxy (the usual setup, since Vaultwarden needs HTTPS):

  • Vaultwarden reads the client address from the X-Real-IP header by default (IP_HEADER). Make sure your proxy sets it, e.g. proxy_set_header X-Real-IP $remote_addr; in nginx; otherwise the log shows the proxy's IP. Recent versions also have IP_HEADER_TRUSTED_PROXIES to restrict which addresses may set the header.
  • The ban must hit the proxy's traffic. If the proxy runs in Docker on the same host (Nginx Proxy Manager, Traefik, Caddy), DOCKER-USER + allports covers it. If Vaultwarden is bound to 127.0.0.1 as above and the proxy runs on the host, use the default action instead — the proxy's ports are on INPUT.
  • Behind Cloudflare, ban at Cloudflare's edge: see the Cloudflare guide.

A stricter jail for the admin page#

The /admin page is protected by ADMIN_TOKEN; nobody legitimate should get it wrong more than once or twice. A second jail with its own small filter bans admin-token guessing faster and longer:

/etc/fail2ban/filter.d/vaultwarden-admin.conf
[Definition]
failregex = ^\s*(?:\[\]\s*)?\[vaultwarden::api::admin\]\[ERROR\] Invalid admin token[\.!] IP: <ADDR>
/etc/fail2ban/jail.d/vaultwarden.local
[vaultwarden-admin]
enabled   = true
backend   = auto
filter    = vaultwarden-admin
logpath   = /opt/vaultwarden/vw-data/vaultwarden.log
banaction = iptables-allports
chain     = DOCKER-USER
maxretry  = 3
findtime  = 1d
bantime   = 1w

It matches both Invalid admin token. IP: … and Invalid admin token! IP: … and ignores user logins (tested). Better still, disable the admin page when you don't need it by unsetting ADMIN_TOKEN.

Test it#

bash
sudo fail2ban-regex /opt/vaultwarden/vw-data/vaultwarden.log vaultwarden
sudo fail2ban-client status vaultwarden
sudo iptables -L DOCKER-USER -n --line-numbers

Log in with a wrong master password a few times from a phone on mobile data, then check sudo tail /var/log/fail2ban.log for [vaultwarden] Ban. If fail2ban-regex matches but the jail never bans, compare the log's timestamps with date on the host — the TZ mismatch above is the usual cause. Lift a test ban with sudo fail2ban-client set vaultwarden unbanip <IP>, and consider adding your home network to ignoreip; a family member's phone with an old password can otherwise lock everyone at home out.

Official Bitwarden server#

For the official Bitwarden self-hosted server (not Vaultwarden), fail2ban ships a separate bitwarden filter and jail, which reads Failed login attempt lines from /home/*/bwdata/logs/identity/Identity/log.txt:

/etc/fail2ban/jail.d/bitwarden.local
[bitwarden]
enabled   = true
backend   = auto
banaction = iptables-allports
chain     = DOCKER-USER

vaultwarden and bitwarden filter reference#

The built-in filters this guide uses, exactly as fail2ban 1.1.1 ships them — with the log lines they match and a link to test them. All other built-in filters are in the filter library.

vaultwarden Vaultwarden

The vaultwarden filter bans failed logins and wrong 2FA codes on Vaultwarden (self-hosted Bitwarden). File: /etc/fail2ban/filter.d/vaultwarden.conf; jail [vaultwarden]; log on Debian/Ubuntu: /var/log/vaultwarden.log.

Log lines it bans (from fail2ban's test suite):

  1. [2024-08-31 02:11:22.129][vaultwarden::api::identity][ERROR] Username or password is incorrect. Try again. IP: 2001:db8::b6d3:95d7:1425:766d. Username: test@example.com.
  2. [2024-08-31 02:11:28.562][vaultwarden::api::identity][ERROR] Username or password is incorrect. Try again. IP: 80.187.85.94. Username: test@example.com.
  3. [2024-08-31 02:11:28.725][vaultwarden::api::admin][ERROR] Invalid admin token. IP: 80.187.85.94
Show the failregex fail2ban compiles (1 line)
failregex (1)
^\s*(?:\[\]\s*)?\[vaultwarden::api::(?:identity|admin|core::two_factor::authenticator)?\]\[ERROR\] (?:Invalid admin token|Invalid TOTP code|Username or password is incorrect)[\.!](?:\s+(?!IP:)\S+)* IP: <ADDR>(?:\. Username: <F-USER>\S+</F-USER>)?

Test vaultwarden in the browser or on the server: sudo fail2ban-regex /var/log/vaultwarden.log vaultwarden · source

bitwarden Bitwarden (self-hosted)

The bitwarden filter bans repeated failed logins to a self-hosted Bitwarden identity server. File: /etc/fail2ban/filter.d/bitwarden.conf; jail [bitwarden]; log on Debian/Ubuntu: /home/*/bwdata/logs/identity/Identity/log.txt.

Log lines it bans (from fail2ban's test suite):

  1. 2019-11-26 01:04:49.008 +08:00 [WRN] Failed login attempt. 192.168.0.16
  2. 2019-11-25 21:39:58.464 +01:00 [WRN] Failed login attempt, 2FA invalid. 192.168.0.21
  3. 2019-11-25 21:39:58.464 +01:00 [Warning] Failed login attempt, 2FA invalid. 192.168.0.21
Show the failregex fail2ban compiles (1 line)
failregex (1)
^(?:\[\])?\s*(?:<[^.]+\.[^.]+>\s+)?(?:\S+\s+)?(?:kernel:\s?\[ *\d+\.\d+\]:?\s+)?(?:@vserver_\S+\s+)?(?:(?:(?:\[\d+\])?:\s+[\[\(]?Bitwarden-Identity(?:\(\S+\))?[\]\)]?:?|[\[\(]?Bitwarden-Identity(?:\(\S+\))?[\]\)]?:?(?:\[\d+\])?:?)\s+)?(?:\[ID \d+ \S+\]\s+)?\s*\[(?:W(?:RN|arning)|Bit\.Core\.[^\]]+)\]\s+Failed login attempt(?:, 2FA invalid)?\. <ADDR>$

Test bitwarden in the browser or on the server: sudo fail2ban-regex /home/*/bwdata/logs/identity/Identity/log.txt bitwarden · source

FAQ#

Does fail2ban have a Vaultwarden filter?#

Yes, since fail2ban 1.1.1: filter.d/vaultwarden.conf with a matching [vaultwarden] jail. It catches wrong passwords, wrong TOTP codes and wrong admin tokens. Older packages lack it; create the file yourself with the upstream failregex shown on this page.

How do I make Vaultwarden log failed logins to a file?#

Set the environment variable LOG_FILE=/data/vaultwarden.log on the container and recreate it. The file then appears in the host directory mounted at /data. Keep EXTENDED_LOGGING enabled (the default) and the log level at info, and set TZ to the host's timezone.

Why does fail2ban ban the IP but Vaultwarden is still reachable?#

Because Docker forwards the published port to the container through the FORWARD chain, and the default ban sits in INPUT. Set banaction = iptables-allports and chain = DOCKER-USER in the jail, reload fail2ban, and check iptables -L DOCKER-USER -n for the f2b-vaultwarden jump.

Why does the Vaultwarden log show my proxy's IP?#

Vaultwarden takes the client address from the header named in IP_HEADER, by default X-Real-IP. If your reverse proxy doesn't set that header, Vaultwarden logs the proxy. Add proxy_set_header X-Real-IP $remote_addr; (nginx) or the equivalent in your proxy.

Does the vaultwarden filter also protect the admin page?#

Yes. The stock filter matches Invalid admin token, so admin-page guesses count towards the same jail. For faster, longer bans on the admin page, add a second jail with a filter that matches only [vaultwarden::api::admin] lines and a low maxretry.

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