При разработке веб-проектов неизбежно поднимается вопрос их размещения в интернете. Разворачивать отдельную виртуальную машину для каждого маленького сервиса иногда избыточно и экономически нецелесообразно.
Давайте пристальнее взглянем на проблему размещения множества веб-сервисов на одном виртуальном сервере и познакомимся с элегантным решением — 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;
- обрабатывает все входящие соединения и маршрутизирует их согласно правилам в файле конфигурации.
- для домена example.com запросы по пути, который начинается с /api перенаправлять на 127.0.0.1:8000;
- остальные запросы для домена example.com перенаправлять на 127.0.0.1:3000.
- определять разные правила для разных доменов, адресов и путей на сервере;
- определять права доступа в зависимости от адреса отправителя;
- обогащать заголовки запроса дополнительной информацией.
- распределять нагрузку между несколькими экземплярами одного приложения.
- обратный прокси-сервер — это известное и проверенное ПО, готовое для работы в продакшене, и в отличие от прототипа, его запуск с правами суперпользователя несет меньшую угрозу;
- у популярных реализаций обратного прокси-сервера есть опция, которая запускает обработчики от имени выделенного «бесправного» пользователя;
- порты 80 и 443 заняты специализированным ПО, которое решает вопросы маршрутизации;
- если обратный прокси-сервер и конечное приложение находятся в одном доверенном сегменте сети или вовсе на одном сервере, то между ними можно использовать открытый HTTP, что упрощает разработку.
Какие обратные прокси-серверы существуют
На момент написания статьи есть несколько различных решений, выполняющих задачи обратного прокси-сервера: nginx, Apache HTTP Server, HAproxy и другие. Стоит отметить, что часть из них способна работать в качестве самостоятельного веб-сервера, то есть раздавать статические файлы из каталога, генерировать листинги директорий и даже запускать CGI- и FastCGI-приложения.Мы рассмотрим только nginx и графический интерфейс для удобной конфигурации — Nginx Proxy Manager (NPM). Не путайте с Node Package Manager, который имеет такую же аббревиатуру и одноименную команду в терминале — npm.
Подготовка инфраструктуры
Страница заказа сервера.
Сперва подготовим сервер. Заходим в панель управления, далее Продукты → VDS Серверы → Создать сервер. Выбираем подходящую локацию, подходящую по бюджету и ресурсам виртуальную машину и предпочтительную операционную систему.
Мы возьмем 2 vCPU, 2 GB RAM, 40 GB NVMe и Ubuntu 24.04 в Санкт-Петербурге. Хотя это не является обязательным, рекомендуем использовать SSH-ключ для подключения к виртуальной машине вместо пароля. Когда вся информация заполнена, нажимаем Создать сервер. После завершения процесс в карточке сервера появится инструкция по подключению. Проверяем подключение:
В терминале увидим подробности устанавливаемого соединения. Обратите внимание, что в первый раз необходимо подтвердить свое намерение.
Приглашение в терминале изменилось — мы на сервере:
Отлично, сервер готов.
Назначение домена
Окно добавления А-записи.
В панели управления есть раздел по работе с доменами и DNS-зонами. Можно купить новый домен и тут же привязать его к серверу или делегировать имеющийся на NS Selectel. Создаем запись типа A с адресом сервера для поддомена til.example.com. В поле комментарий можно оставить заметку например, «Сервер: Chell».
Созданная запись.
После создания проверяем доступность.
Примерно каждую секунду должна появляться очередная запись с метриками прохождения ping-пакета:
Прервать процесс можно по нажатию Ctrl+C:
Создание TLS-сертификата
Раздел управления доменными зонами с кнопкой создания бесплатного TLS-сертификата от Let's Encrypt.
В списке доменных зон можно создать сертификат для домена, который используется для организации подключения по HTTPS. Выбираем Создать под «TLS-сертификатом».
Диалоговое окно выбора сертификата.
Выпускаем сертификат.
Созданный сертификат.
Скачиваем файлы сертификата. Они потребуются нам позже.
Установка Nginx Proxy Manager
NPM создан упрощать настройку nginx, и для максимального комфорта нужен Docker. Сперва установим его, а после — перейдем к развертыванию NPM.Установка Docker
Для Ubuntu есть официальный репозиторий Docker. Добавляем его в источники.Затем устанавливаем Docker:
После установки проверяем, что тот работает.
В ответ должны увидеть краткую сводку:
Развертывание NPM
Обратный прокси будет жить в своем отдельном контейнере, но должен иметь доступ к другим определенным контейнерам. Для этого нужно выделить отдельную сеть в среде Docker.Теперь запускаем образ NPM.
Приветственное сообщение Nginx Proxy Manager.
Открываем til.example.com в браузере и наблюдаем поздравление с запущенным NPM. Пришло время его настраивать!
Настройка Nginx Proxy Manager
Обратите внимание, что в команде docker порт 81 не пробрасывается «наружу». Это сделано из соображений безопасности: на этом порту открывается веб-интерфейс панели администратора, который при первом запуске предлагает создать нового пользователя.Во-первых, такие страницы не стоит делать доступными из интернета, во-вторых, взаимодействие с этим интерфейсом осуществляется по небезопасному протоколу HTTP. Для работе в «админке» лучше воспользоваться SSH-туннелированием:
После выполнения этой команды админ-панель будет доступна в браузере по адресу http://localhost:1081.
Рождение нового администратора.
Создаем нового пользователя и попадаем в панель. Теперь мы можем настраивать все, что нужно.
Подключение HTTPS
Окно создания сертификата.
Ранее мы уже выписывали TLS-сертификат. Теперь его нужно передать в Nginx Proxy Manager. Выбираем Certificate → Custom Certificate.
Выбор скачанных файлов.
Указываем ключ и сертификат, как указано на изображении. Промежуточный сертификат (Intermediate Certificate) не указываем, так как у нас его нет. Нажимаем Save. Теперь мы можем использовать доступный сертификат.
Проксирование до другого контейнера
Представим, что один из сервисов разворачивается в собственном контейнере. Запускаем контейнер:Обязательно указываем ключ --network proxy, чтобы NPM имел сетевой доступ к контейнеру. Также запоминаем название контейнера: оно используется в качестве доменного имени.
Создание прокси-хоста.
Переходим на вкладку Hosts → Proxy Hosts и нажимаем Add Proxy Host. Вписываем домен: til.example.com. В Forward Hostname / IP указываем имя контейнера, который хотим сделать доступным, в нашем случае — «whoami». Docker-сеть можно считать доверенной, внутри нее общение ведется по HTTP и, следовательно, порт указываем 80.
Добавление поддержки HTTPS.
Затем переходим на вкладку SSL и указываем ранее загруженный сертификат. Затем нажимаем Save. Если все правильно, то переходим по адресу http://til.example.com.
Происходят две важные вещи. Во-первых, подключение переходит с HTTP на HTTPS. Во-вторых, открывается страница подобного содержания:
Эту страницу отдает контейнер whoami. Если его остановить, то обратный прокси будет возвращать ошибку 502. Отлично, проксирование до других контейнеров работает.
Проксирование до bare-metal
Может возникнуть ситуация, когда приложение по каким-то причинам не может быть помещено в Docker‑контейнер и запускается непосредственно в операционной системе хоста. В этом случае нет имени контейнера и «магии Docker» не случится.Чтобы проксировать запросы в хостовую систему, необходимо узнать адрес интерфейса, который используется в Docker-сети:
Вывод этой команды — IP-адрес, например, 172.18.0.1. Именно его нужно использовать в поле Forward Hostname / IP.