Nginx Proxy Manager: настройка reverse proxy для Proxmox, Nextcloud, Vaultwarden и ещё семи сервисов

Готовые конфиги NPM для 10 self-hosted сервисов: Proxmox, Nextcloud, Vaultwarden, MailCow, Grafana. WebSocket, SSL, trusted_proxies - всё с объяснением зачем.
Коротко: что получишь
Nginx Proxy Manager (NPM) — это веб-интерфейс поверх Nginx для управления reverse proxy. Настраиваешь через браузер: указываешь домен, IP и порт бэкенда, SSL получается автоматически через Let’s Encrypt. В статье — готовые конфиги для 10 популярных self-hosted сервисов с объяснением, почему каждая строка там нужна.
Содержание: показать

Диагноз: почему это вообще нужно

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 минут.

Архитектура: как это работает

%%{init: {
  'theme': 'base',
  'themeVariables': {
    'primaryColor': '#ffffff',
    'primaryTextColor': '#1e293b',
    'primaryBorderColor': '#94a3b8',
    'lineColor': '#64748b',
    'fontSize': '15px',
    'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
  },
  'flowchart': {'curve': 'linear', 'nodeSpacing': 50, 'rankSpacing': 50}
}}%%
flowchart TD
    A["Браузер / клиент
https://pve.domain.com"] --> B["NPM
порты 80 / 443"]
    B --> C["Proxmox VE
192.168.1.10:8006 https"]
    B --> D["Nextcloud
nextcloud-app:80 http"]
    B --> E["Vaultwarden
vaultwarden:80 http"]
    B --> F["Grafana
grafana:3000 http"]
    G["Let's Encrypt"] --> B
    style A fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
    style B fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#92400e
    style C fill:#f8fafc,stroke:#94a3b8,stroke-width:1px,color:#1e293b
    style D fill:#f8fafc,stroke:#94a3b8,stroke-width:1px,color:#1e293b
    style E fill:#f8fafc,stroke:#94a3b8,stroke-width:1px,color:#1e293b
    style F fill:#f8fafc,stroke:#94a3b8,stroke-width:1px,color:#1e293b
    style G fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d

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.

Первое, что делаешь после входа
Сразу меняй email и пароль администратора. Логин 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

Без этих изменений Nextcloud будет генерировать http-ссылки и ругаться на домен
Открой config/config.php и добавь следующие параметры. Подсеть 172.20.0.0/16 — стандартная для Docker. Если у тебя другая — посмотри через docker network inspect proxy-network.

'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

Причина: приложение не видит реальный хост и протокол
Приложение генерирует CSRF-токен привязанный к хосту. Если NPM не передаёт X-Forwarded-Host и X-Forwarded-Port — приложение думает что запрос пришёл с другого хоста и отклоняет его.

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

Что бэкапить: директории ./data и ./letsencrypt
Вся конфигурация NPM хранится в примонтированных директориях. Сертификаты, настройки прокси-хостов, access lists — всё там. Бэкапируй раз в день, храни хотя бы 7 копий.

# Бэкап конфигурации и сертификатов
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

Простая профилактика:

  1. Раз в месяц проверяй сроки: SSL Certificates в интерфейсе NPM показывает дату истечения для каждого сертификата
  2. Docker Compose файлы держи в Git — при любом изменении видно что именно изменилось
  3. После любых правок файерволла проверяй что порт 80 доступен снаружи
  4. Бэкап перед любым обновлением 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-токен:

  1. Зайди в Cloudflare Dashboard -> My Profile -> API Tokens
  2. Create Token -> Edit zone DNS
  3. Zone Resources: Include -> Specific zone -> твой домен
  4. Скопируй токен

В NPM вставляешь токен в поле Cloudflare API Token. Домен указываешь как *.domain.com. Нажимаешь Save — сертификат получится через 30-60 секунд.

DNS-challenge и изолированные серверы
DNS-challenge не требует доступа к серверу снаружи. Let’s Encrypt проверяет запись TXT в DNS, которую NPM создаёт автоматически через API твоего DNS-провайдера. Работает за NAT, на серверах без публичного IP, во внутренних сетях.

Поддерживаемые провайдеры в 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.

%%{init: {
  'theme': 'base',
  'themeVariables': {
    'primaryColor': '#ffffff',
    'primaryTextColor': '#1e293b',
    'primaryBorderColor': '#94a3b8',
    'lineColor': '#64748b',
    'fontSize': '15px',
    'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
  },
  'flowchart': {'curve': 'linear', 'nodeSpacing': 50, 'rankSpacing': 50}
}}%%
flowchart TD
    A["Браузер
pve.home.lan"] --> B["Pi-hole / AdGuard
локальный DNS"]
    B --> C["Отвечает IP сервера с NPM
192.168.1.5"]
    C --> D["NPM
443/80"]
    D --> E["Proxmox VE
192.168.1.10:8006"]
    style A fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
    style B fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
    style C fill:#f8fafc,stroke:#94a3b8,stroke-width:1px,color:#1e293b
    style D fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#92400e
    style E fill:#f8fafc,stroke:#94a3b8,stroke-width:1px,color:#1e293b

В 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-Аптеку →

Мы ВКонтакте

IT-Аптека — советы, новости и помощь рядом.

Вступить в группу ВКонтакте →
Поделитесь:

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх