Как использовать /opt/fail2ban для конфигов
Самый универсальный способ — создать символические ссылки из /etc/fail2ban/jail.d и /etc/fail2ban/filter.d на соответствующие файлы и папки в /opt/fail2ban:
sudo ln -s /opt/fail2ban/config/jail.d/* /etc/fail2ban/jail.d/ sudo ln -s /opt/fail2ban/config/filter.d/* /etc/fail2ban/filter.d/
Настройка Fail2ban с Nginx Proxy Manager
Описание
Fail2ban - это система защиты от брутфорс атак и автоматизированных сканирований. В связке с Nginx Proxy Manager (nginxpm) обеспечивает автоматическую блокировку подозрительных IP-адресов на основе анализа логов доступа.
Требования
- Docker и docker-compose
- Nginx Proxy Manager (nginxpm)
- Ubuntu/Debian система
- Права root или sudo
Установка Fail2ban
# Обновление пакетов sudo apt update # Установка fail2ban sudo apt install fail2ban -y # Включение автозапуска sudo systemctl enable fail2ban sudo systemctl start fail2ban
Структура конфигурации
/opt/fail2ban/
├── scripts/
│ ├── manage-fail2ban.sh # Скрипт управления и мониторинга
│ └── parse-nginx-logs.sh # Парсер логов nginxpm
├── logs/
│ ├── nginx-access.log # Обработанные логи для fail2ban (последний месяц)
│ └── parser.log # Логи работы парсера
└── config/
├── jail.local # Конфигурация jail'ов
└── filters/ # Кастомные фильтры
Docker Compose конфигурация Nginx Proxy Manager
services: app: image: 'jc21/nginx-proxy-manager:latest' container_name: nginxpm restart: always ports: - '80:80' # HTTP - '443:443' # HTTPS environment: DB_MYSQL_HOST: "db" DB_MYSQL_PORT: 3306 DB_MYSQL_USER: "npm" DB_MYSQL_PASSWORD: "YOUR_PASSWORD" DB_MYSQL_NAME: "npm" volumes: - ./data:/data # данные NPM - ./letsencrypt:/etc/letsencrypt # сертификаты depends_on: - db networks: - webproxy db: image: 'mariadb:10.11' container_name: nginxpm_db restart: always environment: MYSQL_ROOT_PASSWORD: "YOUR_ROOT_PASSWORD" MYSQL_DATABASE: "npm" MYSQL_USER: "npm" DB_MYSQL_PASSWORD: "YOUR_PASSWORD" volumes: - ./mysql:/var/lib/mysql # база MariaDB networks: - webproxy networks: webproxy: external: true
Создание директорий
# Создание структуры папок sudo mkdir -p /opt/fail2ban/{scripts,logs,config,config/filters}
Скрипт парсинга логов parse-nginx-logs.sh
Создать файл /opt/fail2ban/scripts/parse-nginx-logs.sh:
#!/bin/bash # Скрипт парсинга логов nginx-proxy-manager для fail2ban # Обрабатывает все access логи из папки nginxpm (данные за последний месяц) LOG_DIR="/opt/nginxpm/data/logs" LOG_FILE="/opt/fail2ban/logs/nginx-access.log" CONTAINER_NAME="nginxpm" PARSER_LOG="/opt/fail2ban/logs/parser.log" # Создаем директории если они не существуют mkdir -p "$(dirname "$LOG_FILE")" "$(dirname "$PARSER_LOG")" # Проверяем, что контейнер запущен if ! docker ps --format "table {{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then echo "$(date): Контейнер $CONTAINER_NAME не найден или не запущен" >> "$PARSER_LOG" exit 1 fi # Проверяем существование директории с логами if [ ! -d "$LOG_DIR" ]; then echo "$(date): Директория логов $LOG_DIR не найдена" >> "$PARSER_LOG" exit 1 fi # Очищаем целевой файл (файл пересоздается полностью при каждом запуске) > "$LOG_FILE" # Счетчик обработанных файлов processed_files=0 # Дата отсечки (30 дней назад) для фильтрации логов CUTOFF_DATE=$(date -d '30 days ago' '+%d/%b/%Y') CUTOFF_TIMESTAMP=$(date -d '30 days ago' '+%s') # Обрабатываем все файлы с окончанием *_access.log for logfile in "$LOG_DIR"/*_access.log; do # Проверяем, что файл существует (на случай если паттерн не найден) if [ -f "$logfile" ]; then # Читаем файл и фильтруем записи не старше 30 дней awk -v cutoff_ts="$CUTOFF_TIMESTAMP" ' { # Извлекаем дату из строки лога (формат: DD/MMM/YYYY) match($0, /\[([0-9]{2}\/[A-Za-z]{3}\/[0-9]{4})/, date_match) if (date_match[1]) { # Преобразуем дату в timestamp для сравнения cmd = "date -d \"" date_match[1] "\" +%s 2>/dev/null" cmd | getline log_timestamp close(cmd) # Если дата лога >= даты отсечки, выводим строку if (log_timestamp >= cutoff_ts) { print $0 } } else { # Если не удалось извлечь дату, оставляем запись print $0 } }' "$logfile" >> "$LOG_FILE" processed_files=$((processed_files + 1)) echo "$(date): Обработан файл: $(basename "$logfile") - отфильтрованы записи за последний месяц" >> "$PARSER_LOG" fi done # Если не найдено ни одного файла логов if [ $processed_files -eq 0 ]; then echo "$(date): Не найдено файлов логов *_access.log в директории $LOG_DIR" >> "$PARSER_LOG" # Создаем пустой файл чтобы fail2ban не ругался touch "$LOG_FILE" fi # Устанавливаем права доступа chmod 644 "$LOG_FILE" # Логируем статистику каждый час (когда минуты = 00) MINUTE=$(date +%M) if [ "$MINUTE" = "00" ]; then final_lines=$(wc -l < "$LOG_FILE" 2>/dev/null || echo "0") echo "$(date): Итого обработано $processed_files файлов, $final_lines записей (за последний месяц) из контейнера $CONTAINER_NAME" >> "$PARSER_LOG" fi # Ротация логов parser.log (если больше 5MB) if [ -f "$PARSER_LOG" ] && [ $(stat -c%s "$PARSER_LOG" 2>/dev/null || echo 0) -gt 5242880 ]; then mv "$PARSER_LOG" "$PARSER_LOG.old" touch "$PARSER_LOG" chmod 644 "$PARSER_LOG" fi # Удаление записей старше 30 дней из лог-файла (дополнительная очистка) if [ -f "$LOG_FILE" ] && [ -s "$LOG_FILE" ]; then TEMP_FILTERED="/tmp/nginx-final-filtered" # Финальная фильтрация собранного файла awk -v cutoff_ts="$CUTOFF_TIMESTAMP" ' { # Извлекаем дату из строки лога (формат: DD/MMM/YYYY) match($0, /\[([0-9]{2}\/[A-Za-z]{3}\/[0-9]{4})/, date_match) if (date_match[1]) { # Преобразуем дату в timestamp для сравнения cmd = "date -d \"" date_match[1] "\" +%s 2>/dev/null" cmd | getline log_timestamp close(cmd) # Если дата лога >= даты отсечки, выводим строку if (log_timestamp >= cutoff_ts) { print $0 } } else { # Если не удалось извлечь дату, оставляем запись print $0 } }' "$LOG_FILE" > "$TEMP_FILTERED" # Заменяем оригинальный файл отфильтрованным if [ -f "$TEMP_FILTERED" ]; then mv "$TEMP_FILTERED" "$LOG_FILE" chmod 644 "$LOG_FILE" fi fi # Ротация основного лог-файла (если больше 10MB) if [ -f "$LOG_FILE" ] && [ $(stat -c%s "$LOG_FILE" 2>/dev/null || echo 0) -gt 10485760 ]; then mv "$LOG_FILE" "$LOG_FILE.old" touch "$LOG_FILE" chmod 644 "$LOG_FILE" echo "$(date): Выполнена ротация лог-файла $LOG_FILE" >> "$PARSER_LOG" fi exit 0
Настройка прав доступа и cron
# Сделать скрипт исполняемым sudo chmod +x /opt/fail2ban/scripts/parse-nginx-logs.sh # Создать задачу cron для автоматического запуска каждые 5 минут sudo crontab -e # Добавить строку: */5 * * * * /opt/fail2ban/scripts/parse-nginx-logs.sh >/dev/null 2>&1
Скрипт управления manage-fail2ban.sh
Создать файл /opt/fail2ban/scripts/manage-fail2ban.sh:
#!/bin/bash # Скрипт управления и мониторинга fail2ban case "$1" in status) echo "=== Статус fail2ban ===" fail2ban-client status echo "" echo "=== Детальная информация ===" for jail in $(fail2ban-client status | grep "Jail list:" | cut -d: -f2 | tr ',' '\n' | xargs); do echo "--- $jail ---" fail2ban-client status "$jail" done ;; reload) echo "Перезагрузка fail2ban..." systemctl reload fail2ban ;; restart) echo "Перезапуск fail2ban..." systemctl restart fail2ban ;; unban) if [ -z "$2" ]; then echo "Использование: $0 unban <IP>" exit 1 fi echo "Разблокировка IP: $2" fail2ban-client unban "$2" ;; *) echo "Использование: $0 {status|reload|restart|unban <IP>}" exit 1 ;; esac
# Сделать скрипт исполняемым sudo chmod +x /opt/fail2ban/scripts/manage-fail2ban.sh
Команды управления
Управление службой fail2ban
# Проверка статуса службы sudo systemctl status fail2ban # Запуск службы sudo systemctl start fail2ban # Остановка службы sudo systemctl stop fail2ban # Перезапуск службы sudo systemctl restart fail2ban # Включить автозапуск sudo systemctl enable fail2ban
Использование скрипта управления
# Показать статус всех jail'ов sudo /opt/fail2ban/scripts/manage-fail2ban.sh status # Перезагрузить конфигурацию sudo /opt/fail2ban/scripts/manage-fail2ban.sh reload # Перезапустить службу sudo /opt/fail2ban/scripts/manage-fail2ban.sh restart # Разблокировать IP sudo /opt/fail2ban/scripts/manage-fail2ban.sh unban 192.168.1.100
Просмотр логов
Логи fail2ban
# Основные логи fail2ban sudo tail -f /var/log/fail2ban.log # Логи через systemd sudo journalctl -u fail2ban -f # Просмотр последних 100 записей sudo journalctl -u fail2ban -n 100 # Поиск конкретных событий sudo grep "Ban " /var/log/fail2ban.log | tail -20 sudo grep "Unban " /var/log/fail2ban.log | tail -20
Логи парсера nginx
# Лог работы парсера sudo tail -f /opt/fail2ban/logs/parser.log # Просмотр обработанных логов nginx sudo tail -f /opt/fail2ban/logs/nginx-access.log # Статистика размера логов sudo ls -lah /opt/fail2ban/logs/ # Проверка последних записей в исходных логах nginxpm sudo ls -la /opt/nginxpm/data/logs/ sudo tail -10 /opt/nginxpm/data/logs/*_access.log # Подсчет строк в логах sudo wc -l /opt/fail2ban/logs/nginx-access.log sudo wc -l /opt/nginxpm/data/logs/*_access.log
Диагностика и тестирование
Проверка работы парсера
# Запуск парсера вручную sudo /opt/fail2ban/scripts/parse-nginx-logs.sh # Проверка размера результирующего лога sudo wc -l /opt/fail2ban/logs/nginx-access.log # Проверка даты самых старых записей (должно быть не более месяца) sudo head -5 /opt/fail2ban/logs/nginx-access.log sudo tail -5 /opt/fail2ban/logs/nginx-access.log # Проверка что контейнер nginxpm запущен sudo docker ps | grep nginxpm
Тестирование фильтров
# Тест фильтра на соответствие логу sudo fail2ban-regex /opt/fail2ban/logs/nginx-access.log /etc/fail2ban/filter.d/nginx-scan-block.conf # Проверка конкретного jail sudo fail2ban-client status nginx-scan-block # Тест с выводом совпадений sudo fail2ban-regex --print-all-matched /opt/fail2ban/logs/nginx-access.log /etc/fail2ban/filter.d/nginx-404-flood.conf
Мониторинг системы
Ежедневные проверки
# Проверка статуса системы sudo /opt/fail2ban/scripts/manage-fail2ban.sh status # Размер логов sudo du -sh /opt/fail2ban/logs/ sudo du -sh /opt/nginxpm/data/logs/ # Количество заблокированных IP sudo fail2ban-client status | grep "Currently banned" # Статистика по jail'ам sudo fail2ban-client status sshd sudo fail2ban-client status nginx-404-flood sudo fail2ban-client status nginx-scan-block sudo fail2ban-client status nginx-dos-block
Просмотр iptables правил
# Просмотр всех правил fail2ban sudo iptables -L -n | grep f2b # Просмотр правил конкретного jail sudo iptables -L f2b-nginx-scan-block -n # Подсчет заблокированных IP sudo iptables -L f2b-nginx-scan-block -n | grep -c "REJECT"
Особенности работы
- Полное пересоздание файла: Файл
nginx-access.logполностью пересоздается каждые 5 минут - Фильтрация по времени: Скрипт автоматически отбирает только записи за последний месяц (30 дней)
- Ротация логов: Автоматическая ротация при превышении размера в 10MB
- Множественные хосты: Обработка всех файлов
*_access.logв папке nginxpm - Регулярное обновление: Запуск каждые 5 минут через cron
- Логирование процесса: Подробные логи работы парсера в
/opt/fail2ban/logs/parser.log
Устранение неполадок
Типичные проблемы
# Проверка что директория nginxpm существует sudo ls -la /opt/nginxpm/data/logs/ # Проверка прав доступа sudo ls -la /opt/fail2ban/logs/ # Проверка cron задач sudo crontab -l # Проверка запуска Docker контейнера sudo docker logs nginxpm | tail -20 # Проверка работы fail2ban sudo systemctl is-active fail2ban sudo systemctl is-enabled fail2ban
Если нет блокировок
1. Проверить наличие данных в логах:
sudo cat /opt/fail2ban/logs/nginx-access.log | wc -l
2. Проверить работу фильтров:
sudo fail2ban-regex /opt/fail2ban/logs/nginx-access.log /etc/fail2ban/filter.d/nginx-scan-block.conf
3. Проверить cron задачу:
sudo systemctl status cron sudo tail -f /var/log/syslog | grep CRON
<note important>
Регулярно проверяйте работу системы командой sudo /opt/fail2ban/scripts/manage-fail2ban.sh status и следите за размером логов.
</note>
<note tip>
Для тестирования можно добавить тестовую запись в лог и проверить срабатывание фильтров:
echo '192.168.1.100 - - [01/Sep/2025:10:00:00 +0000] «GET /.env HTTP/1.1» 404 162 «-» «curl»' | sudo tee -a /opt/fail2ban/logs/nginx-access.log
</note>