Публичные HTTPS-запросы проходят через Nginx к локальному приложению
DevOps

Reverse proxy Nginx для локального приложения

Настраиваем Nginx перед приложением на localhost: proxy_pass, заголовки, таймауты, WebSocket, проверка upstream и безопасный откат.

Содержание

Приложение на Node.js, Python или Go умеет отвечать по HTTP, но его внутренний порт не обязательно открывать в интернет. Nginx принимает публичное соединение на 80/443, обслуживает HTTPS и передаёт запрос программе на локальном адресе. Такая роль называется reverse proxy, или обратный прокси.

В примерах приложение слушает 127.0.0.1:3000. Адрес 127.0.0.1 доступен только на самом сервере, поэтому внешний клиент не обойдёт правила Nginx. Сначала проверим приложение напрямую, затем подключим proxy_pass, передадим необходимые заголовки и сравним локальный и внешний ответы.

Когда подходит reverse proxy

Схема нужна, если на VPS работает Node.js, Python, Go или другой HTTP-сервис, который не должен самостоятельно обслуживать публичный TLS. Nginx полезен для:

  • единой точки 80/443;
  • сертификатов и HTTP→HTTPS;
  • статических файлов;
  • ограничений размера и времени запроса;
  • безопасной передачи адреса и имени хоста;
  • нескольких приложений на одном IP.

Для полностью статического сайта proxy_pass не нужен: Nginx эффективнее читает файлы напрямую.

Не открывайте административный endpoint приложения только потому, что он доступен через общий proxy. Служебные маршруты лучше оставить на отдельном локальном сокете либо ограничить аутентификацией и источником. Удаляйте заголовки, которые не нужны upstream, и задавайте максимальный размер запроса по реальной функции приложения.

Подготовьте приложение

Сначала отделите ошибку приложения от будущей настройки Nginx. Проверьте состояние службы, слушающий процесс и её служебный URL прямо на локальном порту:

sudo systemctl status myapp --no-pager
sudo ss -lntp | grep ':3000'
curl --fail --silent --show-error http://127.0.0.1:3000/health

Имена и порт — примеры. Приложение должно слушать 127.0.0.1, а не 0.0.0.0, если прямой внешний доступ не нужен. Для Unix-сокета настройте владельца и группу так, чтобы worker Nginx мог подключаться, но посторонние пользователи — нет.

Локальный health endpoint должен проверять готовность процесса без раскрытия конфигурации, версии фреймворка и секретов. Код 200 подтверждает только выбранную проверку, а не весь пользовательский сценарий.

Создайте отдельный конфигурационный файл

На Ubuntu виртуальный хост обычно хранится в /etc/nginx/sites-available/. Сначала сохраните существующее состояние и откройте файл через sudoedit:

sudo cp -a /etc/nginx/sites-available/example.org /etc/nginx/sites-available/example.org.bak
sudoedit /etc/nginx/sites-available/example.org

Внутри уже работающего HTTPS-блока server добавьте правило для запросов к приложению. Оно передаёт запрос на локальный порт и сообщает программе домен, адрес клиента и исходный протокол:

location / {
    proxy_pass http://127.0.0.1:3000;
    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_connect_timeout 5s;
    proxy_read_timeout 30s;
    proxy_send_timeout 30s;
}

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

Разберитесь с путём proxy_pass

Завершающий слеш в proxy_pass меняет путь, который увидит приложение. Первый вариант передаёт исходный URI вместе с префиксом /api/:

location /api/ {
    proxy_pass http://127.0.0.1:3000;
}

Во втором варианте после адреса upstream указан URI /. Nginx заменяет им совпавшую часть /api/ перед отправкой запроса:

location /api/ {
    proxy_pass http://127.0.0.1:3000/;
}

В первом случае upstream обычно получает путь с /api/. Во втором совпавшая часть location заменяется /, и /api/users превращается в /users. Составьте таблицу из двух-трёх ожидаемых URL и проверьте фактическое поведение после reload.

Передавайте заголовки в определённой границе доверия

Host нужен приложению для формирования канонических ссылок и выбора виртуального сайта. X-Forwarded-Proto сообщает, что внешний запрос пришёл по HTTPS, даже если внутреннее соединение HTTP.

