Библиотека, доступная из интернета, будет получать попытки подбора пароля — это не вопрос «если», а вопрос «когда». Flibusta-at-home защищается на двух уровнях: сама блокирует аккаунт и адрес после серии неудач, и пишет лог, по которому fail2ban может забанить источник на уровне файрвола. Эта статья — про второй уровень.
Что делает сама библиотека
Каждая попытка входа (через форму, через HTTP Basic для OPDS и прямых ссылок
на файлы) записывается в таблицу login_attempts в Postgres. Если за
последние 15 минут накопилось больше 10 неудач для имени пользователя или
больше 5 неудач с одного IP, дальнейшие попытки отвергаются с задержкой в
2 секунды, даже если пароль верный. Через 15 минут блокировка снимается сама.
Эти же записи видны администратору в разделе «Пользователи → Активность
пользователей».
Этого достаточно против неторопливого подбора, но не против потока запросов: каждая отвергнутая попытка всё равно стоит соединения, обращения к БД и двух секунд процесса php-fpm. fail2ban отсекает источник раньше — на файрволе.
Лог попыток входа
Все неудачные попытки дополнительно пишутся в текстовый файл внутри
контейнера /cache/login_attempts/flibusta_login_attempts.log, то есть на
хосте — <директория cache>/login_attempts/flibusta_login_attempts.log.
Формат строки:
[2026-09-17 03:14:15]: user=admin from=203.0.113.7 failure code=1
Коды ошибок:
| Код | Значение |
|---|---|
| 1 | неверный пароль (форма входа) |
| 2 | сессия отвергнута из-за смены User-Agent |
| 3 | аккаунт заблокирован по числу неудач |
| 4 | адрес заблокирован по числу неудач |
| 5 | попытка открыть административный раздел без прав администратора |
| 6 | неверный пароль по HTTP Basic (OPDS, прямые ссылки на файлы) |
Время пишется в часовом поясе контейнера (TZ). Он должен совпадать с
часовым поясом хоста, иначе fail2ban сочтёт свежие записи старыми (или
из будущего) и не будет банить.
Важно также, откуда берётся адрес в from=. Это REMOTE_ADDR, который
nginx передаёт через fastcgi_param. Если перед nginx стоит ещё один прокси
или туннель (Cloudflare, VPS с nginx), в лог попадёт адрес этого прокси, и
fail2ban забанит его — то есть заблокирует вообще всех. В таких схемах либо
настройте real_ip_header в nginx, либо ставьте fail2ban на той машине, где
терминируется соединение с клиентом.
Установка fail2ban на хосте
sudo apt install fail2ban # Debian/Ubuntu
sudo dnf install fail2ban # Fedora/RHEL
Проверьте, чем по умолчанию банит ваш дистрибутив (iptables или nftables):
fail2ban-client get <jail> actions после запуска или переменная banaction
в /etc/fail2ban/jail.conf.
Фильтр
Файл /etc/fail2ban/filter.d/flibusta.conf (в репозитории —
docs_and_configs/external_services_config/fail2ban.flibusta.filter.conf):
[Definition]
failregex = ^: user=\S* from=<HOST> failure code=\S+$
datepattern = \[%%Y-%%m-%%d %%H:%%M:%%S\]
ignoreregex =
datepattern описывает отметку времени в квадратных скобках; fail2ban
вырезает её, и failregex применяется к остатку строки, который начинается с
: user=. Проверить фильтр на реальном логе:
fail2ban-regex /opt/flibusta/cache/login_attempts/flibusta_login_attempts.log \
/etc/fail2ban/filter.d/flibusta.conf
В выводе должно быть Lines: N lines, 0 ignored, N matched.
Jail
Файл /etc/fail2ban/jail.d/flibusta.local (шаблон в репозитории —
fail2ban.jail.local.addition.conf, здесь он дополнен):
[flibusta]
enabled = true
filter = flibusta
logpath = /var/log/flibusta/flibusta_login_attempts.log
maxretry = 5
findtime = 10m
bantime = 1h
# библиотека работает в Docker: см. раздел ниже
banaction = iptables-allports
chain = DOCKER-USER
Шаблон ожидает лог в /var/log/flibusta/. Либо укажите в logpath реальный
путь внутри директории cache, либо сделайте символическую ссылку, чтобы
не привязывать конфиг fail2ban к расположению библиотеки:
sudo ln -s /opt/flibusta/cache/login_attempts /var/log/flibusta
maxretry = 5 за findtime = 10m согласуется с внутренней блокировкой
библиотеки (5 неудач с адреса за 15 минут): fail2ban забанит примерно в тот же
момент, когда библиотека начала бы отвергать попытки сама. Можно поставить
bantime = 1d или включить bantime.increment = true для рецидивистов.
Особенность: трафик к Docker-контейнерам
Это самый частый источник «fail2ban показывает бан, а запросы всё равно
проходят». Стандартные действия fail2ban добавляют правила в цепочку INPUT,
но трафик к опубликованным портам контейнеров проходит через FORWARD и
цепочки Docker, минуя INPUT. Docker специально оставляет для
пользовательских правил цепочку DOCKER-USER, которая проверяется до
всех остальных, — именно её и нужно указать в chain.
Если хост использует nftables и banaction = nftables-multiport, эквивалент —
задать chain = DOCKER-USER там же; в свежих версиях fail2ban действие
nftables понимает параметр chain. Проверить, что правило действительно
попало куда надо:
sudo iptables -L DOCKER-USER -n | head # или: nft list chain ip filter DOCKER-USER
Отдельный случай — reverse proxy не в Docker, а на хосте (nginx из
пакетов). Тогда трафик действительно идёт через INPUT, и строку chain
нужно убрать.
Запуск и проверка
sudo systemctl enable --now fail2ban
sudo fail2ban-client status flibusta
Ожидаемый вывод: Currently failed, Total failed, Banned IP list. Для
проверки «в бою» с другого адреса (например, с телефона через мобильную сеть)
введите неверный пароль шесть раз — соединение должно перестать
устанавливаться. Снять бан с себя:
sudo fail2ban-client set flibusta unbanip 203.0.113.7
Свой домашний диапазон и адрес, с которого вы администрируете, имеет смысл
внести в ignoreip в секции [DEFAULT] jail-конфига, чтобы опечатка в
пароле не выкидывала вас из собственной библиотеки.
Ротация лога
Лог только растёт: библиотека в него дописывает, но никогда не чистит. Файл
/etc/logrotate.d/flibusta (шаблон в репозитории — logrotate.conf):
/opt/flibusta/cache/login_attempts/flibusta_login_attempts.log {
size 1M
missingok
rotate 3
compress
copytruncate
notifempty
}
Два момента, которые здесь важны:
copytruncateобязателен. PHP открывает файл на дозапись при каждой записи и не держит дескриптор, но и никакого сигнала «переоткрой лог» ему послать нельзя.copytruncateкопирует содержимое и обнуляет файл на месте, не меняя inode; fail2ban это переживает штатно.- Файл принадлежит uid 82.
logrotateработает от root и с этим справится, но если вы включитеcreate, укажите владельца:create 0644 82 82.
Ротация по размеру (size 1M) вместо ежедневной выбрана потому, что лог
растёт только при атаках; в спокойные недели там будет пусто.
Если библиотека за Tailscale или VPN
В таких схемах внешние адреса до библиотеки не доходят, а банить адреса из
100.64.0.0/10 (это ваши же устройства) — не то, чего хочется. fail2ban там
не нужен; внутренней 15-минутной блокировки достаточно, а лог полезен разве
что для того, чтобы увидеть, кто из домашних забыл пароль.