Содержание

Как использовать /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-адресов на основе анализа логов доступа.

Требования

Установка 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"

Особенности работы

Устранение неполадок

Типичные проблемы

# Проверка что директория 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>