
При разработке веб-проектов неизбежно поднимается вопрос их размещения в интернете. Разворачивать отдельную виртуальную машину для каждого маленького сервиса иногда избыточно и экономически нецелесообразно.
Давайте пристальнее взглянем на проблему размещения множества веб-сервисов на одном виртуальном сервере и познакомимся с элегантным решением — Nginx Proxy Manager.
Содержание
→ Проблемы развертывания множества проектов
→ Что такое обратный прокси
→ Подготовка инфраструктуры
→ Установка Nginx Proxy Manager
→ Настройка 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. Однако такое разделение создает целый ряд задач, которые придется решить.
В браузерах есть механизм безопасности CORS (Cross-Origin Resource Sharing), который запрещает обращения к другим доменам. То есть, если dev-сборка фронтенда на
http://example.com:3000будет обращаться кhttp://example.com:3001/api, то браузер расценит ее как другой источник и потребует от бэкэнда подтвердить, что сexample.com:3000разрешено делать запросы.Если проектов больше одного, то необходимо следить, какие порты за каким приложением закреплены и актуализировать настройки брандмауэра. Более того, явное указание порта выглядит странно и может отпугивать обычных пользователей.
Кажется, что домены могут решить проблему, но это не так.
Сервер и домены
Домены — это человекочитаемые «псевдонимы» для адресов в интернете. Человеку проще запомнить 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-серверы от 200 ₽/месяц
Семь готовых конфигураций, KVM‑виртуализация, NVMe SSD и техническая поддержка 24/7.
Подготовка инфраструктуры

Сперва подготовим сервер. Заходим в панель управления, далее Продукты → 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-зонами. Можно купить новый домен и тут же привязать его к серверу или делегировать имеющийся на NS Selectel. Создаем запись типа A с адресом сервера для поддомена til.example.com. В поле комментарий можно оставить заметку например, «VDS Сервер: Chell».

После создания проверяем доступность.
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-сертификата

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

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

Скачиваем файлы сертификата. Они потребуются нам позже.
Установка 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

Открываем til.example.com в браузере и наблюдаем поздравление с запущенным NPM. Пришло время его настраивать!
Настройка Nginx Proxy Manager
Обратите внимание, что в команде docker порт 81 не пробрасывается «наружу». Это сделано из соображений безопасности: на этом порту открывается веб-интерфейс панели администратора, который при первом запуске предлагает создать нового пользователя.
Во-первых, такие страницы не стоит делать доступными из интернета, во-вторых, взаимодействие с этим интерфейсом осуществляется по небезопасному протоколу HTTP. Для работе в «админке» лучше воспользоваться SSH-туннелированием:
ssh -N -L 1081:localhost:81 root@til.example.com
После выполнения этой команды админ-панель будет доступна в браузере по адресу http://localhost:1081.

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

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

Указываем ключ и сертификат, как указано на изображении. Промежуточный сертификат (Intermediate Certificate) не указываем, так как у нас его нет. Нажимаем Save. Теперь мы можем использовать доступный сертификат.
Проксирование до другого контейнера
Представим, что один из сервисов разворачивается в собственном контейнере. Запускаем контейнер:
docker run -d \ --name whoami \ --network proxy \ traefik/whoami
Обязательно указываем ключ --network proxy, чтобы NPM имел сетевой доступ к контейнеру. Также запоминаем название контейнера: оно используется в качестве доменного имени.

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

Затем переходим на вкладку 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 можно быстро настроить проксирование с разных доменов на нужные контейнеры, то есть максимально эффективно использовать доступные вычислительные ресурсы.