Guides
fail2ban on OpenWrt
OpenWrt packages fail2ban, but a router isn't a server: logs live in a RAM ring buffer, the firewall is nftables, and Python costs flash. This guide gets fail2ban working for dropbear and LuCI, and covers banIP, the lighter native option.
Yes, fail2ban runs on OpenWrt: install the fail2ban package with opkg install fail2ban (or apk add fail2ban on releases that use apk). It pulls in Python 3, so it fits x86 and larger ARM routers better than small flash devices. Three changes make it work. Write the system log to a file, because fail2ban can't read OpenWrt's in-memory log. Set banaction = nftables, because the package's defaults expect iptables. Then enable the stock dropbear jail, plus a small custom filter for LuCI. On small routers, banIP's built-in log monitor does the same job without Python.
First: do you need it at all?#
OpenWrt's default firewall rejects connections from the WAN to the router itself, so dropbear (SSH) and LuCI are only reachable from your LAN. If you haven't opened them to the internet, nobody outside can brute-force them, and fail2ban has nothing to do. The better way to manage a router remotely is a VPN such as WireGuard, with SSH and LuCI left closed on WAN. fail2ban is worth it when you have opened port 22 or LuCI to the internet, or don't trust every device on your LAN.
Install the fail2ban package#
opkg update
opkg install fail2ban
# OpenWrt releases that use apk:
# apk update && apk add fail2banThe package depends on python3-light, a few Python modules and nftables. That is several megabytes of flash for Python alone, so check df -h /overlay first. A router with 16 MB of flash or less is better served by banIP. The OpenWrt package was version 1.1.0 at the time of writing (upstream is 1.1.1). The package also has OpenWrt-specific behaviour:
- A procd init script,
/etc/init.d/fail2ban. It is enabled at install and restarts the daemon when files in/etc/fail2banchange. - A firewall include that restarts fail2ban whenever the firewall reloads, so its rules come back.
- A ban database in
/var/lib/fail2ban/. On OpenWrt/varis in RAM, which spares the flash, but bans don't survive a reboot.
Step 1: write the log to a file#
OpenWrt's logd keeps the system log in a ring buffer that you read with logread. There is no /var/log/auth.log, and fail2ban can only follow files or the systemd journal. Ask logd to also write a file:
uci set system.@system[0].log_file='/var/log/messages'
uci commit system
/etc/init.d/log restartThe file is in RAM as well, so it costs no flash writes. Its lines keep the logread format, which the stock dropbear filter reads as is:
Sun Oct 25 18:36:12 2026 authpriv.warn dropbear[2101]: Bad password attempt for 'root' from 203.0.113.42:51234
Sun Oct 25 18:36:14 2026 authpriv.warn dropbear[2101]: Login attempt for nonexistent user from 198.51.100.7:40122
Sun Oct 25 18:40:05 2026 daemon.err uhttpd[1873]: luci: failed login on /admin for admin from 203.0.113.42Step 2: the LuCI filter#
fail2ban has no LuCI filter. LuCI logs luci: failed login on <path> for <user> from <IP> through syslog. Depending on the release, the tag is user.info : or daemon.err uhttpd[…]:. This filter accepts both:
[Definition]
failregex = ^\s*\w+\.\w+(?: [\w.-]+(?:\[\d+\])?)?\s*: luci: failed login on \S* for <F-USER>\S+</F-USER> from <HOST>\s*$
ignoreregex =Tested with fail2ban-regex from fail2ban 1.1.1, it matched failed logins in both formats, IPv4 and IPv6, and ignored luci: accepted login lines.
Step 3: the jails#
[DEFAULT]
# OpenWrt 22.03+ uses nftables (firewall4); the package's jail.conf expects iptables
banaction = nftables
banaction_allports = nftables[type=allports]
backend = auto
# never ban yourself: LAN and loopback
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24
bantime = 1h
findtime = 10m
maxretry = 5
[dropbear]
enabled = true
port = 22
logpath = /var/log/messages
[luci]
enabled = true
filter = luci
port = 80,443
logpath = /var/log/messagesChange 192.168.1.0/24 to your LAN if you moved it. Ports are given as numbers because a minimal OpenWrt image may not resolve service names; change 22 if dropbear listens elsewhere. The nftables action creates its own f2b-table next to firewall4's fw4 table, so you don't need to edit the router's firewall config. Restart and check:
/etc/init.d/fail2ban restart
fail2ban-client status dropbear
fail2ban-client status luci
nft list table inet f2b-tableThe stock dropbear filter and its test lines are in the SSH filter reference. If you replaced dropbear with OpenSSH, use the ordinary sshd jail with logpath = /var/log/messages.
banIP: the lightweight alternative#
banIP is OpenWrt's own blocking package, written in shell with nftables sets, so it needs no Python. Besides blocklist feeds, it has a background log monitor that reads logread and bans addresses that cause failed logins, which is the same job fail2ban does here. Its default search terms are Exit before auth from (dropbear) and luci: failed login. More patterns for sshd, Asterisk and others are listed in its README.
opkg install banip luci-app-banip
uci set banip.global.ban_enabled='1'
uci commit banip
/etc/init.d/banip start
/etc/init.d/banip statusRead the defaults before you rely on it. banIP is disabled after install, and ban_logcount defaults to 1, so a single matching line is enough for a ban. Raise it to 3–5 to forgive typos. Auto-added addresses go to /etc/banip/banip.blocklist, and ban_nftexpiry sets when they expire. All options can be set in LuCI under Services → banIP → Log Settings.
| Feature | fail2ban | banIP log monitor |
|---|---|---|
| Runtime | Python 3 daemon | Shell + nftables, no Python |
| Flash needed | Several MB | Small |
| Matching | Full regex filters with counting windows (maxretry/findtime) |
Search terms, count per blocking cycle (ban_logcount) |
| Repeat offenders | recidive jail, incremental bans | Blocklist entries with expiry, optional subnet blocking |
| Also blocks by feeds and countries | No | Yes |
| Best for | x86 or larger routers, custom services | Most OpenWrt devices |
Older scripts such as bearDropper also parse dropbear logs, but check that the version you find supports your OpenWrt release before using it. banIP is maintained in the official package feed.
FAQ#
Is fail2ban available for OpenWrt?#
Yes. fail2ban is in the official OpenWrt package feed: opkg install fail2ban, or apk add fail2ban on releases that use apk. It brings Python 3 and nftables with it. Before it can ban anything, set log_file in the system config so there is a log to read, and set banaction = nftables.
Does fail2ban work with dropbear?#
Yes. fail2ban ships a dropbear filter that matches Bad password attempt for '<user>' from <IP>, Login attempt for nonexistent user from <IP> and the Max auth tries reached exit. On OpenWrt, enable the [dropbear] jail with logpath = /var/log/messages after configuring logd to write that file.
Should I use banIP or fail2ban on OpenWrt?#
On most routers, banIP: it is native, needs no Python, and its log monitor already covers dropbear and LuCI. Choose fail2ban when you have flash and RAM to spare and want regex filters, maxretry/findtime counting, the recidive jail or filters for other services running on the router.
How do I see failed LuCI logins on OpenWrt?#
Run logread -e luci to show LuCI's log lines, or logread -f to watch them live. Failed attempts appear as luci: failed login on / for root from <IP>, and successful ones as accepted login.
Spotted something wrong or out of date? Tell us — every page is checked against the current fail2ban release.