Как установить reverse-proxy Nginx Proxy Manager на VDS

Как установить reverse-proxy Nginx Proxy Manager на VDS

При разработке веб-проектов неизбежно поднимается вопрос их размещения в интернете. Разворачивать отдельную виртуальную машину для каждого маленького сервиса иногда избыточно и экономически нецелесообразно. 

Давайте пристальнее взглянем на проблему размещения множества веб-сервисов на одном виртуальном сервере и познакомимся с элегантным решением — Nginx Proxy Manager.

Проблемы развертывания множества проектов 

Развертывание нескольких независимых веб-сервисов на одном сервере может привести к проблемам: от незначительных неудобств до существенных уязвимостей. Оценим наиболее вероятные риски.

Размещение на привилегированных портах

Протокол HTTP использует порт 80, а HTTPS — 443. Это значит, что для доступа по адресу https://example.com сервис должен слушать подключения на порту 443. Проблема в том, что порты от 1 до 1023 считаются привилегированными, то есть прослушивать их могут только приложения с правами суперпользователя. 

Работа прототипа с ничем не ограниченными правами — это явная уязвимость, так как ПО, находящееся в разработке, может некорректно обрабатывать входящие запросы и дать злоумышленнику максимальный уровень доступа ко всей операционной системе. 

Необходимость запуска приложений от имени root в какой‑то степени может быть устранена с помощью Linux capabilities(7), в частности, флагом CAP_NET_BIND_SERVICE, который позволяет слушать привилегированные порты без максимальных прав. Также можно сделать перенаправление через iptables или предусмотреть в приложении «отказ» от прав суперпользователя после начала прослушивания порта.

Однако это лишь полумеры, потому что в операционной системе есть еще одно ограничение.

Один порт — один слушатель

В операционных системах есть фундаментальное ограничение: один сетевой порт в каждый момент времени может слушать только одно программа. Если запустить dev-версию фронтенда приложения на порту 3000 (npm run dev), то бэкэнд не сможет слушать подключения на том же порту: он получит ошибку Address already in use.

Решение простое: вынести разные приложения на разные порты. Например, фронтенд оставить на example.com:3000, а бэкэнд перевести на example.com:3001. Однако такое разделение создает целый ряд задач, которые придется решить.

  1. В браузерах есть механизм безопасности CORS (Cross-Origin Resource Sharing), который запрещает обращения к другим доменам. То есть, если dev-сборка фронтенда на http://example.com:3000 будет обращаться к http://example.com:3001/api, то браузер расценит ее как другой источник и потребует от бэкэнда подтвердить, что с example.com:3000 разрешено делать запросы.
  2. Если проектов больше одного, то необходимо следить, какие порты за каким приложением закреплены и актуализировать настройки брандмауэра. Более того, явное указание порта выглядит странно и может отпугивать обычных пользователей.

Кажется, что домены могут решить проблему, но это не так.

Сервер и домены

Домены — это человекочитаемые «псевдонимы» для адресов в интернете. Человеку проще запомнить example.com, чем 198.51.100.67. Кажется, что можно завести два домена, example.com и test.example.com, сопоставить их с адресом 198.51.100.67 и надеяться, что сервер как-нибудь разберется. 

На самом деле сервер не знает о доменах. Браузер сперва обращается к DNS-серверам с вопросом: «Куда ведет example.com?» — и получает ответ: «На 198.51.100.67». После этого браузер делает запрос напрямую к IP-адресу 198.51.100.67. Домен, к которому был сделан запрос, будет виден только приложению, которое возьмется его обрабатывать. Важно: такое приложение может быть только одно. 

Безопасное подключение

Не стоит также забывать, что HTTP — это текстовый протокол без шифрования. Любые данные, отправленные с его помощью через интернет, будут видны всем промежуточным узлам в сети. Более того, любой из этих узлов может подменить данные. 

Такая незащищенность передачи решается использованием безопасной версией протокола — HTTPS. Большинство современных веб-фреймворков его поддерживают. Однако создание и обновление сертификатов для HTTPS требует доказательства владения веб-сервером, что еще раз усложняет администрирование.

К счастью, для всех проблем есть решение — обратный прокси.

Что такое обратный прокси

Обратный прокси (reverse proxy) — это сервер-посредник, который:

  • принимает входящие запросы от пользователей из интернета;
  • перенаправляет их на внутренние серверы или приложения;
  • занимает «популярные» порты 80 и 443;
  • обрабатывает все входящие соединения и маршрутизирует их согласно правилам в файле конфигурации.

