Инструкция по уставновке и настройке стека компонентов: Docker · MariaDB · CrowdSec · SSL · SELinux · c автозапуском всех сервисов
- Обзор
- Схема сети
- Требования
- Подготовка системы
- Обновление пакетов
- Установка VMware Tools
- Установка Docker
- Настройка фаервола
- Установка Nginx Proxy Manager
- Создание структуры проекта
- Создание Docker Secrets
- Создание docker-compose.yml
- Запуск контейнеров
- Настройка безопасности
- Ограничение доступа к Admin (iptables)
- Настройка SELinux для логов NPM
- Установка CrowdSec
- Что такое CrowdSec
- Установка CrowdSec
- Установка Firewall Bouncer
- Подключение логов NPM к CrowdSec
- Проверка работы CrowdSec
- Первичная настройка NPM
- Вход в веб-интерфейс
- Добавление прокси-хоста с SSL
- Настройка Access Lists
- Автозапуск при загрузке системы
- Чеклист безопасности
- Полезные команды
- Управление NPM
- Управление CrowdSec
- Управление iptables
- Решение проблем
Обзор
Nginx Proxy Manager (NPM) — веб-приложение с графическим интерфейсом для управления обратными прокси на базе Nginx. В связке с CrowdSec обеспечивает комплексную защиту от сканеров, брутфорса и известных CVE-атак.
Nginx Proxy Manager (NPM) — веб-приложение с графическим интерфейсом для управления обратными прокси на базе Nginx. В связке с CrowdSec обеспечивает комплексную защиту от сканеров, брутфорса и известных CVE-атак.
Стек компонентов:
- Nginx Proxy Manager — маршрутизация трафика, SSL, управление хостами
- MariaDB — база данных NPM (изолирована во внутренней Docker-сети)
- CrowdSec — анализ логов, обнаружение атак (45+ сценариев)
- CrowdSec Firewall Bouncer — блокировка атакующих через iptables
Схема сети
| Параметр | Значение |
| Публичный трафик | Интернет → 80/443 → NPM → внутренние сервисы |
| Admin-панель (81) | Только из подсетей 10.5.5.0/24 и 10.3.3.0/24 |
| MariaDB | Только внутри Docker npm-internal (не публикуется) |
| CrowdSec | Читает логи NPM, блокирует через iptables |
| IPv6 | Отключён (DISABLE_IPV6: true) |
Требования
- AlmaLinux 9 (минимальная установка)
- Пользователь с правами sudo
- Минимум 1 ГБ RAM, 10 ГБ диска
- Порты 80/443 открыты публично, порт 81 — только внутренние подсети
- Домен направленный на публичный IP (для SSL)
- Подсети администраторов: 10.5.5.0/24 и 10.3.3.0/24
Подготовка системы
Обновление пакетов
sudo dnf update -y
sudo dnf install -y curl wget nano policycoreutils-python-utils mc policycoreutils-python-utils нужен для управления SELinux контекстами (semanage).
Установка VMware Tools
Если сервер работает на VMware:
sudo dnf install -y open-vm-tools
sudo systemctl enable --now vmtoolsd
sudo systemctl status vmtoolsd # → active (running) Установка Docker
Добавить репозиторий:
sudo dnf install -y dnf-plugins-core
sudo dnf config-manager \
--add-repo https://download.docker.com/linux/centos/docker-ce.repo Установить Docker Engine:
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin Включить автозапуск и запустить:
sudo systemctl enable --now docker
sudo systemctl is-enabled docker # → enabled
sudo docker --version # → Docker version 29.x.x
sudo docker compose version # → Docker Compose version v5.x.x Настройка фаервола
Открываем только публичные порты. Порт 81 через firewalld НЕ открываем — его защитим через iptables DOCKER-USER.
sudo firewall-cmd --permanent --add-port=80/tcp
sudo firewall-cmd --permanent --add-port=443/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-ports # → 80/tcp 443/tcp НЕ открывайте порт 81 через firewalld. Docker обходит firewalld напрямую через iptables. Для защиты Docker-портов используется цепочка DOCKER-USER.
Установка Nginx Proxy Manager
Создание структуры проекта
Размещаем проект в /opt — стандартное место для сервисов в Linux. Это также решает проблему SELinux контекстов (файлы из /root недоступны для CrowdSec).
mkdir /opt/nginx-proxy-manager
cd /opt/nginx-proxy-manager
mkdir .secrets
chmod 700 .secrets Структура проекта:
/opt/nginx-proxy-manager/
├── docker-compose.yml # конфигурация контейнеров
├── .secrets/ # файлы паролей (chmod 700)
│ ├── mysql_pwd.txt # пароль пользователя БД
│ └── db_root_pwd.txt # root-пароль MariaDB
├── .gitignore
├── data/ # данные NPM (создаётся автоматически)
│ └── logs/ # логи nginx (читает CrowdSec)
├── letsencrypt/ # SSL-сертификаты
└── mysql/ # данные MariaDB
Создание Docker Secrets
Docker Secrets хранят пароли в файлах и передают их в контейнер через защищённый механизм. Пароли не видны в docker inspect, не попадают в логи и историю shell.
# Генерация надёжных паролей
echo "$(openssl rand -base64 32)" > .secrets/mysql_pwd.txt
echo "$(openssl rand -base64 32)" > .secrets/db_root_pwd.txt
# Ограничить доступ
chmod 600 .secrets/*.txt
# Проверить
ls -la .secrets/
# -rw-------. mysql_pwd.txt
# -rw-------. db_root_pwd.txt # Добавить в .gitignore
cat > .gitignore << 'EOF'
.secrets/
data/
letsencrypt/
mysql/
EOF Никогда не вставляйте пароли напрямую в docker-compose.yml — они видны в docker inspect и системных логах.
Создание docker-compose.yml
nano docker-compose.yml secrets:
MYSQL_PWD:
file: .secrets/mysql_pwd.txt
DB_ROOT_PWD:
file: .secrets/db_root_pwd.txt
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
container_name: npm-app
restart: unless-stopped
ports:
- '80:80' # HTTP публичный
- '443:443' # HTTPS публичный
- '81:81' # Admin (защищён iptables DOCKER-USER)
environment:
DB_MYSQL_HOST: "db"
DB_MYSQL_PORT: 3306
DB_MYSQL_USER: "npm"
DB_MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD
DB_MYSQL_NAME: "npm"
DISABLE_IPV6: 'true'
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
secrets:
- MYSQL_PWD
depends_on:
- db
networks:
- npm-internal
- npm-public
db:
image: 'jc21/mariadb-aria:latest'
container_name: npm-db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD__FILE: /run/secrets/DB_ROOT_PWD
MYSQL_DATABASE: 'npm'
MYSQL_USER: 'npm'
MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD
MARIADB_AUTO_UPGRADE: '1'
volumes:
- ./mysql:/var/lib/mysql
secrets:
- DB_ROOT_PWD
- MYSQL_PWD
networks:
- npm-internal
networks:
npm-internal:
driver: bridge
internal: true # БД недоступна снаружи Docker-сети
npm-public:
driver: bridge
Сохраните: Ctrl+X → Y → Enter
| Параметр | Значение |
| DB_MYSQL_PASSWORD__FILE | Двойное подчёркивание (__FILE) — стандарт Docker Secrets |
| DISABLE_IPV6: true | IPv6 отключён на сервере — без этого NPM не запустится |
| npm-internal: internal | MariaDB изолирована — только NPM может к ней обратиться |
| restart: unless-stopped | Перезапуск при сбое, но не при ручной остановке |
Запуск контейнеров
docker compose up -d
# Проверить статус
docker compose ps
# npm-app Up 0.0.0.0:80->80, 0.0.0.0:443->443, 0.0.0.0:81->81
# npm-db Up 3306/tcp ← порт НЕ опубликован наружу Настройка безопасности
Ограничение доступа к Admin (iptables)
Docker напрямую управляет iptables и обходит firewalld. Для ограничения доступа к портам контейнеров используется цепочка DOCKER-USER — Docker её не перезаписывает.
Добавить правила whitelist:
# Разрешить подсеть администраторов 1
sudo iptables -I DOCKER-USER -p tcp --dport 81 -s 10.5.5.0/24 -j ACCEPT
# Разрешить подсеть администраторов 2
sudo iptables -I DOCKER-USER -p tcp --dport 81 -s 10.3.3.0/24 -j ACCEPT
# Блокировать всех остальных на порт 81
sudo iptables -A DOCKER-USER -p tcp --dport 81 -j DROP Проверить правила:
sudo iptables -L DOCKER-USER -n --line-numbers
# 1 ACCEPT tcp 10.3.3.0/24 anywhere tcp dpt:81
# 2 ACCEPT tcp 10.5.5.0/24 anywhere tcp dpt:81
# 3 DROP tcp anywhere anywhere tcp dpt:81 Порядок правил важен: ACCEPT должны стоять выше DROP. Флаг -I вставляет в начало, -A добавляет в конец.
Сохранить правила (автовосстановление после перезагрузки):
sudo dnf install -y iptables-services
sudo iptables-save | sudo tee /etc/sysconfig/iptables
sudo systemctl enable iptables Настройка SELinux для логов NPM
CrowdSec читает логи NPM с хоста. SELinux по умолчанию блокирует доступ к файлам в нестандартных директориях. Необходимо установить постоянный контекст var_log_t для директории логов.
Без правильного SELinux контекста CrowdSec не сможет читать логи NPM — атаки не будут обнаруживаться.
Установить постоянное правило SELinux:
# Добавить постоянное правило для директории логов
sudo semanage fcontext -a -t var_log_t "/opt/nginx-proxy-manager/data/logs(/.*)?"
# Применить правило к существующим файлам
sudo restorecon -Rv /opt/nginx-proxy-manager/data/logs/
# Проверить контекст
ls -laZ /opt/nginx-proxy-manager/data/logs/ | head -3
# system_u:object_r:var_log_t:s0 ← правильно Правило semanage постоянное — новые файлы proxy-host-N_access.log автоматически получат контекст var_log_t при создании.
Установка CrowdSec
Что такое CrowdSec
CrowdSec — система обнаружения и предотвращения вторжений. Анализирует логи в реальном времени, использует глобальную базу репутации IP-адресов и блокирует атакующих через firewall bouncer.
| Параметр | Значение |
| CrowdSec agent | Читает логи, анализирует поведение, принимает решения о блокировке |
| Firewall Bouncer | Применяет решения через iptables — блокирует IP на уровне сети |
| Время бана | 4 часа по умолчанию |
| Сценариев защиты | 45+ (SQLi, XSS, CVE, сканеры, брутфорс, бэкдоры) |
| Глобальная база IP | Обновляется в реальном времени от сообщества CrowdSec |
Установка CrowdSec
На AlmaLinux 9 CDN packagecloud.io может быть недоступен. Устанавливаем из архива:
Скачать архив (на Windows-машине и передать через SCP)
# Скачать crowdsec-release.tgz со страницы:
# https://github.com/crowdsecurity/crowdsec/releases/latest
# Передать на сервер через SCP (с Windows):
# scp crowdsec-release.tgz root@<IP>:/tmp/
# Распаковать на сервере
cd /tmp
tar -xzf crowdsec-release.tgz
cd crowdsec-v*
# Установить
sudo bash wizard.sh --unattended Проверить установку
sudo systemctl status crowdsec # → active (running)
sudo systemctl is-enabled crowdsec # → enabled Установка Firewall Bouncer
Архив crowdsec-firewall-bouncer-linux-amd64.tgz уже включён в crowdsec-release.tgz:
cd /tmp
tar -xzf crowdsec-firewall-bouncer-linux-amd64.tgz
cd crowdsec-firewall-bouncer-v*
# Установить (выбрать iptables когда спросит)
sudo bash install.sh
# Found nftables and iptables, which firewall? → iptables
# Проверить
sudo systemctl status crowdsec-firewall-bouncer # → active (running) Подключение логов NPM к CrowdSec
Создать конфиг источника логов
nano /etc/crowdsec/acquis.d/npm.yaml filenames:
- /opt/nginx-proxy-manager/data/logs/*.log
labels:
type: nginx
---
filenames:
- /opt/nginx-proxy-manager/data/logs/proxy-host-*_access.log
labels:
type: nginx Установить коллекцию правил для Nginx
sudo cscli collections install crowdsecurity/nginx Перезапустить CrowdSec
sudo systemctl restart crowdsec
sleep 60
# Проверить что логи NPM читаются
sudo cscli metrics | grep -A 15 'Acquisition'
# file:/opt/nginx-proxy-manager/data/logs/proxy-host-1_access.log | 95 | 94 ... Логи появятся в метриках только после первого реального трафика через NPM. Создайте хотя бы один прокси-хост.
Проверка работы CrowdSec
# Просмотр активных блокировок
sudo cscli decisions list
# Метрики и статистика
sudo cscli metrics
# Список установленных сценариев
sudo cscli scenarios list
# Ручная блокировка IP
sudo cscli decisions add --ip 1.2.3.4 --duration 4h --reason 'manual ban'
# Снять блокировку
sudo cscli decisions delete --ip 1.2.3.4
Первичная настройка NPM
Вход в веб-интерфейс
Подключитесь с хоста в подсети 10.5.5.0/24 или 10.3.3.0/24:
http://<IP-сервера>:81
| Параметр | Значение |
| admin@example.com | |
| Пароль | changeme |
Немедленно смените email и пароль после первого входа. Дефолтные данные публично известны.
Добавление прокси-хоста с SSL
- Hosts → Proxy Hosts → Add Proxy Host
- Details: Domain Names = ваш домен (например app.example.com)
- Forward Hostname/IP = IP сервиса, Forward Port = порт сервиса
- Включить: Block Common Exploits
- SSL → Request a new SSL Certificate
- Включить: Force SSL, HTTP/2 Support
- Ввести email, принять условия Let’s Encrypt → Save
Если домен через Cloudflare — НЕ включайте Force SSL. В Cloudflare установите SSL Mode = Full.
Для SSL необходимо чтобы домен указывал на публичный IP сервера и порт 80 был доступен из интернета.
Настройка Access Lists
Для ограничения доступа к конкретным сервисам по IP:
- Access Lists → Add Access List
- Имя: например internal-only
- Вкладка Access: Allow → добавить подсети 10.5.5.0/24, 10.3.3.0/24
- Save → назначить нужному Proxy Host в поле Access List
Автозапуск при загрузке системы
Автозапуск обеспечивается тремя независимыми механизмами:
| Механизм | Что делает |
| systemctl enable docker | Docker запускается при старте системы |
| restart: unless-stopped | Контейнеры NPM и MariaDB поднимаются вместе с Docker |
| systemctl enable iptables | Правила DOCKER-USER восстанавливаются после перезагрузки |
| systemctl enable crowdsec | CrowdSec agent запускается автоматически |
| systemctl enable crowdsec-firewall-bouncer | Bouncer запускается автоматически |
Все пять механизмов настраиваются автоматически в ходе установки по данной инструкции.
Чеклист безопасности
| # | Мера безопасности | Статус |
| 1 | Docker установлен из официального репозитория | ✅ Шаг 2.3 |
| 2 | Порты 80/443 открыты, порт 81 НЕ открыт через firewalld | ✅ Шаг 2.4 |
| 3 | Проект размещён в /opt (не в /root — SELinux) | ✅ Шаг 3.1 |
| 4 | Пароли хранятся через Docker Secrets (не в env) | ✅ Шаг 3.2 |
| 5 | Файлы .secrets/ chmod 600, добавлены в .gitignore | ✅ Шаг 3.2 |
| 6 | MariaDB не публикует порты, изолирована в npm-internal | ✅ Шаг 3.3 |
| 7 | DISABLE_IPV6: true (IPv6 отключён на сервере) | ✅ Шаг 3.3 |
| 8 | restart: unless-stopped на обоих контейнерах | ✅ Шаг 3.3 |
| 9 | iptables DOCKER-USER: :81 только из 10.5.5.0/24, 10.3.3.0/24 | ✅ Шаг 4.1 |
| 10 | Правила iptables сохранены через iptables-services | ✅ Шаг 4.1 |
| 11 | SELinux: var_log_t постоянно на /opt/…/data/logs | ✅ Шаг 4.2 |
| 12 | CrowdSec установлен и читает логи NPM | ✅ Шаг 5 |
| 13 | Firewall Bouncer активен (блокировка через iptables) | ✅ Шаг 5.3 |
| 14 | Коллекция crowdsecurity/nginx (45+ сценариев) | ✅ Шаг 5.4 |
| 15 | Сменить дефолтные креды admin при первом входе | ⚠️ Вручную |
| 16 | Зарегистрировать аккаунт на app.crowdsec.net для мониторинга | ⚠️ Рекомендуется |
| 17 | Ежемесячно обновлять образы: docker compose pull | ⚠️ Регулярно |
Полезные команды
Управление NPM
cd /opt/nginx-proxy-manager
# Статус контейнеров
docker compose ps
# Логи в реальном времени
docker compose logs -f
docker compose logs -f app # только NPM
# Остановить / перезапустить
docker compose down
docker compose restart
# Обновить образы
docker compose pull && docker compose up -d
docker image prune -f
Управление CrowdSec
# Активные блокировки
sudo cscli decisions list
# Метрики
sudo cscli metrics
# Заблокировать IP вручную
sudo cscli decisions add --ip 1.2.3.4 --duration 4h
# Снять блокировку
sudo cscli decisions delete --ip 1.2.3.4
# Обновить сценарии
sudo cscli hub update && sudo cscli hub upgrade Управление iptables
# Просмотр правил DOCKER-USER
sudo iptables -L DOCKER-USER -n --line-numbers -v
# Сохранить текущие правила
sudo iptables-save | sudo tee /etc/sysconfig/iptables
# Восстановить правила вручную
sudo iptables-restore < /etc/sysconfig/iptables Решение проблем
| Параметр | Значение |
| Admin :81 недоступен из сети | Проверьте iptables -L DOCKER-USER -n. Убедитесь что хост в разрешённой подсети |
| CrowdSec не видит логи NPM | Проверьте SELinux: ls -laZ …/data/logs/ → должно быть var_log_t |
| SELinux блокирует доступ | sudo semanage fcontext -a -t var_log_t «/opt/…/logs(/.*)?» && restorecon -Rv |
| Контейнер не стартует | docker compose logs app. Проверьте наличие .secrets/*.txt |
| Ошибка MYSQL_PASSWORD | Двойное подчёркивание: DB_MYSQL_PASSWORD__FILE (не одинарное) |
| Ошибка подключения к БД | Пароли в .secrets/mysql_pwd.txt должны совпадать у app и db |
| SSL не выдаётся | Домен должен указывать на публичный IP. Порт 80 должен быть доступен |
| IPv6 ошибки в логах | DISABLE_IPV6: ‘true’ в секции environment контейнера app |
| Правила iptables сброшены | sudo iptables-restore < /etc/sysconfig/iptables |
| CDN packagecloud.io недоступен | Скачать crowdsec-release.tgz с GitHub и передать через SCP |
| Redirect loop на Cloudflare | В Cloudflare: SSL Mode = Full. В NPM: отключить Force SSL |
| CrowdSec метрики пустые после restart | Подождать 60 сек — читает только новые строки. Проверить трафик |








