Настройка reverse proxy в Nginx
Пошаговая настройка Nginx как reverse proxy для внутреннего веб-приложения с передачей заголовков, WebSocket и базовой диагностикой.
Reverse proxy принимает запросы от клиентов и передаёт их внутреннему приложению. При этом пользователи обращаются только к Nginx, а внутренний сервис может работать на отдельном порту или локальном адресе.
В этой инструкции рассматривается только базовая настройка Nginx как reverse proxy для одного HTTP-приложения.
Проверено на: Ubuntu Server 24.04 LTS
Уровень сложности: средний
Время выполнения: около 15–20 минут
Требуемый доступ: пользователь с правамиsudo
Что будет настроено
После выполнения инструкции:
- будет проверено внутреннее приложение;
- будет создан отдельный server block;
- Nginx будет передавать запросы приложению;
- будут передаваться исходные заголовки клиента;
- будет добавлена поддержка WebSocket;
- будет выполнена проверка ошибок
502 Bad Gateway; - будет показано безопасное применение конфигурации.
Исходная схема
В примерах используется:
Публичный домен: app.example.com
Внутреннее приложение: http://127.0.0.1:3000
Nginx: порт 80
Используйте собственный домен и фактический порт приложения.
Схема:
Клиент
↓
Nginx :80
↓
Приложение 127.0.0.1:3000
Проверка приложения
До настройки Nginx убедитесь, что приложение работает напрямую.
curl -I http://127.0.0.1:3000
Проверка содержимого:
curl http://127.0.0.1:3000
Если приложение не отвечает, сначала исправьте сам сервис.
Nginx не сможет проксировать недоступный backend.
Проверка прослушиваемого порта
sudo ss -lntp |
grep ':3000'
Ожидается процесс приложения.
Безопаснее, если внутренний сервис слушает:
127.0.0.1:3000
а не:
0.0.0.0:3000
Это предотвращает прямой внешний доступ к backend.
Проверка systemd-сервиса
Если приложение управляется через systemd:
systemctl status APP_SERVICE --no-pager
Проверка журнала:
sudo journalctl -u APP_SERVICE -n 100 --no-pager
Замените APP_SERVICE на имя службы.
Проверка DNS
dig +short app.example.com
Ожидается IP сервера Nginx.
Проверка через resolver:
getent hosts app.example.com
Создание резервной копии
Если server block уже существует:
sudo cp /etc/nginx/sites-available/app.example.com /etc/nginx/sites-available/app.example.com.backup-$(date +%F-%H%M%S)
Создание server block
sudo nano /etc/nginx/sites-available/app.example.com
Добавьте:
server {
listen 80;
listen [::]:80;
server_name app.example.com;
access_log /var/log/nginx/app.example.com.access.log;
error_log /var/log/nginx/app.example.com.error.log;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
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_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
Для переменной $connection_upgrade потребуется отдельная map, показанная ниже.
Настройка WebSocket map
Откройте основной файл:
sudo nano /etc/nginx/nginx.conf
Внутри блока:
http {
добавьте:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
Пример:
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# Остальная конфигурация
}
Эта конструкция корректно обрабатывает обычные HTTP-запросы и WebSocket upgrade.
Упрощённый вариант без WebSocket
Если приложение не использует WebSocket, можно убрать:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
и не добавлять map.
Не добавляйте WebSocket-настройки без необходимости, если приложение работает только по обычному HTTP.
Разбор proxy_pass
proxy_pass http://127.0.0.1:3000;
Nginx передаёт запросы на локальный backend.
Важно различать варианты.
Без завершающего слеша
location /api/ {
proxy_pass http://127.0.0.1:3000;
}
Backend получит исходный URI:
/api/users
С завершающим слешем
location /api/ {
proxy_pass http://127.0.0.1:3000/;
}
Backend получит URI без префикса /api/:
/users
Разница критична при проксировании приложений под префиксом.
Заголовок Host
proxy_set_header Host $host;
Передаёт приложению исходный домен клиента:
app.example.com
Это важно для:
- генерации абсолютных ссылок;
- проверки allowed hosts;
- логирования;
- маршрутизации приложения;
- multi-tenant систем.
X-Real-IP
proxy_set_header X-Real-IP $remote_addr;
Передаёт непосредственный IP клиента.
X-Forwarded-For
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
Добавляет IP клиента в цепочку proxy.
Приложение должно доверять этому заголовку только от собственного reverse proxy.
X-Forwarded-Proto
proxy_set_header X-Forwarded-Proto $scheme;
Сообщает приложению исходную схему:
http
или:
https
Это важно для корректных redirect и secure cookies.
Включение server block
sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/app.example.com
Проверьте:
ls -l /etc/nginx/sites-enabled/
Проверка конфигурации
sudo nginx -t
Ожидаемый результат:
syntax is ok
test is successful
До успешной проверки не выполняйте reload.
Применение конфигурации
sudo systemctl reload nginx
Проверьте:
systemctl status nginx --no-pager
Проверка до обновления DNS
curl -I -H 'Host: app.example.com' http://127.0.0.1
Проверка содержимого:
curl -H 'Host: app.example.com' http://127.0.0.1
Проверка через resolve
curl -I --resolve app.example.com:80:SERVER_IP http://app.example.com
Так можно проверить server block независимо от DNS-кэша.
Проверка через домен
curl -I http://app.example.com
Ожидается ответ backend-приложения через Nginx.
Проверка журналов Nginx
Журнал доступа:
sudo tail -50 /var/log/nginx/app.example.com.access.log
Журнал ошибок:
sudo tail -50 /var/log/nginx/app.example.com.error.log
Наблюдение в реальном времени:
sudo tail -f /var/log/nginx/app.example.com.error.log
Проверка журнала приложения
sudo journalctl -u APP_SERVICE -f
Создайте запрос через Nginx и убедитесь, что он дошёл до приложения.
Настройка таймаутов
Для обычного веб-приложения можно оставить стандартные значения.
Если backend выполняет долгие запросы:
location / {
proxy_pass http://127.0.0.1:3000;
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
proxy_connect_timeout
Максимальное время подключения к backend.
proxy_send_timeout
Максимальный интервал при отправке запроса backend.
proxy_read_timeout
Максимальный интервал ожидания данных от backend.
Не увеличивайте таймауты без анализа причины медленного ответа.
Ограничение размера тела запроса
Для приложений с загрузкой файлов:
client_max_body_size 20m;
Пример:
server {
client_max_body_size 20m;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
Настройка лимита загрузки будет подробно рассмотрена в отдельной статье, поэтому здесь достаточно знать место директивы.
Отключение proxy buffering
Для streaming или Server-Sent Events иногда требуется:
proxy_buffering off;
Не отключайте buffering для всех приложений без необходимости.
Server-Sent Events
Пример:
location /events/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_buffering off;
proxy_cache off;
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 только если приложение действительно поддерживает SSE.
Проксирование на другой сервер
Backend может находиться на другом узле:
proxy_pass http://192.0.2.20:3000;
Проверьте доступность:
curl -I http://192.0.2.20:3000
Проверьте маршрут:
ip route get 192.0.2.20
Backend-порт должен быть разрешён только для reverse proxy, а не для всего интернета.
Использование DNS-имени backend
proxy_pass http://app-backend.example.internal:3000;
Проверьте разрешение:
getent hosts app-backend.example.internal
Для динамически меняющихся DNS-записей поведение Nginx требует отдельной настройки resolver.
Проксирование нескольких приложений
Каждому домену лучше создать отдельный server block:
app1.example.com → 127.0.0.1:3001
app2.example.com → 127.0.0.1:3002
Не смешивайте независимые приложения в одном конфигурационном файле без необходимости.
Проверка backend напрямую и через Nginx
Backend:
curl -I http://127.0.0.1:3000
Reverse proxy:
curl -I http://app.example.com
Сравните:
- HTTP-код;
- заголовки;
- время ответа;
- содержимое;
- redirect.
Ошибка 502 Bad Gateway
Основные причины:
- backend не запущен;
- неверный порт;
- приложение слушает другой адрес;
- firewall блокирует соединение;
- Unix socket недоступен;
- backend завершает соединение;
- используется HTTPS вместо HTTP или наоборот.
Проверка:
curl -I http://127.0.0.1:3000
Проверка порта:
sudo ss -lntp |
grep ':3000'
Журнал:
sudo tail -100 /var/log/nginx/app.example.com.error.log
Ошибка 504 Gateway Timeout
Backend не ответил за время proxy_read_timeout.
Проверьте:
- журнал приложения;
- базу данных;
- внешние API;
- блокировки;
- нагрузку CPU;
- долгие запросы.
Не решайте проблему только увеличением таймаута.
Ошибка connection refused
Пример в журнале:
connect() failed (111: Connection refused)
Это означает, что по указанному адресу никто не слушает порт.
Проверьте службу приложения.
Ошибка permission denied для Unix socket
Если используется:
proxy_pass http://unix:/run/app/app.sock;
проверьте:
ls -l /run/app/app.sock
Пользователь www-data должен иметь доступ к socket.
Unix socket требует отдельной настройки владельца и группы.
Приложение создаёт redirect на внутренний адрес
Например:
Location: http://127.0.0.1:3000/login
Проверьте:
Host;X-Forwarded-Proto;- external URL приложения;
- trusted proxies;
- public base URL;
- allowed hosts.
Исправление обычно требуется и в конфигурации самого приложения.
Неверный IP клиента в приложении
Приложение может видеть только:
127.0.0.1
Проверьте передачу:
X-Real-IP
X-Forwarded-For
Затем настройте trusted proxy в приложении.
Не доверяйте X-Forwarded-For от произвольных клиентов.
WebSocket не подключается
Проверьте наличие:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
Проверьте map в блоке http.
Проверьте журнал браузера и приложения.
Проверка WebSocket через браузер
Откройте Developer Tools:
Network → WS
Проверьте код переключения протокола:
101 Switching Protocols
Если возвращается 400, 404 или 502, проверьте route и backend.
Приложение возвращает 404 только через proxy
Проверьте завершающий слеш в proxy_pass.
Сравните:
proxy_pass http://127.0.0.1:3000;
и:
proxy_pass http://127.0.0.1:3000/;
Проверьте фактический URI в журнале backend.
Проверка активной конфигурации
sudo nginx -T |
grep -nE 'server_name app.example.com|proxy_pass|proxy_set_header'
Отключение reverse proxy
Удалите ссылку:
sudo rm -f /etc/nginx/sites-enabled/app.example.com
Проверьте:
sudo nginx -t
Примените:
sudo systemctl reload nginx
Файл в sites-available сохранится.
Полное удаление конфигурации
После отключения:
sudo rm -f /etc/nginx/sites-available/app.example.com
Проверьте и примените:
sudo nginx -t &&
sudo systemctl reload nginx
Безопасный порядок настройки
- Проверить backend напрямую.
- Проверить порт и процесс.
- Проверить DNS.
- Создать server block.
- Передать основные proxy headers.
- Добавить WebSocket только при необходимости.
- Выполнить
nginx -t. - Выполнить reload.
- Проверить запрос через Host.
- Проверить домен.
- Проверить журналы Nginx и приложения.
- Только после этого настраивать HTTPS.
Быстрый набор команд
Проверка backend:
curl -I http://127.0.0.1:3000
Проверка порта:
sudo ss -lntp |
grep ':3000'
Проверка Nginx:
sudo nginx -t
Применение:
sudo systemctl reload nginx
Проверка proxy:
curl -I -H 'Host: app.example.com' http://127.0.0.1
Журнал ошибок:
sudo tail -100 /var/log/nginx/app.example.com.error.log
Итог
После выполнения инструкции:
- проверен внутренний backend;
- создан отдельный reverse proxy server block;
- переданы исходные заголовки клиента;
- добавлена поддержка WebSocket;
- проверен доступ через домен;
- рассмотрены таймауты;
- разобраны ошибки
502и504.
Следующим самостоятельным этапом можно ограничить доступ к отдельному сайту или location по IP-адресу.