Например, на сервере запущена dev-сборка фронтенда по адресу 127.0.0.1:3000, а бэкэнд работает по адресу 127.0.0.1:8000. Обратный прокси слушает подключения по адресу 198.51.100.67. Тогда для него можно написать такие правила:

  • для домена example.com запросы по пути, который начинается с /api перенаправлять на 127.0.0.1:8000;
  • остальные запросы для домена example.com перенаправлять на 127.0.0.1:3000.

Так как обратный прокси-сервер разбирает, принимает и обрабатывает запросы, то возможности маршрутизации практически безграничны. Он может:

  • определять разные правила для разных доменов, адресов и путей на сервере;
  • определять права доступа в зависимости от адреса отправителя;
  • обогащать заголовки запроса дополнительной информацией.
  • распределять нагрузку между несколькими экземплярами одного приложения.

Такое богатство функций решает практически все проблемы, описанные ранее:

  • обратный прокси-сервер — это известное и проверенное ПО, готовое для работы в продакшене, и в отличие от прототипа, его запуск с правами суперпользователя несет меньшую угрозу;
  • у популярных реализаций обратного прокси-сервера есть опция, которая запускает обработчики от имени выделенного «бесправного» пользователя;
  • порты 80 и 443 заняты специализированным ПО, которое решает вопросы маршрутизации;
  • если обратный прокси-сервер и конечное приложение находятся в одном доверенном сегменте сети или вовсе на одном сервере, то между ними можно использовать открытый HTTP, что упрощает разработку.

Кроме того, появляется возможность в настройках фаервола открыть только минимально необходимые порты — например, 22, 80 и 443. Внутренние адреса веб-сервисов, конечно нужно отслеживать и прописывать в файле конфигурации обратного прокси, но зато по умолчанию все новые веб-сервисы защищены от «дикого интернета».

Какие обратные прокси-серверы существуют

На момент написания статьи есть несколько различных решений, выполняющих задачи обратного прокси-сервера: nginx, Apache HTTP Server, HAproxy и другие. Стоит отметить, что часть из них способна работать в качестве самостоятельного веб-сервера, то есть раздавать статические файлы из каталога, генерировать листинги директорий и даже запускать CGI- и FastCGI-приложения.

Мы рассмотрим только nginx и графический интерфейс для удобной конфигурации — Nginx Proxy Manager (NPM). Не путайте с Node Package Manager, который имеет такую же аббревиатуру и одноименную команду в терминале — npm.

Подготовка инфраструктуры

Создание нового VDS сервера на базе ОС Ubuntu в панели управления хостингом.
Страница заказа сервера.

Сперва подготовим сервер. Заходим в панель управления, далее Продукты → VDS Серверы → Создать сервер. VDS Серверы — это простые и дешевые виртуальные машины. Выбираем подходящую локацию, подходящую по бюджету и ресурсам виртуальную машину и предпочтительную операционную систему.

Мы возьмем 2 vCPU, 2 GB RAM, 40 GB NVMe и Ubuntu 24.04 в Санкт-Петербурге. Хотя это не является обязательным, рекомендуем использовать SSH-ключ для подключения к виртуальной машине вместо пароля. Когда вся информация заполнена, нажимаем Создать сервер. После завершения процесс в карточке сервера появится инструкция по подключению. Проверяем подключение:


      ssh root@136.234.X.X

В терминале увидим подробности устанавливаемого соединения. Обратите внимание, что в первый раз необходимо подтвердить свое намерение.

