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
Усунення несправностей + 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 для резервних копій і моніторингу.