X-Forwarded-For нельзя безусловно принимать от интернета как доказательство адреса клиента. Nginx добавляет $remote_addr, а приложение должно доверять forwarded-заголовкам только от известного proxy. Если перед Nginx есть CDN или балансировщик, настройка real IP требует отдельного списка доверенных адресов провайдера.

Не передавайте входной заголовок Host как $http_host, если приложение не готово проверять произвольное значение. $host имеет предсказуемый fallback на имя server.

WebSocket и потоковые ответы

Для WebSocket клиент просит сменить протокол через заголовок Upgrade. В контексте http создайте переменную, которая передаст upgrade только при наличии такого запроса:

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

Затем используйте вычисленное значение в отдельном маршруте WebSocket. Этот блок располагается внутри нужного server:

location /socket/ {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host $host;
}

Не добавляйте WebSocket-настройки, если приложение его не использует. Для Server-Sent Events или потоковой выдачи отдельно проверьте buffering и таймауты на конкретном маршруте.

Проверьте конфигурацию и примените

После сохранения файла сначала проверьте всё дерево Nginx. Если тест успешен, выполните reload и убедитесь, что служба осталась активной:

sudo nginx -t
sudo systemctl reload nginx
sudo systemctl status nginx --no-pager

nginx -t проверяет синтаксис и попытку открыть упомянутые файлы. Reload сохраняет текущие соединения лучше полного restart. Старую SSH-сессию и резервную копию конфигурации держите до внешней проверки.

С самого сервера сравните прямой health-check приложения с тем же маршрутом через HTTPS-виртуальный хост Nginx:

curl --fail --silent --show-error http://127.0.0.1:3000/health
curl --resolve example.org:443:127.0.0.1 --fail --silent --show-error https://example.org/health

Второй запрос проверяет Nginx и имя виртуального хоста локально; сертификат должен соответствовать домену. Затем выполните запрос извне, чтобы включить DNS, firewall и реальный сетевой путь.

Диагностика 502 и 504

При ошибке сопоставьте сообщения приложения, Nginx и фактический слушающий порт за один интервал времени:

sudo journalctl -u myapp --since '15 minutes ago'
sudo tail -n 100 /var/log/nginx/error.log
sudo ss -lntp | grep ':3000'

502 обычно означает ошибку соединения или некорректный ответ upstream. 504 — превышение ожидания. Сопоставьте время запроса в access/error log и журнале приложения. Не раскрывайте пользователю stack trace и значения окружения.

Как откатить

Если новый proxy не работает, верните сохранённый файл, проверьте и перечитайте Nginx:

sudo cp -a /etc/nginx/sites-available/example.org.bak /etc/nginx/sites-available/example.org
sudo nginx -t
sudo systemctl reload nginx

Откат Nginx не откатывает приложение и миграцию данных. Если менялись оба слоя, используйте согласованный релиз и совместимый rollback.

Upstream должен работать как ограниченная systemd-служба и слушать только ожидаемый локальный адрес. После настройки proxy проверяйте внешний HTTPS-ответ и связывайте ошибки Nginx с событиями приложения через journalctl.

Рекламное местоВаша компания здесьРазместить рекламу

Самопроверка

Проверьте, что материал усвоен

Ответьте на все вопросы. Результат сохранится только в этом браузере и будет учтён в статистике прочитанных материалов.

01Где лучше слушать приложению, доступному только через Nginx?
02Что нужно сделать перед reload Nginx?
03Как безопасно доверять X-Forwarded-For?

Разбираем коротко

Частые вопросы

Зачем приложению Nginx, если оно само умеет HTTP?

Nginx принимает публичные HTTPS-соединения, централизует сертификаты и ограничения, а приложение остаётся на локальном интерфейсе. Это разделяет внешний периметр и прикладной процесс.

Чем отличаются proxy_pass с завершающим слешем и без него?

При указании URI в proxy_pass совпавшая часть location заменяется указанным URI. Без URI исходный путь обычно передаётся upstream целиком. Разницу нужно проверять на реальных вложенных маршрутах.

Почему Nginx возвращает 502 Bad Gateway?

Чаще всего приложение не запущено, слушает другой адрес или порт, недоступно по правам сокета либо завершает соединение. Сначала проверьте systemd, ss, локальный curl и error log Nginx.