The authenticity of host '136.234.X.X (136.234.X.X)' can't be established.
ED25519 key fingerprint is SHA256:iCwdllXerMRYQa2Vr8NhpmgHXlBnQ8G3wlBAyJ5qQEM.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '136.234.X.X' (ED25519) to the list of known hosts.
Welcome to Ubuntu 24.04.4 LTS (GNU/Linux 6.8.0-138-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

Expanded Security Maintenance for Applications is not enabled.

0 updates can be applied immediately.

Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status


The list of available updates is more than a week old.
To check for new updates run: sudo apt update

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

Приглашение в терминале изменилось — мы на сервере:


      root@chell:~#

Отлично, сервер готов.

Назначение домена

Заполнение формы для добавления DNS A-записи поддомена с привязкой к IP-адресу VDS сервера.
Окно добавления А-записи.

В панели управления есть раздел по работе с доменами и DNS-зонами. Можно купить новый домен и тут же привязать его к серверу или делегировать имеющийся на NS Selectel. Создаем запись типа A с адресом сервера для поддомена til.example.com. В поле комментарий можно оставить заметку например, «VDS Сервер: Chell».

Успешно созданная A-запись для маршрутизации трафика на виртуальный сервер.
Созданная запись.

После создания проверяем доступность.


      ping til.example.com

Примерно каждую секунду должна появляться очередная запись с метриками прохождения ping-пакета:

PING til.f1remoon.ru (136.234.X.X) 56(84) bytes of data.
64 bytes from 136.234.X.X: icmp_seq=1 ttl=57 time=3.19 ms
64 bytes from 136.234.X.X: icmp_seq=2 ttl=57 time=4.83 ms
64 bytes from 136.234.X.X: icmp_seq=3 ttl=57 time=3.11 ms
64 bytes from 136.234.X.X: icmp_seq=4 ttl=57 time=4.68 ms

Прервать процесс можно по нажатию Ctrl+C:

^C
--- til.f1remoon.ru ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3004ms
rtt min/avg/max/mdev = 3.108/3.950/4.830/0.805 ms

Создание TLS-сертификата

Раздел управления доменными зонами с кнопкой создания бесплатного TLS-сертификата от Let's Encrypt.

В списке доменных зон можно создать сертификат для домена, который используется для организации подключения по HTTPS. Выбираем Создать под «TLS-сертификатом».

Диалоговое окно выпуска нового TLS-сертификата Let's Encrypt для основного домена и поддоменов.
Диалоговое окно выбора сертификата.

Выпускаем сертификат.

Страница управления созданным TLS-сертификатом со ссылками для скачивания файлов pem-ключей.
Созданный сертификат.

Скачиваем файлы сертификата. Они потребуются нам позже.

Установка Nginx Proxy Manager

NPM создан упрощать настройку nginx, и для максимального комфорта нужен Docker. Сперва установим его, а после — перейдем к развертыванию NPM.

Установка Docker

Для Ubuntu есть официальный репозиторий Docker. Добавляем его в источники.


      # Добавляем ключ репозитория
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Добавляем сам репозиторий
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

# Обновляем информацию о пакетах
sudo apt update

Затем устанавливаем Docker:


      sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

После установки проверяем, что тот работает.


      root@chell:~# sudo docker run hello-world

В ответ должны увидеть краткую сводку:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
4f55086f7dd0: Pull complete 
d5e71e642bf5: Download complete 
Digest: sha256:5e23090353324d887c48ad5e5c56d294eab81588df9605b07d1afe895f9cc8f8
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

Развертывание NPM

Обратный прокси будет жить в своем отдельном контейнере, но должен иметь доступ к другим определенным контейнерам. Для этого нужно выделить отдельную сеть в среде Docker.


      docker network create proxy

Теперь запускаем образ NPM. 


      docker run -d \
  --name app \
  --network proxy \
  --restart unless-stopped \
  -e TZ="Europe/Moscow" \
  -p 80:80 \
  -p 127.0.0.1:81:81 \
  -p 443:443 \
  -v "/opt/nginx/data:/data" \
  -v "/opt/nginx/letsencrypt:/etc/letsencrypt" \
  jc21/nginx-proxy-manager:latest
Приветственное сообщение об успешном запуске Nginx Proxy Manager в браузере.
Приветственное сообщение Nginx Proxy Manager.

Открываем til.example.com в браузере и наблюдаем поздравление с запущенным NPM. Пришло время его настраивать! 

Настройка Nginx Proxy Manager

Обратите внимание, что в команде docker порт 81 не пробрасывается «наружу». Это сделано из соображений безопасности: на этом порту открывается веб-интерфейс панели администратора, который при первом запуске предлагает создать нового пользователя. 

Во-первых, такие страницы не стоит делать доступными из интернета, во-вторых, взаимодействие с этим интерфейсом осуществляется по небезопасному протоколу HTTP. Для работе в «админке» лучше воспользоваться SSH-туннелированием:


      ssh -N -L 1081:localhost:81 root@til.example.com

После выполнения этой команды админ-панель будет доступна в браузере по адресу http://localhost:1081.

Форма регистрации первой учетной записи администратора в веб-интерфейсе Nginx Proxy Manager.
Рождение нового администратора.

Создаем нового пользователя и попадаем в панель. Теперь мы можем настраивать все, что нужно.

Подключение HTTPS

Выбор опции добавления собственного пользовательского сертификата (Custom Certificate) в панели Nginx Proxy Manager.
Окно создания сертификата.

Ранее мы уже выписывали TLS-сертификат. Теперь его нужно передать в Nginx Proxy Manager. Выбираем Certificate → Custom Certificate.

Окно загрузки скачанных файлов приватного ключа и сертификата в настройках Nginx Proxy Manager.
Выбор скачанных файлов.

Указываем ключ и сертификат, как указано на изображении. Промежуточный сертификат (Intermediate Certificate) не указываем, так как у нас его нет. Нажимаем Save. Теперь мы можем использовать доступный сертификат.

Проксирование до другого контейнера

Представим, что один из сервисов разворачивается в собственном контейнере. Запускаем контейнер:


      docker run -d \
  --name whoami \
  --network proxy \
  traefik/whoami

Обязательно указываем ключ --network proxy, чтобы NPM имел сетевой доступ к контейнеру. Также запоминаем название контейнера: оно используется в качестве доменного имени.

Настройка деталей нового прокси-хоста и маршрутизации до Docker-контейнера в Nginx Proxy Manager.
Создание прокси-хоста.

Переходим на вкладку Hosts → Proxy Hosts и нажимаем Add Proxy Host. Вписываем домен: til.example.com. В Forward Hostname / IP указываем имя контейнера, который хотим сделать доступным, в нашем случае — «whoami». Docker-сеть можно считать доверенной, внутри нее общение ведется по HTTP и, следовательно, порт указываем 80.

Включение принудительной поддержки HTTPS и привязка загруженного SSL-сертификата к прокси-хосту.
Добавление поддержки HTTPS.

Затем переходим на вкладку SSL и указываем ранее загруженный сертификат. Затем нажимаем Save. Если все правильно, то переходим по адресу http://til.example.com. 

Происходят две важные вещи. Во-первых, подключение переходит с HTTP на HTTPS. Во-вторых, открывается страница подобного содержания:

Hostname: 16d1ddf20675
IP: 127.0.0.1
IP: ::1
IP: 172.18.0.3
RemoteAddr: 172.18.0.2:38082
GET / HTTP/1.1
Host: til.example.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: en-US,en;q=0.9,ru;q=0.8
Cache-Control: max-age=0
Connection: close
Sec-Ch-Ua: "Google Chrome";v="129", "Not=A?Brand";v="8", "Chromium";v="129"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
X-Forwarded-For: 198.51.100.69
X-Forwarded-Proto: https
X-Forwarded-Scheme: https
X-Real-Ip: 198.51.100.69

Эту страницу отдает контейнер whoami. Если его остановить, то обратный прокси будет возвращать ошибку 502. Отлично, проксирование до других контейнеров работает.

Проксирование до bare-metal

Может возникнуть ситуация, когда приложение по каким-то причинам не может быть помещено в Docker‑контейнер и запускается непосредственно в операционной системе хоста. В этом случае нет имени контейнера и «магии Docker» не случится.

Чтобы проксировать запросы в хостовую систему, необходимо узнать адрес интерфейса, который используется в Docker-сети:


      docker network inspect proxy -f '{{range .IPAM.Config}}{{.Gateway}}{{end}}'

Вывод этой команды — IP-адрес, например, 172.18.0.1. Именно его нужно использовать в поле Forward Hostname / IP.

Важно, чтобы приложение слушало подключение именно на этом адресе.

Для Nginx Proxy Manager, работающего в контейнере, адрес 127.0.0.1 — это сам контейнер, а не хостовая ОС. Поэтому NPM просто не увидит приложение.

Если же приложение ожидает подключения на 0.0.0.0, то нужно проверить настройки фаервола: без него приложение будет доступно по публичному адресу и номеру порта в обход обратного прокси-сервера.

Заключение

Nginx Proxy Manager — прекрасный инструмент, использующий проверенный nginx, но абстрагирующий от установки и редактирования текстовых файлов конфигурации. Благодаря веб-интерфейсу NPM и Docker можно быстро настроить проксирование с разных доменов на нужные контейнеры, то есть максимально эффективно использовать доступные вычислительные ресурсы.