Обратный прокси в Nginx для нескольких приложений: варианты
Один Nginx может направлять запросы к нескольким приложениям по домену, пути или порту. Сравниваем схемы, разбираем proxy_pass, WebSocket и типичные ошибки, из-за которых сервисы получают 404 и 502.

Один сервер Nginx может принимать трафик для нескольких приложений, но цена ошибки зависит от выбранного правила маршрутизации. Если отправить сервисы по путям, придётся учитывать префиксы URL и настройки статики; если развести их по поддоменам, понадобится DNS и отдельная настройка cookie. Ниже сравниваем три рабочих схемы для Nginx 1.25.x и выбираем вариант по устройству проекта, а не по привычке.
Что именно делает обратный прокси
Клиент подключается к Nginx по HTTP или HTTPS, а Nginx передаёт запрос одному из внутренних приложений. Приложения могут слушать разные локальные порты, Unix-сокеты или адреса в Docker-сети. Наружу при этом открывается один адрес и, как правило, только порты 80 и 443.
Обратный прокси — сервер-посредник, который получает запрос от клиента и сам обращается к внутреннему приложению от своего имени. Клиент не видит внутренний порт, адрес контейнера или структуру сети.
Главный выбор — не между разными директивами Nginx, а между способами адресовать приложение: отдельным доменом, URL-префиксом или портом.
Три схемы маршрутизации
| Схема | Как выглядит снаружи | Плюсы | Цена и риск |
|---|---|---|---|
| Поддомены | app.example.com и admin.example.com | Чистые URL, проще изолировать cookie и статику | Нужны DNS-записи и отдельные server-блоки |
| Пути | example.com/app и example.com/admin | Один домен, удобно для внутренних сервисов | Приложение должно корректно работать под префиксом |
| Порты | example.com:8081 и example.com:8082 | Минимум магии с путями | Порты нужно открывать, URL менее удобны, HTTPS сложнее объяснить пользователю |
Для публичных независимых приложений обычно выбирают поддомены. Для единого интерфейса с несколькими backend-сервисами — пути. Порты оставляют для технических панелей, локальных стендов и случаев, когда приложение нельзя адаптировать к доменному или URL-префиксу.
Вариант 1: разные приложения на поддоменах
Пусть первое приложение слушает 127.0.0.1:8000, второе — 127.0.0.1:9000. В DNS должны существовать записи для app.example.com и admin.example.com, указывающие на сервер с Nginx.
# Nginx 1.25.x
upstream frontend_app {
server 127.0.0.1:8000;
}
upstream admin_app {
server 127.0.0.1:9000;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://frontend_app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name admin.example.com;
location / {
proxy_pass http://admin_app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Здесь proxy_pass указан без URI после имени upstream. Поэтому Nginx передаёт приложению исходный путь: запрос /users останется /users. После добавления HTTPS блоки обычно дополняют редиректом с 80 на 443 и сертификатами; выпуск сертификата и автоматическое продление — отдельная задача.
Когда поддомены выигрывают
- приложения принадлежат разным командам или имеют разные циклы релиза;
- каждое приложение ожидает, что работает от корня URL
/; - нужно раздельно настраивать cookie, CORS, кэширование или авторизацию;
- публичные адреса должны быть стабильными и понятными.
Недостаток схемы — инфраструктурный: потребуется управлять DNS, сертификатами и политиками доступа для нескольких имён. Это небольшая цена по сравнению с переделкой приложения, которое не умеет жить под /admin.
Вариант 2: несколько приложений по путям
В этой схеме один домен обслуживает несколько backend-сервисов. Критически важно, что приложение должно понимать свой внешний префикс. Например, оно должно генерировать ссылки вида /admin/assets/app.css, а не /assets/app.css.
# Nginx 1.25.x
upstream shop_app {
server 127.0.0.1:8000;
}
upstream reports_app {
server 127.0.0.1:9000;
}
server {
listen 80;
server_name example.com;
location = /shop {
return 301 /shop/;
}
location /shop/ {
proxy_pass http://shop_app/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /reports {
return 301 /reports/;
}
location /reports/ {
proxy_pass http://reports_app/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Завершающий слеш в proxy_pass http://shop_app/ имеет значение: при таком варианте Nginx заменяет совпавший префикс /shop/ на /. Запрос /shop/catalog уйдёт приложению как /catalog. Если написать proxy_pass http://shop_app; без завершающего слеша, backend получит путь /shop/catalog.
Что проверить до выбора путей
- умеет ли приложение задавать базовый URL или внешний префикс;
- не зашиты ли в HTML, JavaScript и CSS ссылки от корня домена;
- совпадает ли путь для API, статики, редиректов и callback-адресов;
- не пересекаются ли правила:
/appи/applicationтребуют аккуратной проверки маршрутов; - не используют ли приложения одинаковые имена cookie с несовместимыми значениями.
Типичный симптом неправильного префикса — главная страница открывается, но CSS и JavaScript получают 404. Это не обязательно ошибка Nginx: backend может сформировать ссылки так, будто он работает на корне домена.
Вариант 3: раздельные порты
Самая короткая схема выглядит так: example.com:8081 проксируется на первое приложение, а example.com:8082 — на второе. Для неё нужны отдельные директивы listen и открытые в firewall порты.
# Nginx 1.25.x
server {
listen 8081;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 8082;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:9000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Порты полезны на тестовом сервере, но для публичного продукта создают лишние точки входа. Нужно отдельно проверять HTTPS, firewall, ссылки в документации и корпоративные прокси, которые могут блокировать нестандартные порты.
WebSocket, таймауты и заголовки
Обычного HTTP-проксирования недостаточно для WebSocket: соединение после начального запроса должно перейти в режим upgrade. Для приложения, которое использует WebSocket, добавьте в соответствующий location:
# Nginx 1.25.x
location /socket/ {
proxy_pass http://frontend_app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 60m;
}
proxy_read_timeout задаёт время ожидания данных от backend между операциями чтения, а не общий лимит жизни соединения. Ставьте большое значение только для действительно долгих каналов: иначе зависшие соединения будут дольше занимать worker и ресурсы приложения.
Заголовки Host, X-Forwarded-For и X-Forwarded-Proto нужны не ради шаблона. Приложение использует их для абсолютных URL, журналов, определения исходного IP и понимания, был ли внешний запрос HTTPS. Доверять этим заголовкам внутри приложения следует только от известного прокси, а не от любого клиента.
Что ломается чаще всего
502 Bad Gateway
Nginx не может подключиться к upstream. Проверьте, запущен ли процесс, слушает ли он нужный адрес и порт, не ограничен ли доступ firewall. Для systemd полезно отдельно проверить журнал приложения и журнала Nginx, а не менять директивы наугад.
404 после переноса под префикс
Сравните фактический путь, который получает backend, с вариантом proxy_pass со слешем и без него. Затем проверьте базовый URL приложения и ссылки на статику.
Бесконечный редирект на HTTPS
Внешний TLS завершается на Nginx, а приложение видит внутренний HTTP. Передайте X-Forwarded-Proto и включите в самом приложении доверие к этому прокси по правилам конкретного фреймворка.
WebSocket сразу закрывается
Проверьте proxy_http_version 1.1, заголовки Upgrade и Connection, а также маршрут, по которому клиент открывает канал. Обычный API может работать, пока WebSocket остаётся сломанным.
Как выбрать схему
Используйте поддомены, если приложения самостоятельны и должны работать от корня. Выбирайте пути, если пользователю нужен единый домен и вы контролируете базовый URL каждого сервиса. Раздельные порты оставляйте для разработки и инфраструктурных интерфейсов, где удобство публичного URL не является требованием.
Перед изменением конфигурации составьте таблицу маршрутов: внешний адрес, внутренний адрес, ожидаемый путь backend, WebSocket-маршруты, требования к cookie и способ проверки. После правки сначала проверьте конфигурацию командой nginx -t, затем выполните безопасную перезагрузку штатным способом вашей системы и протестируйте каждый маршрут отдельно.
Что делать дальше
После запуска настройте HTTPS, ограничьте доступ к внутренним портам и добавьте в мониторинг коды 502, 504, время ответа upstream и число активных соединений. Отдельно проверьте приложение в браузере, через API-клиент и по WebSocket, если он используется. При росте нагрузки следующим шагом будет не добавление случайных таймаутов, а измерение времени ответа backend и количества соединений на каждый upstream.
Техническая сноска: upstream — группа серверов, объявленная в конфигурации Nginx для проксирования запросов. В простом случае в группе один backend, но тот же механизм позволяет распределять запросы между несколькими экземплярами.
Prompt-инженер: Идеальные запросы для Midjourney, ChatGPT и других моделей.
Спросить за 15 ₽Источники: Официальная документация Nginx: ngx_http_proxy_module, Официальная документация Nginx: WebSocket proxying
Частые вопросы
Что выбрать для двух независимых приложений: пути или поддомены?
Почему proxy_pass со слешем и без него даёт разный результат?
Нужно ли использовать upstream, если у приложения только один экземпляр?
Почему приложение открывается, но CSS и JavaScript возвращают 404?
Что делать, если после настройки появляется 502 Bad Gateway?
Нужна ли отдельная настройка для WebSocket?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.