Настройка 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

Безопасный порядок настройки

  1. Проверить backend напрямую.
  2. Проверить порт и процесс.
  3. Проверить DNS.
  4. Создать server block.
  5. Передать основные proxy headers.
  6. Добавить WebSocket только при необходимости.
  7. Выполнить nginx -t.
  8. Выполнить reload.
  9. Проверить запрос через Host.
  10. Проверить домен.
  11. Проверить журналы Nginx и приложения.
  12. Только после этого настраивать 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-адресу.

← Предыдущая статья Настройка HTTPS в Nginx через Certbot Следующая статья → Ограничение доступа по IP в Nginx