Nginx как reverse proxy: настройка и конфигурация
Reverse proxy на Nginx — это когда Nginx принимает внешние запросы на порту 80 или 443 и перенаправляет их на приложение которое работает на внутреннем порту (3000, 8000, 8080). Снаружи видно только Nginx, само приложение сети не касается. Три строки в конфиге Nginx — и Node.js, Python или любой другой HTTP-сервер уже доступен через стандартные порты.
Когда нужен reverse proxy
Стандартная ситуация на VPS: приложение на Node.js слушает порт 3000, Python-сервер на 8000, ещё один сервис на 8080. Открывать эти порты наружу неудобно — пользователям придётся указывать номер порта в URL. Работать от root чтобы занять порт 80 — плохая практика безопасности.
Nginx как reverse proxy решает это элегантно: один процесс слушает порты 80 и 443, принимает запросы и направляет их нужному приложению. Это также позволяет запускать несколько приложений на одном сервере под разными доменами, добавить SSL-терминацию в одном месте, кешировать ответы и сжимать трафик.
Базовая конфигурация
Минимальный конфиг для проксирования приложения на порту 3000:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://localhost: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;
}
}
Проверьте конфиг и примените:
sudo nginx -t && sudo systemctl reload nginx
Почему заголовки обязательны
Четыре директивы proxy_set_header — не опциональные. Без них приложение работает некорректно.
Host $host — без этого заголовка бэкенд получает localhost вместо реального домена. Приложения которые формируют ссылки на себя (например, для редиректов или email) будут генерировать адреса с localhost вместо вашего домена.
X-Real-IP $remote_addr — без этого заголовка бэкенд видит IP 127.0.0.1 вместо реального IP пользователя. Логи приложения, аналитика, гео-фильтрация и защита от брутфорса становятся бесполезными — все запросы приходят «с localhost».
X-Forwarded-For $proxy_add_x_forwarded_for — цепочка IP-адресов при прохождении через несколько прокси. $proxy_add_x_forwarded_for добавляет IP текущего клиента к уже существующей цепочке — важно при работе за CDN или нескольких уровнях прокси.
X-Forwarded-Proto $scheme — передаёт бэкенду информацию о протоколе (http или https). Без этого приложение не знает что пользователь пришёл по HTTPS и может генерировать незащищённые редиректы или ссылки.
Критичная ловушка: слеш в proxy_pass
Это самая частая причина неработающего проксирования. Наличие или отсутствие слеша в конце proxy_pass меняет поведение кардинально.
Без слеша — URI передаётся бэкенду как есть:
location /api/ {
proxy_pass http://localhost:3000;
}
Запрос GET /api/users → бэкенд получает /api/users
Со слешем — Nginx убирает prefix локации:
location /api/ {
proxy_pass http://localhost:3000/;
}
Запрос GET /api/users → бэкенд получает /users
Когда нужен вариант без слеша: если бэкенд сам обрабатывает путь /api/... и ждёт его полностью. Когда со слешем: если хотите «смонтировать» бэкенд на подпуть — приложение видит запросы как будто оно работает в корне, без префикса /api/.
Настройка таймаутов
По умолчанию Nginx ждёт ответа от бэкенда 60 секунд. Для большинства API этого достаточно, но если у вас медленные операции — нужно увеличить:
location / {
proxy_pass http://localhost:3000;
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
Что означает каждый параметр:
proxy_connect_timeout— сколько ждать установки TCP-соединения с бэкендом. 10 секунд — разумный максимум, если бэкенд не отвечает дольше — он скорее всего упал.proxy_send_timeout— сколько ждать пока Nginx передаст запрос бэкенду.proxy_read_timeout— сколько ждать ответа. Для long-polling, генерации отчётов или AI-запросов — увеличьте до300sили больше.
Поддержка WebSocket
WebSocket требует особой обработки — это долгоживущее соединение с переключением протокола. Без трёх дополнительных директив WebSocket-соединения будут устанавливаться, но сразу разрываться:
location /ws/ {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 3600s;
}
proxy_http_version 1.1 обязателен — WebSocket работает только поверх HTTP/1.1. Upgrade и Connection "upgrade" сигнализируют о переключении протокола. proxy_read_timeout 3600s — WebSocket-соединение может жить часами, стандартные 60 секунд его разорвут.
Несколько приложений на одном сервере
Каждое приложение получает свой server блок с уникальным server_name:
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://localhost: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;
}
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://localhost:8080;
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;
}
}
Nginx маршрутизирует запросы по заголовку Host — каждый домен идёт в нужное приложение.
Upstream блок и балансировка нагрузки
Для нескольких экземпляров одного приложения используйте блок upstream:
upstream myapp {
least_conn;
server localhost:3000;
server localhost:3001;
server localhost:3002;
keepalive 32;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://myapp;
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_http_version 1.1;
proxy_set_header Connection "";
}
}
Директива least_conn направляет запрос на сервер с наименьшим числом активных соединений — это разумнее чем round-robin когда запросы имеют разное время обработки.
keepalive 32 — пул из 32 постоянных соединений между Nginx и бэкендами. Без него каждый входящий запрос создаёт новое TCP-соединение к бэкенду — дополнительные миллисекунды задержки и нагрузка на сеть. С keepalive соединения переиспользуются, и proxy_set_header Connection "" очищает заголовок Connection унаследованный от клиента — это обязательно для корректной работы keepalive.
Методы балансировки:
| Метод | Директива | Когда использовать |
|---|---|---|
| Round-robin | (по умолчанию) | Запросы одинаковой длительности |
| Наименьшее число соединений | least_conn; |
Запросы разной длительности |
| По IP клиента | ip_hash; |
Sticky sessions, авторизация |
Готовый продакшн-конфиг
Полная конфигурация с SSL, правильными заголовками, таймаутами и gzip:
upstream myapp {
least_conn;
server localhost:3000;
keepalive 32;
}
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
gzip on;
gzip_types text/plain application/json application/javascript text/css;
location / {
proxy_pass http://myapp;
proxy_http_version 1.1;
proxy_set_header Connection "";
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 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
Проверка что всё работает
Проверить синтаксис конфига:
sudo nginx -t
Применить без даунтайма:
sudo systemctl reload nginx
Проверить что заголовки доходят до бэкенда — добавьте в приложение временный эндпоинт который возвращает заголовки запроса, или используйте curl:
curl -I http://example.com
Посмотреть лог Nginx в реальном времени:
sudo tail -f /var/log/nginx/access.log
Часто задаваемые вопросы
В чём разница между proxy_pass с слешем и без слеша?
Без слеша (proxy_pass http://localhost:3000) — Nginx передаёт полный URI запроса бэкенду. Со слешем (proxy_pass http://localhost:3000/) — Nginx убирает префикс из location и передаёт только остаток пути. Например, при location /api/ запрос на /api/users превратится в /users если в proxy_pass есть слеш.
Почему бэкенд видит IP 127.0.0.1 вместо реального IP пользователя?
Потому что заголовок X-Real-IP $remote_addr не добавлен в конфиг reverse proxy. Nginx передаёт запрос бэкенду от своего имени — бэкенд видит соединение с localhost. Чтобы бэкенд знал реальный IP, добавьте proxy_set_header X-Real-IP $remote_addr и настройте приложение читать IP из этого заголовка.
Как настроить Nginx reverse proxy для WebSocket?
Добавьте три директивы в location: proxy_http_version 1.1, proxy_set_header Upgrade $http_upgrade и proxy_set_header Connection "upgrade". Также увеличьте proxy_read_timeout до нескольких часов — WebSocket соединения живут долго и стандартный таймаут 60 секунд их оборвёт.
Можно ли проксировать несколько приложений через один Nginx?
Да. Создайте отдельный server блок для каждого домена с нужным proxy_pass. Nginx маршрутизирует запросы по заголовку Host и направляет их в соответствующее приложение. Таким образом можно запустить десятки приложений на разных портах за одним Nginx.
Зачем нужен keepalive в upstream блоке?
Без keepalive каждый входящий запрос создаёт новое TCP-соединение к бэкенду. Это добавляет несколько миллисекунд задержки на каждый запрос и увеличивает нагрузку на оба сервера. keepalive 32 создаёт пул из 32 постоянных соединений с бэкендом — соединения переиспользуются и производительность заметно растёт.
Для работы Nginx как reverse proxy нужен VPS с постоянным IP-адресом. На UFO.Hosting каждый сервер получает выделенный IP сразу после создания — можно сразу настраивать домен, SSL и reverse proxy.
Официальная документация: nginx.org/en/docs/http/ngx_http_proxy_module.html
Похожее
Все статьи
Node.js через NVM и PM2: установка и запуск на Ubuntu
NVM (Node Version Manager) позволяет устанавливать несколько версий Node.js на одном сервере и переключаться между ними без переустановки. PM2 — менеджер процессов, который держит Node.js-приложение запущенным в фоне, перезапускает его при падении и добавляет в автозагрузку. Вместе они закрывают большинство…
Какая CMS лучше подходит для разных типов сайтов
Выбор системы управления сайтом — это всегда компромисс в какой-то степени компромисс между сложностью, доступностью и привычностью. Нельзя просто так взять и сказать: «Вот конкретно именно эта CMS самая лучшая». Под каждую задачу есть свой инструмент. Поэтому более корректно спрашивать:…