vm:nginx:06-file2ban_v2

Это старая версия документа!


Установка и настройка Fail2ban для Nginx Proxy Manager

Данная инструкция описывает процесс установки и настройки Fail2ban для защиты Nginx Proxy Manager от:

  • Сканирования уязвимостей
  • 404 флуда
  • DoS атак
  • SSH брутфорса
  • Ubuntu Server 24.04 (или аналогичный дистрибутив)
  • Nginx Proxy Manager установленный в Docker
  • Root доступ к серверу
apt update
apt install fail2ban -y
mkdir -p /opt/fail2ban/{scripts,logs,config/{jail.d,filter.d}}

Создайте файл /opt/fail2ban/scripts/parse-nginx-logs.sh:

cat > /opt/fail2ban/scripts/parse-nginx-logs.sh << 'EOF'
#!/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
total_lines=0
 
# Обрабатываем все файлы с окончанием *_access.log
for logfile in "$LOG_DIR"/proxy-host-*_access.log; do
    # Проверяем, что файл существует (на случай если паттерн не найден)
    if [ -f "$logfile" ]; then
        lines_from_file=$(tail -n 2000 "$logfile" | wc -l)
        tail -n 2000 "$logfile" >> "$LOG_FILE"
        processed_files=$((processed_files + 1))
        total_lines=$((total_lines + lines_from_file))
        echo "$(date): Обработан файл: $(basename "$logfile") - $lines_from_file строк" >> "$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
    CUTOFF_DATE=$(date -d '30 days ago' '+%d/%b/%Y')
    TEMP_FILTERED="/tmp/nginx-filtered-logs"
 
    # Фильтруем записи не старше 30 дней
    awk -v cutoff="$CUTOFF_DATE" '
    {
        # Извлекаем дату из строки лога (формат: 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)
 
            cmd2 = "date -d \"" cutoff "\" +%s 2>/dev/null"
            cmd2 | getline cutoff_timestamp
            close(cmd2)
 
            # Если дата лога >= даты отсечки, выводим строку
            if (log_timestamp >= cutoff_timestamp) {
                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
EOF
chmod +x /opt/fail2ban/scripts/parse-nginx-logs.sh
(crontab -l 2>/dev/null; echo "*/2 * * * * /opt/fail2ban/scripts/parse-nginx-logs.sh") | crontab -

Проверка добавления в cron:

crontab -l
/opt/fail2ban/scripts/parse-nginx-logs.sh

Проверка результата:

ls -lh /opt/fail2ban/logs/nginx-access.log
tail -10 /opt/fail2ban/logs/nginx-access.log
cat > /opt/fail2ban/config/filter.d/nginx-scan-block.conf << 'EOF'
[Definition]
 
# Формат NPM: [DATE] - STATUS STATUS - METHOD PROTOCOL DOMAIN "PATH" [Client IP]
failregex = ^\[.*\] - \d+ \d+ - GET https? .* ".*(/\.env|/\.git|/\.aws|/\.ssh|/config|/backup|wp-config|phpinfo|admin/config|server-status|server-info).*" \[Client <HOST>\]
            ^\[.*\] - 40\d \d+ - GET https? .* ".*\.(php|asp|jsp|cgi|pl|py).*" \[Client <HOST>\]
            ^\[.*\] - \d+ \d+ - GET https? .* ".*\.\.\/.*" \[Client <HOST>\]
            ^\[.*\] - \d+ \d+ - GET https? .* ".*(union|select|insert|update|delete|drop|create|alter).*" \[Client <HOST>\]
            ^\[.*\] - \d+ \d+ - (GET|POST) https? .* ".*(<script|javascript:|vbscript:|onload|onerror).*" \[Client <HOST>\]
            ^\[.*\] - \d+ \d+ - (GET|POST) https? .* ".*(\||`|;|<|>|\\|\{|\}|\[|\]).*" \[Client <HOST>\]
            ^\[.*\] - \d+ \d+ - GET https? .* ".*/(etc/|var/|bin/|usr/bin/|tmp/|proc/).*" \[Client <HOST>\]
 
ignoreregex =
EOF
cat > /opt/fail2ban/config/filter.d/nginx-404-flood.conf << 'EOF'
[Definition]
 
# Блокировка множественных 404 ошибок
failregex = ^\[.*\] - 404 \d+ - (GET|POST|HEAD) https? .* ".*" \[Client <HOST>\]
 
ignoreregex =
EOF
cat > /opt/fail2ban/config/filter.d/nginx-dos-block.conf << 'EOF'
[Definition]
 
# Блокировка слишком частых запросов (DoS)
failregex = ^\[.*\] - \d+ \d+ - (GET|POST|HEAD) https? .* ".*" \[Client <HOST>\]
 
ignoreregex = ^\[.*\] - \d+ \d+ - (GET|POST) https? .* ".*\.(css|js|jpg|jpeg|png|gif|ico|woff|woff2|ttf|svg).*" \[Client <HOST>\]
EOF
ln -sf /opt/fail2ban/config/filter.d/nginx-scan-block.conf /etc/fail2ban/filter.d/nginx-scan-block.conf
ln -sf /opt/fail2ban/config/filter.d/nginx-404-flood.conf /etc/fail2ban/filter.d/nginx-404-flood.conf
ln -sf /opt/fail2ban/config/filter.d/nginx-dos-block.conf /etc/fail2ban/filter.d/nginx-dos-block.conf

<WRAP center round important 60%> ВАЖНО: Замените IP адреса в параметре ignoreip на свои локальные сети и доверенные IP. </WRAP>

cat > /opt/fail2ban/config/jail.d/nginx-protection.conf << 'EOF'
# Jail конфигурация для nginx защиты
[DEFAULT]
# Время бана в секундах (1 час = 3600)
bantime = 3600
# Время поиска нарушений (5 минут = 300)
findtime = 300
# Игнорировать локальные IP
ignoreip = 127.0.0.1/8 ::1 192.168.0.0/24 172.16.0.0/12 10.0.0.0/8
 
[nginx-scan-block]
enabled = true
port = http,https
filter = nginx-scan-block
logpath = /opt/fail2ban/logs/nginx-access.log
backend = polling
maxretry = 3
bantime = 7200
findtime = 300
action = iptables-multiport[name=nginx-scan, port="http,https", protocol=tcp]
 
[nginx-dos-block]
enabled = true
port = http,https  
filter = nginx-dos-block
logpath = /opt/fail2ban/logs/nginx-access.log
backend = polling
maxretry = 50
bantime = 600
findtime = 60
action = iptables-multiport[name=nginx-dos, port="http,https", protocol=tcp]
 
[nginx-404-flood]
enabled = true
port = http,https
filter = nginx-404-flood
logpath = /opt/fail2ban/logs/nginx-access.log
backend = polling
maxretry = 10
bantime = 3600
findtime = 600
action = iptables-multiport[name=nginx-404, port="http,https", protocol=tcp]
EOF
ln -sf /opt/fail2ban/config/jail.d/nginx-protection.conf /etc/fail2ban/jail.d/nginx-protection.conf
fail2ban-client -t

Ожидаемый результат:

OK: configuration test is successful
fail2ban-regex /opt/fail2ban/logs/nginx-access.log /opt/fail2ban/config/filter.d/nginx-scan-block.conf
fail2ban-regex /opt/fail2ban/logs/nginx-access.log /opt/fail2ban/config/filter.d/nginx-404-flood.conf
fail2ban-regex /opt/fail2ban/logs/nginx-access.log /opt/fail2ban/config/filter.d/nginx-dos-block.conf

Должны быть найдены совпадения (matched > 0).

# Включить автозагрузку
systemctl enable fail2ban
 
# Запустить службу
systemctl start fail2ban
 
# Проверить статус
systemctl status fail2ban
fail2ban-client status

Ожидаемый вывод:

Status
|- Number of jail:      4
`- Jail list:   nginx-404-flood, nginx-dos-block, nginx-scan-block, sshd
fail2ban-client status nginx-scan-block
fail2ban-client status nginx-404-flood
fail2ban-client status nginx-dos-block

Пример вывода:

Status for the jail: nginx-scan-block
|- Filter
|  |- Currently failed: 5
|  |- Total failed:     176
|  `- File list:        /opt/fail2ban/logs/nginx-access.log
`- Actions
   |- Currently banned: 2
   |- Total banned:     2
   `- Banned IP list:   3.96.220.169 56.228.32.138
iptables -L -n | grep -E "Chain|nginx"

Ожидаемый результат:

Chain INPUT (policy ACCEPT)
f2b-nginx-dos  6    --  0.0.0.0/0            0.0.0.0/0            multiport dports 80,443
f2b-nginx-404  6    --  0.0.0.0/0            0.0.0.0/0            multiport dports 80,443
f2b-nginx-scan  6    --  0.0.0.0/0            0.0.0.0/0            multiport dports 80,443
Chain f2b-nginx-404 (1 references)
Chain f2b-nginx-dos (1 references)
Chain f2b-nginx-scan (1 references)

Детальный просмотр забаненных IP:

iptables -L f2b-nginx-scan -n -v
iptables -L f2b-nginx-404 -n -v
iptables -L f2b-nginx-dos -n -v
# Последние 50 строк
tail -50 /var/log/fail2ban.log
 
# Только баны
tail -100 /var/log/fail2ban.log | grep -E "Ban|Unban"
 
# Мониторинг в реальном времени
tail -f /var/log/fail2ban.log
tail -50 /opt/fail2ban/logs/parser.log
echo "=== NGINX SCAN BLOCK ==="
fail2ban-client status nginx-scan-block
echo ""
echo "=== NGINX 404 FLOOD ==="
fail2ban-client status nginx-404-flood
echo ""
echo "=== NGINX DOS BLOCK ==="
fail2ban-client status nginx-dos-block
# Разбан в конкретном jail
fail2ban-client set nginx-scan-block unbanip 1.2.3.4
 
# Разбан во всех jail
fail2ban-client unban 1.2.3.4
fail2ban-client set nginx-scan-block banip 1.2.3.4
Параметр Описание Рекомендуемое значение
maxretry Количество попыток до бана 3-10 для scan, 50 для DoS, 10 для 404
findtime Временное окно поиска нарушений (секунды) 300 (5 минут)
bantime Время бана (секунды) 3600-7200 (1-2 часа)
  • maxretry: 3 попытки
  • findtime: 300 секунд (5 минут)
  • bantime: 7200 секунд (2 часа)

Блокирует попытки доступа к:

  • Конфигурационным файлам (.env, .git, wp-config.php)
  • Скриптам (.php, .asp, .jsp, .cgi)
  • SQL инъекциям
  • XSS атакам
  • Path traversal
  • maxretry: 10 попыток
  • findtime: 600 секунд (10 минут)
  • bantime: 3600 секунд (1 час)

Блокирует множественные запросы к несуществующим страницам.

  • maxretry: 50 запросов
  • findtime: 60 секунд (1 минута)
  • bantime: 600 секунд (10 минут)

Блокирует слишком частые запросы, игнорируя статические файлы (css, js, изображения).

Проверка логов:

journalctl -xeu fail2ban
systemctl status fail2ban

Проверка конфигурации:

fail2ban-client -d

Проверка чтения лог-файла:

fail2ban-client status nginx-scan-block

Должно быть File list: /opt/fail2ban/logs/nginx-access.log, а не Journal matches.

Если видите Journal matches, убедитесь что в конфигурации jail указан backend = polling.

Тестирование фильтра:

fail2ban-regex /opt/fail2ban/logs/nginx-access.log /opt/fail2ban/config/filter.d/nginx-scan-block.conf --print-all-matched

Проверка формата логов:

head -3 /opt/fail2ban/logs/nginx-access.log

Формат должен быть:

[DATE] - STATUS STATUS - METHOD PROTOCOL DOMAIN "PATH" [Client IP] ...

Проверка работы парсера:

bash -x /opt/fail2ban/scripts/parse-nginx-logs.sh
cat /opt/fail2ban/logs/parser.log

Проверка наличия логов Nginx Proxy Manager:

ls -la /opt/nginxpm/data/logs/proxy-host-*_access.log

Проверка работы контейнера:

docker ps | grep nginxpm

Fail2ban обновляется вместе с системой:

apt update
apt upgrade fail2ban -y
systemctl restart fail2ban
# Конфигурация
/opt/fail2ban/config/
 
# Скрипты
/opt/fail2ban/scripts/
 
# Логи (опционально)
/opt/fail2ban/logs/
tar -czf fail2ban-backup-$(date +%Y%m%d).tar.gz \
  /opt/fail2ban/config/ \
  /opt/fail2ban/scripts/ \
  /etc/fail2ban/jail.d/nginx-protection.conf \
  /etc/fail2ban/filter.d/nginx-*.conf
# Остановить службу
systemctl stop fail2ban
systemctl disable fail2ban
 
# Удалить пакет
apt remove --purge fail2ban -y
 
# Удалить конфигурацию
rm -rf /opt/fail2ban
rm -f /etc/fail2ban/jail.d/nginx-protection.conf
rm -f /etc/fail2ban/filter.d/nginx-scan-block.conf
rm -f /etc/fail2ban/filter.d/nginx-404-flood.conf
rm -f /etc/fail2ban/filter.d/nginx-dos-block.conf
 
# Удалить из cron
crontab -e
# Удалить строку с parse-nginx-logs.sh
 
# Очистить правила iptables
iptables -F

После выполнения всех шагов ваш сервер будет защищен от:

  • ✅ Сканирования уязвимостей
  • ✅ 404 флуда
  • ✅ DoS атак
  • ✅ SSH брутфорса

Fail2ban автоматически:

  • Собирает логи из Nginx Proxy Manager каждые 2 минуты
  • Анализирует запросы на предмет атак
  • Блокирует подозрительные IP на уровне iptables
  • Автоматически разбанивает IP после истечения времени бана

Автор: Nick
Дата создания: 2025-11-30
Последнее обновление: 2025-11-30

  • vm/nginx/06-file2ban_v2.1764590944.txt.gz
  • Последнее изменение: 2025/12/01 12:09
  • admin