Nginx Proxy Manager: SSL и домены для всех контейнеров на одном VPS
TL;DR
Nginx Proxy Manager позволяет разместить десятки Docker-контейнеров на одном VPS, дать каждому отдельный домен или поддомен и автоматически выпускать SSL-сертификаты Let’s Encrypt без ручной настройки Nginx.
- Один публичный IP-адрес обслуживает несколько сайтов, API, панелей управления и self-hosted-сервисов.
- Nginx Proxy Manager принимает HTTP/HTTPS-трафик на портах 80 и 443 и передаёт его нужному контейнеру во внутренней Docker-сети.
- SSL-сертификаты Let’s Encrypt выпускаются и продлеваются автоматически через веб-интерфейс.
- Админ-панель Nginx Proxy Manager нельзя оставлять открытой всему интернету без дополнительной защиты.
- Для небольшого набора сервисов обычно достаточно VPS с 2 vCPU, 2–4 ГБ RAM и SSD-диском от 30 ГБ.
- Критичные данные — Docker Compose-файлы, тома Nginx Proxy Manager, база SQLite или MariaDB и резервные копии приложений — нужно регулярно выгружать за пределы VPS.
Что мы настраиваем и зачем
Типичный VPS быстро превращается в набор сервисов: GitLab или Gitea, Mattermost, Vaultwarden, Nextcloud, Grafana, домашняя страница, API приложения, тестовые стенды и панели мониторинга. Каждый контейнер обычно слушает свой внутренний порт: 3000, 8080, 9000, 5000 или другой. Открывать все эти порты в интернет неудобно и небезопасно.
Nginx Proxy Manager, далее NPM, решает эту задачу как reverse proxy с веб-интерфейсом. Он принимает запросы на стандартных портах HTTP 80 и HTTPS 443, определяет домен из заголовка Host и перенаправляет трафик к нужному контейнеру. Например, запрос к git.example.com отправляется в Gitea, chat.example.com — в Mattermost, а status.example.com — в Uptime Kuma.
В результате внешнему пользователю неважно, на каком порту реально работает приложение. Все сервисы доступны по понятным HTTPS-адресам, а на VPS достаточно открыть только SSH, HTTP и HTTPS. Внутренние порты приложений остаются закрытыми Docker-сетью.
Итоговая схема
В этом руководстве будет использоваться следующая архитектура:
- Один VPS с Ubuntu Server 24.04 LTS.
- Docker Engine и Docker Compose Plugin.
- Nginx Proxy Manager в отдельном Docker Compose-проекте.
- Общая внешняя Docker-сеть
proxy. - Несколько приложений, подключённых к этой сети.
- DNS-записи доменов типа
app.example.com, указывающие на IP VPS. - Let’s Encrypt для бесплатных TLS-сертификатов.
Поток запроса будет выглядеть так: браузер открывает https://vault.example.com → DNS возвращает IP VPS → NPM принимает запрос на порту 443 → NPM завершает TLS → NPM передаёт HTTP во внутренний контейнер Vaultwarden по Docker-сети.
Что вы получите после настройки
- Единую точку входа для всех контейнеров на сервере.
- HTTPS для каждого домена без ручного создания конфигураций Nginx.
- Автоматическое продление сертификатов Let’s Encrypt.
- Возможность включать WebSocket, access list, базовую авторизацию и перенаправления через UI.
- Изоляцию сервисов: приложения не обязаны публиковать порты на IP VPS.
- Более простой перенос инфраструктуры: конфигурация хранится в Compose-файлах и Docker volumes.
Какие есть альтернативы
| Подход | Плюсы | Ограничения |
|---|---|---|
| Cloud-managed ingress или load balancer | Высокая доступность, управляемые сертификаты, меньше администрирования | Ежемесячная стоимость, зависимость от облака, часто избыточно для одного VPS |
| Обычный Nginx вручную | Максимальная гибкость, привычные конфиги, высокая производительность | Нужно самостоятельно писать virtual host-конфиги, настраивать Certbot и обновления |
| Traefik | Удобная автоматизация через Docker labels, хорош для большого числа сервисов | Конфигурация через labels и middleware может быть сложнее для новичка |
| Caddy | Простая конфигурация, автоматический HTTPS, компактный Caddyfile | Меньше визуального управления, часть задач требует ручной работы с конфигом |
| Nginx Proxy Manager | Понятный UI, Let’s Encrypt, access lists, proxy hosts без ручного Nginx | Дополнительная панель управления, необходимо защищать административный доступ |
Для solo-разработчика, маленькой команды или владельца одного VPS NPM удобен тем, что не требует держать в голове синтаксис Nginx при добавлении очередного сервиса. Он не отменяет необходимость понимать сети, DNS и безопасность, но значительно снижает число ручных операций.
Nginx Proxy Manager не заменяет firewall. Даже если сервис доступен только через прокси, открытые Docker-порты могут обойти правила UFW. Публикуйте порты приложений только на
127.0.0.1или не публикуйте вовсе.
Какой VPS-конфиг нужен под эту задачу
Сам Nginx Proxy Manager потребляет немного ресурсов. На чистом сервере его контейнер обычно укладывается в сотни мегабайт RAM, а TLS-операции заметно нагружают CPU только при большом количестве новых соединений. Ресурсы нужно выбирать прежде всего под приложения за прокси: GitLab, Nextcloud, базы данных, игровые серверы и CI намного тяжелее самого NPM.
| Сценарий | CPU | RAM | Диск | Сеть |
|---|---|---|---|---|
| NPM + 2–5 лёгких сервисов | 2 vCPU | 2 ГБ | 30 ГБ SSD | 100 Мбит/с |
| NPM + Nextcloud, Gitea, Mattermost, мониторинг | 2–4 vCPU | 4–8 ГБ | 80–160 ГБ NVMe | 100 Мбит/с или 1 Гбит/с |
| Много пользователей, файлы, CI, базы данных | 4–8 vCPU | 8–16 ГБ | 160 ГБ+ NVMe | 1 Гбит/с |
Практичный стартовый вариант для NPM, Vaultwarden, Uptime Kuma, Gitea и небольшого API — 2 vCPU, 4 ГБ RAM, 60–80 ГБ NVMe и 1 Гбит/с. При необходимости можно взять VPS с указанными характеристиками или аналогичный тариф у другого провайдера. Важнее названия тарифа наличие выделенного публичного IPv4, стабильной сети, доступа по SSH и возможности делать snapshots.
Почему нужен публичный IP
Для стандартной HTTP-01 проверки Let’s Encrypt сервер должен быть доступен извне на порту 80. Если VPS находится за CGNAT или имеет только частный IP, автоматический выпуск сертификата через обычную HTTP-проверку не сработает. В таком случае нужен публичный IPv4, публичный IPv6 с корректным DNS или DNS-01 challenge через DNS-провайдера.
Когда нужен dedicated, а не VPS
Для одного reverse proxy dedicated-сервер не нужен. Он становится оправданным, когда за прокси работают тяжёлые сервисы: крупный GitLab с CI-runner, файловое облако с терабайтами данных, транскодирование видео, высоконагруженная база данных, десятки тысяч запросов в секунду или требования к предсказуемой производительности диска.
Также dedicated стоит рассматривать при высоком сетевом трафике, большом количестве одновременных TLS-соединений, требованиях к нескольким физическим дискам в RAID и когда виртуализация соседей на VPS недопустима по политике безопасности. Для 5–30 внутренних сервисов небольшой команды обычно достаточно качественного VPS.
Как выбрать локацию
Локация VPS влияет на задержку, требования законодательства и скорость первоначальной загрузки. Если основная аудитория находится в Европе, выбирайте европейский дата-центр; если пользователи в Казахстане, России, Турции или на Ближнем Востоке, предварительно измерьте ping до нескольких локаций. Для админ-панелей разница между 20 и 60 мс почти незаметна, но для игр, голосовых сервисов, API и файлового хранилища она важнее.
Не размещайте сервер только исходя из близости к себе, если сервисом пользуется команда из другой страны. Проверьте также, разрешены ли нужные порты, есть ли reverse DNS при необходимости почтовых сервисов и доступны ли резервные хранилища в другой локации.
Подготовка сервера
Ниже предполагается свежий сервер с Ubuntu Server 24.04 LTS и входом под пользователем root. Если у вас Debian 12 или Debian 13, логика остаётся прежней, но отдельные команды установки пакетов могут отличаться. Перед настройкой доменов создайте A-записи: например, npm.example.com, vault.example.com и status.example.com должны указывать на публичный IPv4 сервера.
Обновите систему и создайте администратора
Выполняйте начальную настройку через SSH. Вместо admin укажите собственное имя пользователя, а вместо примера публичного ключа вставьте ключ из файла ~/.ssh/id_ed25519.pub на локальном компьютере.
apt update && apt upgrade -y
apt install -y sudo curl ca-certificates gnupg fail2ban ufw vim git
adduser admin
usermod -aG sudo admin
Команда обновляет систему, устанавливает базовые утилиты, Fail2ban и UFW, затем создаёт пользователя с правами sudo.
install -d -m 700 -o admin -g admin /home/admin/.ssh
nano /home/admin/.ssh/authorized_keys
chmod 600 /home/admin/.ssh/authorized_keys
chown admin:admin /home/admin/.ssh/authorized_keys
Этот блок создаёт каталог SSH и добавляет публичный ключ. Вставьте ключ одной строкой, сохраните файл и не закрывайте текущую root-сессию до проверки нового входа.
ssh -i ~/.ssh/id_ed25519 admin@SERVER_IP
Команда проверяет, что новый пользователь действительно может подключиться по ключу. Откройте отдельное окно терминала и убедитесь, что вход выполняется без пароля.
Отключите парольный вход SSH
После успешной проверки отредактируйте отдельный файл конфигурации SSH. Это безопаснее, чем менять основной файл /etc/ssh/sshd_config, который может обновляться пакетным менеджером.
sudo nano /etc/ssh/sshd_config.d/99-hardening.conf
Добавьте следующие параметры:
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
X11Forwarding no
MaxAuthTries 3
sudo sshd -t && sudo systemctl restart ssh
Команда проверяет синтаксис конфигурации и только затем перезапускает SSH. Если проверка вернула ошибку, не перезапускайте сервис, пока не исправите файл.
Настройте firewall и Fail2ban
NPM должен получать внешние подключения на 80 и 443. SSH желательно ограничить IP-адресом офиса или домашнего VPN, но для первого запуска можно временно оставить доступ с любого IP. Docker имеет особенности взаимодействия с UFW, поэтому главная защита контейнеров — не публиковать их порты наружу.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
Команды запрещают входящий трафик по умолчанию и разрешают только SSH, HTTP и HTTPS. Позднее можно добавить ограничение SSH по исходному IP или перенести доступ к панели NPM за VPN.
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
Этот блок запускает Fail2ban и показывает состояние jail для SSH. Fail2ban уменьшает эффект перебора паролей, но не заменяет ключи SSH, обновления и ограничение доступа.
Проверьте DNS до выпуска сертификата
Сертификат Let’s Encrypt не выдастся, пока DNS-запись не указывает на текущий сервер. Проверяйте запись с VPS и, желательно, через внешние DNS-резолверы.
getent hosts npm.example.com
curl -4 ifconfig.me
dig +short npm.example.com A @1.1.1.1
IP, который возвращает DNS, должен совпадать с публичным IPv4 VPS. Если DNS ещё распространяется, подождите TTL записи. Не создавайте десятки неудачных запросов сертификата: Let’s Encrypt применяет rate limits.
Установка ПО — пошагово
Для Nginx Proxy Manager используйте Docker Engine из официального репозитория Docker, а не устаревший пакет docker.io из стандартного репозитория Ubuntu. На 2026 год актуальная ветка Docker Engine 29 поддерживает Compose Plugin; конкретный номер минорной версии проверяйте перед установкой в официальной документации Docker.
Установите Docker Engine и Compose Plugin
sudo apt remove -y docker.io docker-compose docker-compose-v2 podman-docker containerd runc || true
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
Команды удаляют конфликтующие старые пакеты и добавляют GPG-ключ официального Docker-репозитория.
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Этот блок добавляет репозиторий и устанавливает Docker Engine, containerd, Buildx и современный Compose Plugin.
sudo usermod -aG docker admin
newgrp docker
docker version
docker compose version
Команда добавляет пользователя в группу Docker и выводит версии Docker и Compose. Учтите, что членство в группе docker фактически даёт привилегии уровня root. Не добавляйте в неё обычных пользователей приложений.
Создайте общую Docker-сеть
Контейнеры, к которым NPM должен обращаться по внутреннему DNS Docker, будут подключаться к одной внешней сети. Это лучше, чем пробрасывать порты каждого приложения на публичный интерфейс сервера.
docker network create proxy
docker network inspect proxy
Команды создают bridge-сеть proxy и показывают её параметры. В будущем NPM сможет обращаться к контейнеру по имени сервиса, например vaultwarden или uptime-kuma.
Создайте каталог Nginx Proxy Manager
mkdir -p ~/stacks/nginx-proxy-manager
cd ~/stacks/nginx-proxy-manager
mkdir -p data letsencrypt
chmod 700 data letsencrypt
Этот блок создаёт рабочий каталог и постоянные директории. В data NPM хранит конфигурацию и SQLite-базу, а в letsencrypt — сертификаты и данные ACME.
Подготовьте Compose-файл
Для небольшого VPS можно использовать встроенную SQLite-базу. Это минимизирует число контейнеров и достаточно для одного экземпляра NPM. В крупных установках и при требованиях к резервированию обычно используют MariaDB, но для одного reverse proxy SQLite проще в обслуживании.
nano compose.yml
Вставьте конфигурацию с закреплённым образом NPM ветки 2.12.3. Перед новым развёртыванием проверяйте GitHub Releases проекта и changelog: не заменяйте стабильный тег на latest без тестирования.
services:
npm:
image: jc21/nginx-proxy-manager:2.12.3
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "127.0.0.1:81:81"
environment:
TZ: "Europe/Berlin"
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
networks:
- proxy
networks:
proxy:
external: true
Порты 80 и 443 опубликованы для всего интернета, а административный порт 81 привязан только к 127.0.0.1. Это важный безопасный вариант: панель не будет доступна напрямую по http://SERVER_IP:81.
docker compose pull
docker compose up -d
docker compose ps
docker logs --tail=100 nginx-proxy-manager
Команды загружают образ, запускают стек в фоне, показывают состояние контейнера и последние логи. Статус должен быть Up. Если контейнер перезапускается, проверьте логи целиком.
Откройте административную панель через SSH-туннель
Так как порт 81 доступен только локально на VPS, создайте туннель с рабочего компьютера. Команда выполняется на вашем локальном компьютере, а не на сервере.
ssh -L 8181:127.0.0.1:81 admin@SERVER_IP
Пока SSH-сессия открыта, перейдите в браузере по адресу http://127.0.0.1:8181. Для первого входа используйте стандартные данные NPM: email [email protected], пароль changeme. Сразу после входа интерфейс предложит сменить email и пароль.
Не публикуйте порт 81 через
81:81, если нет строгой необходимости. Даже с сильным паролем админ-панель не должна быть лишней публичной поверхностью атаки.
Конфигурация
Теперь настроим пример с двумя приложениями: Vaultwarden и Uptime Kuma. Они будут работать без внешних опубликованных портов. NPM увидит их через общую сеть proxy, а пользователи будут обращаться к ним через домены vault.example.com и status.example.com.
Разверните тестовые контейнеры
Создайте отдельный Compose-проект. Секреты не храните в исходном коде и не публикуйте в Git. Для Vaultwarden используйте файл .env с правами только для владельца.
mkdir -p ~/stacks/apps
cd ~/stacks/apps
nano .env
chmod 600 .env
В файл .env добавьте длинный случайный пароль. Его можно сгенерировать командой openssl rand -base64 48.
VAULTWARDEN_ADMIN_TOKEN=replace_with_a_long_random_secret
TZ=Europe/Berlin
nano compose.yml
services:
vaultwarden:
image: vaultwarden/server:1.34.3
container_name: vaultwarden
restart: unless-stopped
environment:
DOMAIN: "https://vault.example.com"
WEBSOCKET_ENABLED: "true"
ADMIN_TOKEN: "${VAULTWARDEN_ADMIN_TOKEN}"
TZ: "${TZ}"
SIGNUPS_ALLOWED: "false"
volumes:
- ./vaultwarden-data:/data
networks:
- proxy
uptime-kuma:
image: louislam/uptime-kuma:1.23.16
container_name: uptime-kuma
restart: unless-stopped
volumes:
- ./uptime-kuma-data:/app/data
networks:
- proxy
networks:
proxy:
external: true
В этом примере нет секции ports. Контейнеры доступны только соседям по сети proxy. Если приложению нужен доступ к базе данных, базу тоже подключайте к отдельной внутренней сети и не публикуйте её порт 3306, 5432 или 6379 наружу.
docker compose up -d
docker compose ps
docker network inspect proxy
Команды запускают приложения и проверяют, что контейнеры подключены к сети. В выводе docker network inspect proxy должны появиться nginx-proxy-manager, vaultwarden и uptime-kuma.
Проверьте внутреннюю связность
До создания proxy host убедитесь, что NPM видит целевые контейнеры. Внутри контейнера NPM можно выполнить HTTP-запрос по Docker DNS-имени. Vaultwarden по умолчанию использует порт 80, Uptime Kuma — 3001.
docker exec nginx-proxy-manager curl -I http://vaultwarden:80
docker exec nginx-proxy-manager curl -I http://uptime-kuma:3001
Для Vaultwarden ожидается HTTP-ответ вроде 200, 301 или 302. Для Uptime Kuma также допустим ответ перенаправления. Ошибка Could not resolve host означает, что контейнер не подключён к сети proxy.
Создайте Proxy Host для Vaultwarden
Откройте SSH-туннель к панели, перейдите в Hosts → Proxy Hosts → Add Proxy Host и заполните поля:
- Domain Names:
vault.example.com - Scheme:
http - Forward Hostname / IP:
vaultwarden - Forward Port:
80 - Cache Assets: выключено для первого запуска
- Block Common Exploits: включено
- Websockets Support: включено
На вкладке SSL выберите Request a new SSL Certificate, укажите email для Let’s Encrypt, включите Force SSL, HTTP/2 Support и согласитесь с условиями Let’s Encrypt. Затем сохраните запись.
Для HTTP-01 challenge домен должен вести на VPS, а входящий порт 80 должен быть доступен. NPM сам использует встроенную ACME-логику, аналогичную Certbot: вручную ставить Certbot или Caddy поверх NPM не нужно и нельзя, потому что они будут конкурировать за порты 80 и 443.
Создайте Proxy Host для Uptime Kuma
Добавьте второй host аналогично, но с другими значениями:
- Domain Names:
status.example.com - Scheme:
http - Forward Hostname / IP:
uptime-kuma - Forward Port:
3001 - Websockets Support: включено
- Block Common Exploits: включено
На вкладке SSL снова запросите отдельный сертификат. Можно выпустить один сертификат на несколько доменов, но отдельные сертификаты проще обслуживать и безопаснее с точки зрения изоляции. Wildcard-сертификат потребует DNS-01 challenge и API DNS-провайдера.
Настройте access list для внутренних панелей
Не все сервисы должны быть доступны всем пользователям. Например, админ-панель, Grafana, Portainer или тестовое API лучше закрыть Basic Auth и, если возможно, списком разрешённых IP. В NPM перейдите в Access Lists → Add Access List, создайте список admins-only, добавьте пользователя с длинным паролем и на вкладке Access укажите разрешённые CIDR-сети.
После этого выберите созданный список в настройках нужного Proxy Host. Basic Auth — дополнительный слой, а не замена аутентификации самого приложения. Для критичных панелей лучше использовать WireGuard, Tailscale или SSH-туннель.
Дополнительные параметры proxy host
Некоторые приложения требуют передачу реального IP клиента, увеличенный лимит загрузки или длинные таймауты. NPM уже добавляет стандартные proxy headers, но в поле Advanced можно добавить безопасные директивы для конкретного host:
client_max_body_size 2g;
proxy_connect_timeout 60s;
proxy_send_timeout 3600s;
proxy_read_timeout 3600s;
send_timeout 3600s;
Такой пример нужен для загрузки больших файлов в Nextcloud или аналогичный сервис. Не вставляйте случайные директивы из форумов в глобальную конфигурацию: ошибка синтаксиса может сломать генерацию конфигов Nginx для всех доменов.
Проверьте HTTPS и сертификат
curl -I https://vault.example.com
curl -I https://status.example.com
curl -Iv https://vault.example.com 2>&1 | grep -E "SSL certificate verify|subject:|issuer:"
Ответ должен содержать успешный HTTP-код и не должен выдавать ошибку проверки TLS. Если используется Cloudflare в режиме проксирования, для первоначальной диагностики временно отключите proxy-режим записи или убедитесь, что его SSL-режим не конфликтует с origin-сертификатом.
docker logs --tail=100 nginx-proxy-manager
docker exec nginx-proxy-manager nginx -t
Эти команды показывают логи NPM и проверяют синтаксис сгенерированной Nginx-конфигурации. Выполняйте их после изменения сложных advanced-настроек или при ошибках 502/503.
Бэкапы и обслуживание
Nginx Proxy Manager можно быстро пересоздать, но без резервной копии вы потеряете proxy hosts, access lists, пользователей, сертификаты и настройки. Кроме NPM необходимо отдельно резервировать данные приложений: каталоги Nextcloud, Vaultwarden, Git-репозитории, базы PostgreSQL и MariaDB, файлы Mattermost и конфигурации Compose.
Что нужно включить в резервную копию
| Объект | Где находится | Зачем нужен |
|---|---|---|
| NPM data | ~/stacks/nginx-proxy-manager/data |
SQLite-база, proxy hosts, пользователи, настройки |
| Let’s Encrypt | ~/stacks/nginx-proxy-manager/letsencrypt |
Сертификаты, аккаунт ACME, ключи |
| Compose-файлы и .env | ~/stacks |
Воспроизводимое развёртывание и секреты |
| Данные приложений | bind mounts или named volumes | Пользовательские файлы, базы, конфигурация |
| Дампы БД | отдельный каталог backup | Консистентное восстановление PostgreSQL/MariaDB |
Не считайте snapshot VPS полноценным бэкапом. Snapshot полезен перед обновлением, но если он хранится в том же аккаунте или дата-центре, он не защищает от удаления аккаунта, компрометации учётной записи или аварии у провайдера. Используйте правило 3-2-1: минимум три копии, на двух типах хранилища, одна вне основного сервера.
Простой бэкап с Restic в S3
Restic поддерживает шифрование на стороне клиента и S3-совместимые хранилища. Создайте отдельный bucket, отдельного пользователя с доступом только к этому bucket и сохраните ключи в закрытом файле.
sudo apt install -y restic
sudo install -d -m 700 /root/.config/restic
sudo nano /root/.config/restic/env
sudo chmod 600 /root/.config/restic/env
Добавьте в файл переменные доступа. Значения замените данными вашего S3-хранилища.
export RESTIC_REPOSITORY="s3:https://s3.example-storage.net/server-backups/npm-vps"
export RESTIC_PASSWORD="use_a_long_unique_backup_password"
export AWS_ACCESS_KEY_ID="YOUR_ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="YOUR_SECRET_KEY"
sudo bash -c 'source /root/.config/restic/env && restic init'
Команда создаёт новый зашифрованный репозиторий. Пароль RESTIC_PASSWORD храните отдельно от VPS: в password manager или офлайн-хранилище. Потеря этого пароля делает резервные копии невосстановимыми.
sudo nano /usr/local/sbin/backup-stacks.sh
sudo chmod 700 /usr/local/sbin/backup-stacks.sh
Создайте скрипт:
#!/usr/bin/env bash
set -euo pipefail
source /root/.config/restic/env
STAMP=$(date +%F)
BACKUP_DIR="/root/backup-work/$STAMP"
mkdir -p "$BACKUP_DIR"
docker exec vaultwarden /bin/sh -c 'sqlite3 /data/db.sqlite3 ".backup /data/db-backup.sqlite3"' || true
restic backup /home/admin/stacks \
--exclude='/node_modules' \
--exclude='/cache' \
--tag docker-stacks
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
restic check
Скрипт сохраняет каталоги стеков, удаляет старые снимки по политике хранения и проверяет репозиторий. Для приложений с PostgreSQL или MariaDB перед бэкапом добавьте pg_dump или mariadb-dump; копирование файлов активной базы без дампа может дать повреждённую или логически неконсистентную копию.
sudo crontab -e
Добавьте ежедневный запуск ночью:
15 3 * /usr/local/sbin/backup-stacks.sh >> /var/log/backup-stacks.log 2>&1
После настройки обязательно выполните восстановление в тестовый каталог. Бэкап считается рабочим только после успешной проверки восстановления.
sudo bash -c 'source /root/.config/restic/env && restic snapshots'
sudo mkdir -p /root/restore-test
sudo bash -c 'source /root/.config/restic/env && restic restore latest --target /root/restore-test'
Обновления NPM и контейнеров
Для одного VPS оптимальна модель maintenance window: выберите период, например раз в две недели, сделайте бэкап, прочитайте release notes и обновите один стек за раз. Rolling update полезен в кластере с несколькими репликами, но на одиночном сервере обычно не даёт высокой доступности: перезапуск NPM на 10–30 секунд всё равно кратко оборвёт соединения.
cd ~/stacks/nginx-proxy-manager
docker compose pull
docker compose up -d
docker image prune -f
docker compose ps
Команды скачивают образ, пересоздают контейнер при изменении версии, удаляют неиспользуемые dangling-образы и показывают итоговый статус. Сначала фиксируйте версию образа в compose.yml, затем обновляйте её осознанно. Не запускайте бездумно docker system prune -a --volumes: команда способна удалить данные, которые кажутся Docker неиспользуемыми.
Раз в месяц проверяйте обновления ОС, срок сертификатов, размер логов и свободное место:
sudo apt update && sudo apt upgrade -y
df -h
docker system df
openssl s_client -connect vault.example.com:443 -servername vault.example.com < /dev/null 2>/dev/null | openssl x509 -noout -dates
Troubleshooting + FAQ
Почему при выпуске сертификата Let’s Encrypt появляется Internal Error или timeout?
Сначала проверьте DNS: домен должен возвращать IP именно этого VPS. Затем убедитесь, что порт 80 открыт в UFW, в панели провайдера и не занят другим контейнером или Nginx на хосте. Выполните sudo ss -lntp | grep -E ":80|:443" и проверьте, что порты слушает Docker. Если используется CDN-прокси, временно отключите его для диагностики. Также проверьте docker logs nginx-proxy-manager и не превышайте лимиты Let’s Encrypt повторными попытками.
Почему Nginx Proxy Manager возвращает 502 Bad Gateway?
Ошибка 502 почти всегда означает, что NPM не может подключиться к upstream-контейнеру. Проверьте имя контейнера, внутренний порт и Docker-сеть. Выполните docker exec nginx-proxy-manager curl -I http://SERVICE:PORT. Если имя не резолвится, оба контейнера не находятся в сети proxy. Если соединение отклонено, приложение слушает другой порт или не запустилось. Не указывайте в поле Forward Hostname публичный домен самого VPS: это может создать петлю прокси.
Почему домен открывается по HTTP, но не перенаправляется на HTTPS?
Откройте настройки соответствующего Proxy Host и убедитесь, что сертификат выбран, а переключатель Force SSL включён. Если сертификат не был выпущен, NPM не сможет безопасно перенаправить на HTTPS. Проверьте, не используется ли отдельный DNS-запись IPv6 AAAA, ведущая на другой сервер: браузер может подключаться по IPv6, а вы проверяете IPv4. Команда dig A domain и dig AAAA domain помогает быстро увидеть расхождения.
Какой VPS-конфиг минимально подойдёт?
Для одного Nginx Proxy Manager и нескольких лёгких контейнеров минимально разумный вариант — 2 vCPU, 2 ГБ RAM, 30 ГБ SSD и публичный IPv4. Один vCPU и 1 ГБ RAM могут работать для теста, но обновления, TLS-операции и соседние сервисы быстро упрутся в память. Для Vaultwarden, Uptime Kuma, небольшой Gitea и reverse proxy лучше сразу выбрать 2 vCPU, 4 ГБ RAM и 60 ГБ NVMe.
Что выбрать — VPS или dedicated для этой задачи?
Для Nginx Proxy Manager, личных сервисов и небольшой команды выбирайте VPS: он дешевле, быстрее разворачивается и обычно легко масштабируется по CPU, RAM и диску. Dedicated оправдан не из-за прокси, а из-за нагрузки приложений за ним: большого GitLab, тяжёлой базы данных, терабайтового файлового хранилища, CI, видеообработки или очень высокого трафика. Если сервер обслуживает менее нескольких десятков активных пользователей, VPS чаще всего достаточно.
Почему приложение работает по IP и порту, но не работает через домен?
Проверьте настройку Proxy Host: правильный scheme, имя контейнера и порт. Многие приложения генерируют ссылки, cookies и redirect URL на основе переменных вроде DOMAIN, URL, ROOT_URL или PUBLIC_URL. Укажите там внешний HTTPS-домен, а не http://localhost:PORT. Для сервисов с WebSocket включите Websockets Support. После изменения переменных окружения пересоздайте контейнер через docker compose up -d.
Нужно ли открывать порты приложений, например 3000, 8080 или 3001, в UFW?
Нет, если NPM и приложение подключены к одной Docker-сети. Не добавляйте правила UFW для внутренних портов и не публикуйте их через ports. NPM обращается к сервису по имени контейнера внутри сети Docker. Исключение — отладка, но даже тогда безопаснее временно привязать порт к 127.0.0.1, например 127.0.0.1:3001:3001, и подключаться через SSH-туннель.
Как безопасно открыть админ-панель NPM?
Лучший простой вариант — оставить порт 81 привязанным к 127.0.0.1 и использовать SSH-туннель. Для постоянного доступа можно создать отдельный proxy host с HTTPS, access list и дополнительной аутентификацией, но это увеличивает внешнюю поверхность атаки. Более надёжный подход — доступ к панели только через WireGuard или Tailscale. Всегда меняйте стандартные учётные данные, используйте уникальный пароль и регулярно обновляйте контейнер.
Выводы и следующие шаги
Теперь один VPS может обслуживать несколько Docker-приложений через отдельные домены с автоматическим HTTPS. Nginx Proxy Manager скрывает внутренние порты контейнеров, централизует TLS-сертификаты и упрощает добавление новых сервисов.
- Добавьте мониторинг доступности доменов через Uptime Kuma и настройте уведомления в Telegram, email или Mattermost.
- Перенесите административные интерфейсы за WireGuard или Tailscale, а публичными оставьте только сервисы, предназначенные для пользователей.
- Настройте регулярную проверку восстановления резервных копий и документируйте домены, контейнеры, порты и секреты в закрытом репозитории или password manager.
Когда число сервисов вырастет, разделите приложения по Compose-проектам, ограничьте ресурсы контейнеров и рассмотрите отдельную базу данных или второй VPS для резервных копий и мониторинга.