Top.Mail.Ru

Nginx как reverse proxy: настройка и конфигурация

6
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

Похожее

Все статьи
nvm node.js

Node.js через NVM и PM2: установка и запуск на Ubuntu

NVM (Node Version Manager) позволяет устанавливать несколько версий Node.js на одном сервере и переключаться между ними без переустановки. PM2 — менеджер процессов, который держит Node.js-приложение запущенным в фоне, перезапускает его при падении и добавляет в автозагрузку. Вместе они закрывают большинство…

124877

Какая CMS лучше подходит для разных типов сайтов

Выбор системы управления сайтом — это всегда компромисс в какой-то степени компромисс между сложностью, доступностью и привычностью. Нельзя просто так взять и сказать: «Вот конкретно именно эта CMS самая лучшая».  Под каждую задачу есть свой инструмент. Поэтому более корректно спрашивать:…