Диагноз: почему это вообще нужно
Proxmox висит на 192.168.1.10:8006. Nextcloud — на 192.168.1.15:8080. Pi-hole — на 192.168.1.20:80. Vaultwarden — на 192.168.1.25:8080. Grafana — на 192.168.1.30:3000.
Полгода так жить можно. Через год начинаешь вести блокнотик с портами. Ещё через год блокнотик теряется.
NPM решает это один раз. После настройки у тебя: https://pve.domain.com, https://cloud.domain.com, https://vault.domain.com — всё с валидным SSL, все порты спрятаны внутрь, единая точка входа.
Что сделаем в этой статье:
- Поднимем NPM через Docker Compose
- Разберём, что значит каждый параметр в настройках
- Настроим 10 сервисов: Proxmox VE, Proxmox Backup Server, Zimbra, MailCow, Pi-hole, Nextcloud, OpenMediaVault, Vaultwarden, Grafana
- Разберём типичные ошибки и как их чинить
Потребуется: сервер с Docker, домен с DNS-записями на твой IP, 30-60 минут.
Архитектура: как это работает
NPM принимает входящий HTTPS-трафик, терминирует SSL и пересылает запрос на бэкенд. Бэкенд может работать по HTTP внутри Docker-сети — снаружи всё равно будет HTTPS.
Базовые понятия: что нужно понять до настройки
Scheme: http или https к бэкенду
Это не про то, как клиент подключается к NPM. Это про то, как NPM подключается к твоему сервису сзади. Proxmox всегда слушает на HTTPS с самоподписанным сертом — ставишь https и добавляешь proxy_ssl_verify off. Контейнеры в одной Docker-сети — обычно http, и это нормально.
WebSocket Support
Включай для всего, что показывает данные в реальном времени: консоли VM в Proxmox, live-графики в Grafana, уведомления в Nextcloud Talk, статистика Pi-hole. Без этой галочки эти функции просто не работают — без ошибок, просто молча.
Заголовки X-Forwarded-*
Когда NPM проксирует запрос, бэкенд видит IP адрес NPM, а не реального пользователя. Заголовки X-Forwarded-For, X-Real-IP и X-Forwarded-Proto передают реальный IP и протокол. Без них: некорректные логи, сломанный CSRF в MailCow и Zimbra, неправильные редиректы в Nextcloud.
Docker-сеть как транспорт
Если NPM и сервис в одной Docker-сети, обращайся к контейнеру по имени, не по IP. IP контейнера меняется при каждом перезапуске. Имя — нет.
Установка Nginx Proxy Manager
Рекомендуемый способ — Docker Compose. Никакой магии, просто удобнее обновлять и бэкапить.
Создай директорию и файл конфига:
mkdir -p /opt/nginx-proxy-manager && cd /opt/nginx-proxy-manager
Создай docker-compose.yml:
version: '3.8'
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- '80:80'
- '81:81'
- '443:443'
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
networks:
- proxy-network
networks:
proxy-network:
driver: bridge
name: proxy-network
Запусти:
docker compose up -d
Открой http://твой-сервер:81. Логин по умолчанию: admin@example.com / changeme.
Если сервер смотрит в интернет — порт 81 закрой через firewall или Access List в самом NPM. Интерфейс управления не должен быть доступен снаружи.
Системные требования
| Компонент | Минимум | Рекомендуется |
|---|---|---|
| Docker | 20.x+ | последняя стабильная |
| Docker Compose | v2.x (плагин) | v2.20+ |
| RAM | 256 МБ | 512 МБ+ |
| Порты | 80, 443, 81 | свободны на хосте |
| ОС | любой Linux с Docker | Ubuntu 22.04 / Debian 12 |
На момент публикации актуальна версия NPM 2.12.x. Перед установкой проверь свежие релизы на GitHub.
Таблица портов
| Порт | Протокол | Назначение | Доступен снаружи? |
|---|---|---|---|
| 80 | TCP | HTTP трафик, HTTP-challenge Let’s Encrypt | Да |
| 443 | TCP | HTTPS трафик | Да |
| 81 | TCP | Веб-интерфейс управления NPM | Нет (только LAN / VPN) |
Proxmox VE: настройка reverse proxy (порт 8006)
Proxmox — один из самых капризных в плане проксирования. Самоподписанный сертификат, WebSocket для консолей VM и лимит на загрузку ISO. Всё это нужно учесть.
В NPM создаёшь новый Proxy Host:
| Параметр | Значение |
|---|---|
| Domain Names | pve.yourdomain.com |
| Scheme | https |
| Forward Hostname/IP | IP твоего Proxmox сервера |
| Forward Port | 8006 |
| WebSocket Support | включить |
| Block Common Exploits | включить |
| SSL — Let’s Encrypt | включить, Force SSL |
Вкладка Advanced — вставляй конфиг:
# Proxmox использует самоподписанный сертификат - проверку отключаем
proxy_ssl_verify off;
# Стандартные заголовки прокси
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port 443;
# WebSocket - без этого консоли VM и LXC не откроются
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 60 минут - консоль держит соединение, дефолтные 60 секунд не хватит
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
# Отключаем буферизацию - консоль должна работать в реальном времени
proxy_buffering off;
# Для загрузки ISO и дисков через интерфейс
client_max_body_size 50G;
Проверка: открой https://pve.yourdomain.com, зайди, запусти консоль любой VM. Если консоль не открывается — первым делом смотри, включён ли WebSocket Support в настройках Proxy Host, не в Advanced конфиге.
Proxmox Backup Server: настройка reverse proxy (порт 8007)
Всё то же самое, что PVE, плюс бесконечный таймаут для длительных задач резервного копирования.
| Параметр | Значение |
|---|---|
| Domain Names | pbs.yourdomain.com |
| Scheme | https |
| Forward Port | 8007 |
| WebSocket Support | включить |
proxy_ssl_verify off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Без ограничений - бэкапы бывают очень большими
client_max_body_size 0;
# 24 часа - длительные задачи резервного копирования
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
# Отключаем перекодирование для бинарных данных
proxy_set_header Accept-Encoding "";
Zimbra Collaboration: настройка reverse proxy (порты 8443 и 7071)
Zimbra — два отдельных веб-интерфейса. Веб-клиент на порту 8443 и административная панель на 7071. Каждый получает свой Proxy Host.
Веб-клиент (mail.yourdomain.com -> порт 8443)
proxy_ssl_verify off;
# Zimbra чувствителен к этим заголовкам - без них ломается авторизация
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port 443;
# Для загрузки вложений
client_max_body_size 1024M;
proxy_read_timeout 300s;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
# Zimbra сам управляет сжатием
proxy_set_header Accept-Encoding "";
Административная панель (zimbra-admin.yourdomain.com -> порт 7071)
Конфиг аналогичен веб-клиенту. Но вот тут важно: ограничь доступ через Access Lists только для администраторских IP. Не выставляй admin-панель в открытый интернет.
MailCow Dockerized: настройка reverse proxy
Тут чуть сложнее. MailCow по умолчанию слушает на 0.0.0.0:80 и 0.0.0.0:443 — напрямую конфликтует с NPM. Нужно перенести MailCow на другие порты и объединить их в одну Docker-сеть.
Шаг 1: перенастраиваем порты MailCow
В файле mailcow.conf меняй:
HTTP_PORT=8081
HTTPS_PORT=8442
HTTP_BIND=127.0.0.1
HTTPS_BIND=127.0.0.1
Шаг 2: подключаем контейнер nginx-mailcow к общей сети
Создай файл docker-compose.override.yml в директории MailCow:
version: '3.8'
services:
nginx-mailcow:
networks:
- proxy-network
networks:
proxy-network:
external: true
Шаг 3: перезапускаем MailCow
docker compose down && docker compose up -d
Настройка Proxy Host в NPM
| Параметр | Значение |
|---|---|
| Scheme | http |
| Forward Hostname/IP | nginx-mailcow (имя контейнера) |
| Forward Port | 80 |
| WebSocket Support | включить |
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Критически важно для CSRF-защиты MailCow - без этого получишь "Invalid CSRF token"
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_buffering off;
Pi-hole: настройка reverse proxy (порт 80)
Pi-hole прост в проксировании, но у него отдельный WebSocket endpoint для live-статистики дашборда. Начиная с версии 6 интерфейс переработан — проверяй пути в актуальной документации.
| Параметр | Значение |
|---|---|
| Scheme | http |
| Forward Port | 80 |
| WebSocket Support | включить |
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Отдельный location для WebSocket Pi-hole v5 (для v6 проверяй документацию)
location /ws {
proxy_pass http://pihole-ip:80;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
# Security заголовки
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
Nextcloud: настройка reverse proxy (Docker)
Nextcloud — самый требовательный к конфигурации из всех. Он проверяет заголовки, требует явного указания trusted_proxies и ломает ссылки если не указать overwriteprotocol. Зато после правильной настройки работает без нареканий.
Настройка Proxy Host в NPM
| Параметр | Значение |
|---|---|
| Scheme | http |
| Forward Hostname/IP | nextcloud-app (имя контейнера) |
| Forward Port | 80 |
| WebSocket Support | включить |
| SSL | Let’s Encrypt, Force SSL, HTTP/2 |
# Базовые заголовки
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port 443;
# Security заголовки - убирают предупреждения в /settings/admin/overview
add_header Referrer-Policy "no-referrer" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Download-Options "noopen" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
add_header X-Robots-Tag "none" always;
add_header X-XSS-Protection "1; mode=block" always;
# Для загрузки больших файлов
client_max_body_size 10G;
proxy_request_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
# WebSocket для Nextcloud Talk
location /push/ {
proxy_pass http://nextcloud-app:80;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
# WebDAV для клиентов синхронизации
location /remote.php/dav/ {
proxy_pass http://nextcloud-app:80;
client_max_body_size 10G;
proxy_read_timeout 3600s;
}
# Редиректы для CalDAV и CardDAV
location /.well-known/carddav {
return 301 /remote.php/dav/;
}
location /.well-known/caldav {
return 301 /remote.php/dav/;
}
Обязательные изменения в config/config.php Nextcloud
'overwriteprotocol' => 'https',
'overwritehost' => 'cloud.yourdomain.com',
'overwritewebroot' => '/',
// Доверенные прокси - подсеть Docker или конкретный IP NPM
'trusted_proxies' => ['172.20.0.0/16'],
'forwarded_for_headers' => ['HTTP_X_FORWARDED_FOR'],
'trusted_domains' => [
'cloud.yourdomain.com',
],
Проверка: открой https://cloud.yourdomain.com/settings/admin/overview. Раздел «Безопасность и настройка» не должен содержать предупреждений о заголовках, протоколе или доверенных прокси.
OpenMediaVault: настройка reverse proxy (порт 80 или 8080)
OMV по умолчанию занимает порт 80. Если на том же хосте уже что-то висит — меняй порт через omv-env.
Если нужно освободить порт 80 на хосте OMV
sudo omv-env set OMV_WEBGUI_HTTP_PORT 8080
sudo omv-salt deploy run webgui
Вот тут важно: OMV нужен именно $http_host в заголовке Host, а не $host. Разница небольшая, но OMV без этого ломает сессии.
# OMV требует $http_host, не $host - с $host ломаются сессии
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
location /ws {
proxy_pass http://omv-server:80;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
Vaultwarden: настройка reverse proxy (Bitwarden self-hosted)
Vaultwarden требует HTTPS — без валидного сертификата клиентские приложения Bitwarden просто не подключатся. Не «ругаются», а именно не подключаются. Поэтому Force SSL и HSTS обязательны.
У Vaultwarden два порта для WebSocket: основной HTTP на 80 и отдельный WebSocket на 3012. Нужно проксировать оба.
| Параметр | Значение |
|---|---|
| Scheme | http |
| Forward Port | 80 |
| SSL | Let’s Encrypt, Force SSL, HSTS |
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket для синхронизации между устройствами - отдельный порт 3012
location /notifications/hub {
proxy_pass http://vaultwarden:3012;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
location /notifications/hub/negotiate {
proxy_pass http://vaultwarden:80;
}
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "same-origin" always;
В docker-compose.yml Vaultwarden добавь переменные среды:
environment:
- WEBSOCKET_ENABLED=true
- DOMAIN=https://vault.yourdomain.com
Grafana: настройка reverse proxy (порт 3000)
Grafana проксируется просто, но без правильного root_url в конфиге ломаются ссылки в дашбордах и уведомлениях алертов. Ссылки будут вести на localhost:3000 вместо твоего домена.
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket для live-обновления графиков
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
В grafana.ini или через переменные среды в docker-compose.yml:
# grafana.ini
[server]
domain = grafana.yourdomain.com
root_url = https://grafana.yourdomain.com
serve_from_sub_path = false
# docker-compose.yml - через переменные среды
environment:
- GF_SERVER_DOMAIN=grafana.yourdomain.com
- GF_SERVER_ROOT_URL=https://grafana.yourdomain.com
Docker Compose для всей инфраструктуры
Если поднимаешь всё сразу — вот шаблон. Все контейнеры в одной сети, NPM обращается к ним по именам.
version: '3.8'
networks:
proxy-network:
name: proxy-network
driver: bridge
services:
npm:
image: jc21/nginx-proxy-manager:latest
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "81:81"
volumes:
- ./npm/data:/data
- ./npm/letsencrypt:/etc/letsencrypt
networks:
- proxy-network
nextcloud:
image: nextcloud:latest
container_name: nextcloud-app
restart: unless-stopped
volumes:
- ./nextcloud/html:/var/www/html
- ./nextcloud/data:/var/www/html/data
networks:
- proxy-network
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
environment:
- WEBSOCKET_ENABLED=true
- DOMAIN=https://vault.yourdomain.com
volumes:
- ./vaultwarden/data:/data
networks:
- proxy-network
pihole:
image: pihole/pihole:latest
container_name: pihole
restart: unless-stopped
environment:
- TZ=Europe/Moscow
- WEBPASSWORD=твой_пароль
volumes:
- ./pihole/etc-pihole:/etc/pihole
- ./pihole/etc-dnsmasq.d:/etc/dnsmasq.d
networks:
- proxy-network
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
environment:
- GF_SERVER_DOMAIN=grafana.yourdomain.com
- GF_SERVER_ROOT_URL=https://grafana.yourdomain.com
volumes:
- ./grafana/data:/var/lib/grafana
networks:
- proxy-network
Продвинутая настройка: SSO с Authelia
Если хочешь единый вход для всех сервисов с двухфакторной аутентификацией — смотри в сторону Authelia. Интегрируется с NPM через auth_request. Подробно разберём в отдельной статье, но вот скелет конфига для Proxy Host:
location / {
auth_request /authelia;
auth_request_set $user $upstream_http_remote_user;
auth_request_set $groups $upstream_http_remote_groups;
proxy_set_header Remote-User $user;
proxy_set_header Remote-Groups $groups;
# Редирект на страницу входа при 401
error_page 401 =302 https://auth.yourdomain.com/?rd=$scheme://$host$request_uri;
proxy_pass http://твой-сервис:порт;
}
location = /authelia {
internal;
proxy_pass http://authelia:9091/api/verify;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Ограничение доступа по IP
Для административных интерфейсов — обязательно. Zimbra Admin, Proxmox — не должны быть открыты для всего интернета.
# Разрешаем только из локальной сети и VPN
allow 192.168.1.0/24;
allow 10.8.0.0/24;
deny all;
Проверка работы
После настройки каждого сервиса — проверяй командами, а не только «открыл в браузере и посмотрел.
# Проверить синтаксис конфигурации Nginx внутри NPM
docker exec nginx-proxy-manager nginx -t
# Логи NPM - последние 100 строк
docker logs --tail 100 nginx-proxy-manager
# Проверить SSL сертификат
openssl s_client -connect cloud.yourdomain.com:443 -servername cloud.yourdomain.com 2>/dev/null | openssl x509 -noout -dates
# Проверить заголовки ответа
curl -I https://cloud.yourdomain.com
# Access-лог конкретного proxy host (1 = номер хоста из URL в интерфейсе NPM)
docker exec nginx-proxy-manager tail -f /data/logs/proxy-host-1_access.log
# Проверить WebSocket
curl -i \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" \
-H "Sec-WebSocket-Version: 13" \
https://pve.yourdomain.com
# Проверить реальный IP в заголовках (должен видеть твой IP, не IP NPM)
curl -s https://cloud.yourdomain.com/ocs/v2.php/cloud/user | grep -i forwarded
Осложнения: типичные ошибки и их решения
Ошибка: Invalid CSRF token в MailCow или Zimbra
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header X-Forwarded-Proto https;
Ошибка: консоль VM в Proxmox не открывается
Либо не включён WebSocket Support в настройках Proxy Host (это галочка на вкладке Details, не в Advanced), либо маленький таймаут.
proxy_read_timeout 3600s;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_buffering off;
Ошибка 413: Request Entity Too Large
Nginx блокирует загрузку файлов больше 1 МБ по умолчанию. Добавляй в Advanced конфиг нужного хоста:
# Для Nextcloud и Zimbra - конкретный лимит
client_max_body_size 10G;
# Для PBS - без ограничений
client_max_body_size 0;
Ошибка: Nextcloud пишет «Untrusted domain»
Домен не добавлен в trusted_domains или NPM не в trusted_proxies в config.php.
'trusted_domains' => ['cloud.yourdomain.com'],
'trusted_proxies' => ['172.20.0.0/16'],
'overwriteprotocol' => 'https',
Ошибка: Let’s Encrypt сертификат не получается
Три частых причины:
- Порт 80 закрыт на файерволле или роутере — HTTP challenge не проходит
- DNS запись ещё не распространилась — жди до 30 минут
- Rate limit Let’s Encrypt — не больше 5 сертификатов на домен в неделю
# Смотреть ошибки получения сертификата
docker logs nginx-proxy-manager | grep -i "cert\|renew\|error\|acme"
# Проверить что порт 80 доступен снаружи
curl -v http://pve.yourdomain.com
# Проверить DNS запись
nslookup pve.yourdomain.com
Ошибка: реальные IP не отображаются в логах бэкенда
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
Для Nextcloud — дополнительно в config.php:
'forwarded_for_headers' => ['HTTP_X_FORWARDED_FOR'],
Ошибка: 502 Bad Gateway
NPM не может достучаться до бэкенда. Первым делом — проверить что сервис вообще запущен и слушает на нужном порту:
# Проверить с хоста NPM
docker exec nginx-proxy-manager curl -k https://192.168.1.10:8006
# Если сервис в Docker - проверить что в одной сети
docker network inspect proxy-network | grep -A 4 "nginx-proxy-manager\|nextcloud"
Безопасность
NPM — единая точка входа в твою инфраструктуру. Если NPM падает или взламывают NPM — всё падает или взламывается всё. Минимум который нужно сделать:
- Смени пароль по умолчанию — логин
admin@example.com / changemeсканируется ботами - Закрой порт 81 от внешнего мира через firewall или Access List в NPM
- Для административных сервисов — ограничь доступ по IP через Access Lists
- Включай HSTS для сервисов с чувствительными данными — особенно Vaultwarden
- Регулярно обновляй NPM
# Обновление NPM без потери конфигурации
cd /opt/nginx-proxy-manager
docker compose pull
docker compose up -d
# Проверить что обновилось
docker inspect nginx-proxy-manager | grep -i "image\|version"
Резервное копирование NPM
# Бэкап конфигурации и сертификатов
cd /opt/nginx-proxy-manager
tar -czf /backup/npm-backup-$(date +%Y%m%d-%H%M%S).tar.gz ./data ./letsencrypt
# Восстановление - остановить NPM, распаковать, запустить
docker compose down
tar -xzf /backup/npm-backup-20260101-120000.tar.gz
docker compose up -d
Добавь в crontab для ежедневного бэкапа:
# Ежедневный бэкап в 2:00
0 2 * * * cd /opt/nginx-proxy-manager && tar -czf /backup/npm-backup-$(date +\%Y\%m\%d).tar.gz ./data ./letsencrypt
Сравнительная таблица всех сервисов
| Сервис | Порт | Scheme | WebSocket | Особенности |
|---|---|---|---|---|
| Proxmox VE | 8006 | https | Да | proxy_ssl_verify off, client_max_body_size 50G, timeout 3600s |
| Proxmox Backup Server | 8007 | https | Да | proxy_ssl_verify off, client_max_body_size 0, timeout 86400s |
| Zimbra (webmail) | 8443 | https | Нет | proxy_ssl_verify off, client_max_body_size 1G |
| Zimbra (admin) | 7071 | https | Нет | Ограничить доступ по IP |
| MailCow | 80 (контейнер) | http | Да | X-Forwarded-Host, общая Docker-сеть, override compose |
| Pi-hole | 80 | http | Да (/ws) | Отдельный location для /ws |
| Nextcloud | 80 (контейнер) | http | Да (/push/) | trusted_proxies в config.php, client_max_body_size 10G |
| OpenMediaVault | 80 или 8080 | http | Да (/ws) | Host $http_host вместо $host |
| Vaultwarden | 80 + 3012 (WS) | http | Да (порт 3012) | HTTPS обязателен, DOMAIN в env, HSTS |
| Grafana | 3000 | http | Да | root_url в grafana.ini или через env |
Rate Limiting: защита от перебора паролей
NPM не делает rate limiting из коробки, но Nginx умеет. Добавляется в Custom Nginx Configuration нужного Proxy Host.
Для Vaultwarden и любых форм авторизации:
limit_req_zone $binary_remote_addr zone=vault_auth:10m rate=5r/m;
location /api/accounts/login {
limit_req zone=vault_auth burst=3 nodelay;
limit_req_status 429;
proxy_pass http://vaultwarden:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Для Nextcloud ограничиваем login endpoint:
limit_req_zone $binary_remote_addr zone=nc_login:10m rate=10r/m;
location /login {
limit_req zone=nc_login burst=5 nodelay;
proxy_pass http://nextcloud-app:80;
}
Security Headers: укрепляем HTTP-ответы
Заголовки безопасности — не только для Nextcloud. Хорошая практика добавлять их для всех публично доступных сервисов.
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# HSTS - только HTTPS. Добавляй только если уверен что SSL работает стабильно.
# После включения браузер откажется открывать сайт по HTTP на год вперёд.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
HSTS добавляй последним — после того как убедился что Let’s Encrypt обновляет сертификат нормально.
Профилактика: как не сломать снова
Три вещи, которые ломают NPM после того как всё настроено:
- Обновление MailCow без пересоздания override-файла для Docker-сети — контейнер nginx-mailcow отваливается от proxy-network
- Смена IP бэкенд-сервиса без обновления Forward Hostname в NPM — получаешь 502
- Истечение сертификата из-за закрытого порта 80 на файерволле — NPM не может пройти HTTP-challenge
Простая профилактика:
- Раз в месяц проверяй сроки: SSL Certificates в интерфейсе NPM показывает дату истечения для каждого сертификата
- Docker Compose файлы держи в Git — при любом изменении видно что именно изменилось
- После любых правок файерволла проверяй что порт 80 доступен снаружи
- Бэкап перед любым обновлением NPM — это 30 секунд одной командой
И ещё одно: если что-то перестало работать после плановых обновлений сервисов — первым делом смотри логи NPM, потом логи бэкенда. Порядок диагностики: NPM -> сеть -> бэкенд. Не наоборот.
Обновление NPM
Смотри на страницу релизов перед обновлением — иногда бывают breaking changes. Процедура простая:
# 1. Сделай бэкап перед обновлением
tar -czf /backup/npm-before-update-$(date +%Y%m%d).tar.gz ./data ./letsencrypt
# 2. Обновляй
docker compose pull && docker compose up -d
# 3. Проверь что всё работает
docker compose ps
docker logs nginx-proxy-manager --tail 50
Получение wildcard-сертификата через DNS-challenge
HTTP-challenge подходит для публичных серверов. Но что если сервер не смотрит в интернет, или хочешь один сертификат на все поддомены? Тогда DNS-challenge.
Wildcard *.domain.com покрывает все поддомены одним сертификатом. Настройка раз — работает для pve, cloud, vault, grafana и всего остального.
В NPM: SSL Certificates -> Add SSL Certificate -> Let’s Encrypt. Включи «Use a DNS Challenge». Выбери своего провайдера.
Для Cloudflare нужен API-токен:
- Зайди в Cloudflare Dashboard -> My Profile -> API Tokens
- Create Token -> Edit zone DNS
- Zone Resources: Include -> Specific zone -> твой домен
- Скопируй токен
В NPM вставляешь токен в поле Cloudflare API Token. Домен указываешь как *.domain.com. Нажимаешь Save — сертификат получится через 30-60 секунд.
Поддерживаемые провайдеры в NPM: Cloudflare, Namecheap, Route53, DigitalOcean, Linode, Godaddy и другие. Полный список — в документации Let’s Encrypt.
Access Lists: ограничение доступа
Access Lists в NPM — мощный инструмент. Можно ограничить доступ по IP, добавить Basic Auth или комбинировать оба способа.
Создаёшь Access List: Access Lists -> Add Access List.
Пример для Proxmox: только из локальной сети и VPN:
| Тип | Значение | Действие |
|---|---|---|
| IP | 192.168.1.0/24 | allow |
| IP | 10.8.0.0/24 | allow |
| IP | all | deny |
Потом привязываешь Access List к нужному Proxy Host на вкладке Details. Снаружи вместо сервиса увидят 403 Forbidden.
Если нужен Basic Auth поверх IP-фильтрации — добавляй учётные данные на вкладке Authorization в Access List. Два фактора защиты одновременно.
Настройка локального DNS для self-hosted стека
Всё что сделано выше работает для публичных доменов. Но можно и без интернета — только для внутренней сети.
Схема: используешь Pi-hole или AdGuard Home как локальный DNS. Добавляешь записи вида pve.home.lan -> 192.168.1.5. NPM принимает запросы на этом IP и проксирует на бэкенды. Сертификаты получаешь через DNS-challenge.
В Pi-hole добавляешь Local DNS Records: pve.home.lan -> 192.168.1.5. В AdGuard Home: Settings -> DNS rewrites.
Устройства в сети должны использовать Pi-hole/AdGuard как DNS-сервер. Обычно настраивается в DHCP на роутере.
Мониторинг и алерты
NPM не имеет встроенного мониторинга. Но логи есть — и это уже что-то.
# Следить за access-логами в реальном времени
docker exec nginx-proxy-manager tail -f /data/logs/proxy-host-1_access.log
# Смотреть ошибки 4xx и 5xx
docker exec nginx-proxy-manager grep -E " (4|5)[0-9][0-9] " /data/logs/proxy-host-1_access.log | tail -50
# Логи получения сертификатов
docker exec nginx-proxy-manager cat /data/logs/letsencrypt.log
# Статус сертификатов - когда истекают
docker exec nginx-proxy-manager find /etc/letsencrypt/live -name "fullchain.pem" -exec openssl x509 -in {} -noout -subject -dates \;
Если используешь Uptime Kuma или Grafana + Prometheus — подключай туда. Uptime Kuma умеет мониторить HTTP-эндпоинты и отправлять уведомления в Telegram.
Минимальный мониторинг: проверка что сертификаты не истекают и что каждый проксируемый сервис отвечает 200. Это можно сделать простым bash-скриптом в cron:
#!/bin/bash
# Проверка сроков SSL-сертификатов
DOMAINS="pve.yourdomain.com cloud.yourdomain.com vault.yourdomain.com"
for DOMAIN in $DOMAINS; do
EXPIRY=$(echo | openssl s_client -servername $DOMAIN -connect $DOMAIN:443 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
if [ $DAYS_LEFT -lt 14 ]; then
echo "WARN: $DOMAIN - сертификат истекает через $DAYS_LEFT дней"
else
echo "OK: $DOMAIN - $DAYS_LEFT дней до истечения"
fi
done
Итоговый чеклист после настройки
- Все сервисы открываются по HTTPS по своим доменам
- SSL-сертификаты действительны, Let’s Encrypt не показывает ошибок
- Консоли VM в Proxmox открываются без ошибок
- Загрузка файлов работает в Nextcloud и Zimbra
- Нет ошибок CSRF в MailCow и Zimbra
- Реальные IP пользователей отображаются в логах
- Nextcloud не показывает предупреждений в /settings/admin/overview
- Vaultwarden синхронизирует данные на мобильном клиенте
- Порт 81 (интерфейс NPM) закрыт от внешнего мира
- Административные интерфейсы защищены Access Lists
- Пароль администратора NPM сменён
- Бэкап ./data и ./letsencrypt настроен и проверен
NPM или Traefik: что выбрать
Вопрос регулярный — вот честный ответ.
NPM лучше если: у тебя домашняя лаборатория или небольшой self-hosted стек, нужен UI без погружения в YAML-конфиги, не пишешь код и не управляешь Kubernetes.
Traefik лучше если: управляешь кластером контейнеров, нужна автоматическая конфигурация через Docker Labels, важна интеграция с CI/CD.
Разница не в качестве, а в подходе. NPM — кликаешь мышкой. Traefik — пишешь конфиги. Оба делают одно и то же.
FAQ
Нужен ли белый IP для SSL-сертификата Let’s Encrypt?
Для HTTP-challenge — да, порт 80 должен быть доступен из интернета. Для DNS-challenge — нет. DNS-challenge поддерживает Cloudflare, Namecheap, Route53 и другие. Позволяет получать wildcard-сертификаты (*.domain.com) и работать на полностью изолированных серверах без публичного IP.
Как настроить NPM для сервисов без публичного доступа?
Используй локальный DNS через Pi-hole или AdGuard Home — пусть твои домены резолвятся только внутри сети. Сертификаты получай через DNS-challenge. Снаружи ничего не открываешь.
Почему Nextcloud продолжает генерировать http-ссылки после настройки NPM?
Не добавлен параметр overwriteprotocol => 'https' в config.php. Или NPM не в списке trusted_proxies — Nextcloud игнорирует заголовки X-Forwarded-Proto от недоверенных прокси.
Что делать если сертификат Let’s Encrypt не обновляется?
# Смотри ошибки
docker logs nginx-proxy-manager | grep -i "renew\|error\|acme"
# Порт 80 должен быть открыт
curl -v http://твой-домен.com
# NPM пытается обновить за 30 дней до истечения
# Можно принудить через UI: SSL Certificates -> три точки -> Renew
Как ограничить доступ к интерфейсу NPM (порт 81)?
Firewall на хосте — самый надёжный способ:
# UFW - разрешить только из локальной сети
ufw allow from 192.168.1.0/24 to any port 81
ufw deny 81
Или через Access List в самом NPM: Settings -> Default Site -> Require Auth.
Как проверить что WebSocket работает?
# Должен вернуть 101 Switching Protocols
curl -i \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" \
-H "Sec-WebSocket-Version: 13" \
https://твой-домен.com
# Если вернул 200 или 400 - WebSocket не работает
Прогноз
После настройки по этой статье у тебя: 10 сервисов за одним HTTPS-прокси, SSL получается и обновляется автоматически, все нужные заголовки передаются, консоли Proxmox открываются, Nextcloud не ругается на trusted_domains, Vaultwarden синхронизирует пароли между устройствами.
Следующий шаг — Authelia поверх NPM: единый вход с двухфактором для всего self-hosted стека. Один логин для Proxmox, Nextcloud, Grafana и всего остального. Разберём в отдельной статье.
Оставайтесь на связи
Рецепты от IT-боли. Без воды, без рекламы, без маркетинговой шелухи.
Подписаться на IT-Аптеку →



