Настройка Nginx как обратного прокси
Настройте Nginx как reverse proxy для Node.js, Ollama и других бэкендов. Разбираем proxy_pass, проброс заголовков, WebSocket, upstream-балансировку и таймауты для продакшена с проверкой на каждом шагу.
Обратный прокси (reverse proxy) стоит между клиентами и вашим бэкенд-приложением. Nginx принимает входящие запросы, пересылает их на бэкенд-сервер (Node.js, Python, Go, Ollama) и возвращает ответ клиенту. Это позволяет добавить TLS-терминацию, балансировку нагрузки, кеширование и контроль доступа без изменения приложения.
В этом руководстве разбираем настройку reverse proxy в Nginx: от базового proxy_pass до WebSocket-проксирования, upstream-балансировки и продакшен-конфигурации для Ollama.
Необходимые условия
Перед началом вам понадобится:
- VPS на Debian 12 или Ubuntu 24.04 с root или sudo-доступом
- Установленный и запущенный Nginx (Установка Nginx на Debian и Ubuntu)
- Бэкенд-приложение, слушающее локальный порт (в руководстве есть тестовые примеры)
- Базовое понимание структуры конфигурационных файлов Nginx (Структура конфиг-файлов Nginx)
Убедитесь, что Nginx запущен:
sudo systemctl status nginx
В выводе должно быть active (running).
Как настроить proxy_pass в Nginx?
Директива proxy_pass указывает, куда Nginx должен пересылать запросы. Она размещается внутри блока location, который находится в блоке server. Nginx отправляет запрос клиента на указанный URL бэкенда и передает ответ обратно. Директива принимает URL по HTTP и HTTPS, может указывать на IP-адрес, доменное имя или Unix-сокет.
Создайте новый файл серверного блока:
sudo nano /etc/nginx/sites-available/app.conf
Добавьте минимальную конфигурацию reverse proxy:
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Включите сайт и проверьте конфигурацию:
sudo ln -s /etc/nginx/sites-available/app.conf /etc/nginx/sites-enabled/
sudo nginx -t
Если nginx -t вернул syntax is ok и test is successful, перезагрузите:
sudo systemctl reload nginx
Проверьте работу прокси:
curl -I http://app.example.com
В заголовках ответа должны быть данные от вашего бэкенда. Если видите 502 Bad Gateway, значит бэкенд не запущен на порту 3000.
Что происходит с trailing slash в proxy_pass?
Завершающий слеш (trailing slash) в proxy_pass управляет перезаписью URI. Это одна из самых частых причин ошибок в конфигурации.
| Конфигурация | Запрос | Передается на бэкенд |
|---|---|---|
proxy_pass http://127.0.0.1:3000 |
/app/users |
http://127.0.0.1:3000/app/users |
proxy_pass http://127.0.0.1:3000/ |
/app/users |
http://127.0.0.1:3000/users |
proxy_pass http://127.0.0.1:3000/v2/ |
/app/users |
http://127.0.0.1:3000/v2/users |
proxy_pass http://127.0.0.1:3000/v2 |
/app/users |
http://127.0.0.1:3000/v2users |
Правило: когда proxy_pass содержит URI (что угодно после host:port, даже просто /), Nginx отрезает совпадающий префикс location из URI запроса и добавляет остаток к URI в proxy_pass. Когда URI в proxy_pass нет, исходный путь запроса передается без изменений.
Для блока location /app/ применимы примеры выше. Обратите внимание на четвертую строку: без trailing slash после /v2 путь становится /v2users вместо /v2/users. Всегда добавляйте завершающий слеш, когда указываете путь.
Какие заголовки нужно пробрасывать на бэкенд?
Без явной настройки заголовков бэкенд не видит реальный IP клиента, исходный протокол и запрошенное имя хоста. Nginx подменяет их по умолчанию. Пробрасывайте вручную.
| Заголовок | Значение | Назначение |
|---|---|---|
Host |
$host |
Сохраняет оригинальный заголовок Host, чтобы бэкенд знал, какой домен запрошен |
X-Real-IP |
$remote_addr |
Передает IP-адрес клиента на бэкенд |
X-Forwarded-For |
$proxy_add_x_forwarded_for |
Добавляет IP клиента в цепочку прокси |
X-Forwarded-Proto |
$scheme |
Сообщает бэкенду, использовался HTTP или HTTPS |
Добавьте заголовки в блок location:
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;
}
После перезагрузки Nginx проверьте, что заголовки доходят до бэкенда. Если приложение логирует входящие заголовки, посмотрите:
sudo systemctl reload nginx
curl -s http://app.example.com/headers
Бэкенд должен видеть X-Real-IP с IP клиента, а не 127.0.0.1. Если показывает 127.0.0.1, директивы proxy_set_header отсутствуют или указаны в неправильном контексте.
Про безопасность: используйте $proxy_add_x_forwarded_for вместо $remote_addr для X-Forwarded-For. Он добавляет IP клиента к существующему заголовку X-Forwarded-For. Если поставить только $remote_addr, вы потеряете цепочку прокси, что сломает отслеживание IP за несколькими прокси. Если Nginx -- единственный прокси, оба варианта эквивалентны.
Как проксировать приложение на Node.js?
Вот полный серверный блок для проксирования Node.js-приложения на порту 3000. Пример включает проброс заголовков, поддержку WebSocket и скрытие версии.
Создайте серверный блок:
sudo nano /etc/nginx/sites-available/nodeapp.conf
server {
listen 80;
server_name nodeapp.example.com;
# Hide Nginx version in responses
server_tokens off;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
# Header forwarding
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;
# WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
}
Включите, протестируйте и перезагрузите:
sudo ln -s /etc/nginx/sites-available/nodeapp.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Проверьте работу прокси:
curl -I http://nodeapp.example.com
Обратите внимание: в ответе не должно быть строки Server: nginx/1.x.x. Директива server_tokens off скрывает номер версии. Раскрытие версии помогает атакующим нацеливаться на известные уязвимости.
Для TLS-терминации с Let's Encrypt смотрите Nginx SSL/TLS с Let's Encrypt.
Как проксировать WebSocket-соединения через Nginx?
WebSocket-соединения начинаются как HTTP-запрос с заголовком Upgrade, затем переключаются на постоянное двунаправленное соединение. По умолчанию Nginx использует HTTP/1.0 для upstream-соединений, что не поддерживает механизм Upgrade. Нужны три директивы: proxy_http_version в 1.1, проброс заголовка Upgrade и установка Connection в "upgrade".
Для location-блоков, обслуживающих и обычный HTTP, и WebSocket-трафик, используйте директиву map для условной установки заголовка Connection. Добавьте в блок http в /etc/nginx/nginx.conf:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
Затем используйте $connection_upgrade в серверном блоке:
server {
listen 80;
server_name ws.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Prevent idle timeout killing WebSocket connections
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
По умолчанию proxy_read_timeout равен 60 секундам. Если за это время данные не проходят, Nginx закрывает соединение. Для WebSocket ставьте значение выше. Ваше приложение должно отправлять WebSocket ping-фреймы с интервалом меньше этого таймаута.
Проверьте конфигурацию:
sudo nginx -t && sudo systemctl reload nginx
Для проверки WebSocket-подключения установите wscat:
npm install -g wscat
wscat -c ws://ws.example.com/
Если соединение открылось, WebSocket-проксирование работает. Если получили unexpected server response (200), заголовки Upgrade не пробрасываются. Проверьте, что директива map находится внутри блока http, а не внутри блока server.
Как настроить reverse proxy для Ollama?
Ollama по умолчанию обслуживает LLM-инференс на порту 11434 и слушает только 127.0.0.1. Проксирование через Nginx позволяет добавить TLS, аутентификацию и контроль доступа без изменения конфигурации Ollama. Главное отличие от стандартного проксирования: инференс LLM может занимать минуты, а для стриминговых ответов нужно отключить буферизацию.
Создайте серверный блок:
sudo nano /etc/nginx/sites-available/ollama.conf
server {
listen 80;
server_name ollama.example.com;
server_tokens off;
# Restrict access to specific IPs
allow 192.168.1.0/24;
allow 10.0.0.0/8;
deny all;
location / {
proxy_pass http://127.0.0.1:11434;
proxy_http_version 1.1;
# Header forwarding
proxy_set_header Host localhost:11434;
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 Connection '';
# Disable buffering for streaming responses
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding on;
# Extended timeouts for LLM inference
proxy_connect_timeout 300s;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
}
Включите и проверьте:
sudo ln -s /etc/nginx/sites-available/ollama.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Убедитесь, что Ollama отвечает через прокси:
curl http://ollama.example.com/api/tags
Должен вернуться JSON со списком доступных моделей. Если получили 403 Forbidden, IP клиента не в списке allow. Если 502 Bad Gateway, Ollama не запущен:
sudo systemctl status ollama
Протестируйте стриминговую генерацию:
curl -N http://ollama.example.com/api/generate -d '{
"model": "llama3.2",
"prompt": "Hello",
"stream": true
}'
Обратите внимание: флаг -N отключает буферизацию вывода curl. Токены должны приходить по одному. Если весь ответ приходит разом, proxy_buffering off не установлен или перезаписан.
Почему эти настройки отличаются от стандартного прокси:
proxy_buffering off: обычно Nginx буферизует ответы бэкенда и отправляет их пакетом. Для LLM-стриминга каждый токен должен отправляться клиенту немедленно.proxy_read_timeout 600s: инференс на больших моделях может занимать несколько минут. Дефолтный таймаут в 60 секунд оборвет соединение посреди генерации.proxy_set_header Host localhost:11434: Ollama проверяет заголовок Host и отклоняет запросы, не совпадающие с его bind-адресом.proxy_set_header Connection '': очищает заголовок Connection, чтобы предотвратить проблемы с keep-alive при chunked-стриминге.
Для TLS-терминации и безопасного доступа к Ollama через интернет смотрите Nginx SSL/TLS с Let's Encrypt. Никогда не открывайте Ollama без контроля доступа. Для продакшена комбинируйте IP-ограничения с HTTP Basic Auth или валидацией API-ключей.
Чтобы добавить HTTP Basic Auth поверх IP-ограничений:
sudo apt install apache2-utils
sudo htpasswd -c /etc/nginx/.ollama_htpasswd apiuser
sudo chmod 640 /etc/nginx/.ollama_htpasswd
sudo chown root:www-data /etc/nginx/.ollama_htpasswd
Затем добавьте в блок location в ollama.conf:
auth_basic "Ollama API";
auth_basic_user_file /etc/nginx/.ollama_htpasswd;
Проверьте права доступа к файлу:
ls -la /etc/nginx/.ollama_htpasswd
Файл должен показывать -rw-r----- с владельцем root и группой www-data. Ограничение прав не дает другим пользователям на сервере читать хеши паролей.
Как настроить upstream-балансировку нагрузки?
Блок upstream определяет группу бэкенд-серверов, между которыми Nginx распределяет запросы. По умолчанию используется взвешенный round-robin. Каждый сервер получает долю запросов пропорционально своему весу (вес по умолчанию: 1).
Добавьте блок upstream перед блоком server:
upstream app_backends {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://app_backends;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Методы балансировки
Round-robin (по умолчанию): запросы распределяются равномерно между серверами. Дополнительная директива не нужна.
Least connections (наименьшее количество соединений): запросы идут на сервер с наименьшим числом активных соединений. Лучше подходит для бэкендов с разным временем ответа:
upstream app_backends {
least_conn;
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
IP hash: привязывает каждый IP клиента к конкретному бэкенду. Полезно для сохранения сессий без sticky cookies:
upstream app_backends {
ip_hash;
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
Проверки состояния: max_fails и fail_timeout
Nginx пассивно следит за здоровьем бэкендов. Если сервер не отвечает, Nginx помечает его как недоступный и прекращает отправку запросов на определенный период.
upstream app_backends {
server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3002 backup;
}
| Параметр | По умолчанию | Описание |
|---|---|---|
max_fails |
1 | Количество неудачных попыток за fail_timeout до пометки сервера как недоступного |
fail_timeout |
10s | Окно подсчета ошибок и период недоступности сервера |
backup |
- | Сервер получает запросы только когда все основные серверы недоступны |
weight |
1 | Относительная доля запросов при round-robin |
После настройки upstream протестируйте и перезагрузите:
sudo nginx -t && sudo systemctl reload nginx
Проверьте работу балансировки, отправив несколько запросов:
for i in $(seq 1 6); do curl -s http://app.example.com/health; echo; done
Если бэкенды возвращают идентификатор в ответе, вы увидите распределение запросов между ними.
Как настроить буферизацию и таймауты прокси?
По умолчанию Nginx буферизует ответы бэкенда. Он считывает весь ответ в память (или на диск, если превышен буфер) и затем отправляет клиенту. Это эффективно для большинства приложений, но не подходит для стриминга, Server-Sent Events (SSE) или long-polling.
Директивы таймаутов
| Директива | По умолчанию | Рекомендуется | Назначение |
|---|---|---|---|
proxy_connect_timeout |
60s | 5-10s | Время установки соединения с бэкендом. Ставьте короткое для быстрого отказа. |
proxy_read_timeout |
60s | 60-300s | Ожидание ответа от бэкенда. Увеличьте для медленных API. |
proxy_send_timeout |
60s | 60s | Время отправки тела запроса на бэкенд. Увеличьте для больших загрузок. |
location / {
proxy_pass http://127.0.0.1:3000;
proxy_connect_timeout 10s;
proxy_read_timeout 120s;
proxy_send_timeout 60s;
}
Эти таймауты действуют между двумя последовательными операциями чтения/записи, а не на весь запрос целиком. Ответ, отправляющий данные каждые 30 секунд, никогда не вызовет срабатывание 60-секундного proxy_read_timeout.
Управление буферизацией
| Директива | По умолчанию | Описание |
|---|---|---|
proxy_buffering |
on | Буферизовать полный ответ бэкенда перед отправкой клиенту |
proxy_buffer_size |
4k или 8k | Буфер для первой части ответа (заголовки) |
proxy_buffers |
8 4k или 8 8k | Количество и размер буферов для тела ответа |
proxy_busy_buffers_size |
8k или 16k | Максимальный размер буферов, занятых отправкой клиенту |
Когда отключать буферизацию?
Отключайте буферизацию, когда бэкенд стримит данные: LLM-инференс (Ollama, vLLM), Server-Sent Events, длинные WebSocket-подобные ответы или любое API с инкрементальной отправкой chunked-данных.
location /stream/ {
proxy_pass http://127.0.0.1:8080;
proxy_buffering off;
}
Оставляйте буферизацию включенной для обычных запросов-ответов. Буферизация защищает бэкенд от медленных клиентов: Nginx быстро забирает ответ и отдает клиенту в его темпе. Без буферизации медленный клиент держит соединение с бэкендом открытым.
Для продвинутой оптимизации производительности смотрите .
Как диагностировать ошибки 502 и 504?
502 Bad Gateway означает, что Nginx не смог подключиться к бэкенду или бэкенд вернул некорректный ответ. 504 Gateway Timeout означает, что бэкенд не ответил за время proxy_read_timeout.
Чеклист для 502 Bad Gateway
- Бэкенд запущен?
ss -tlnp | grep 3000
Если вывод пустой, бэкенд не слушает порт 3000. Запустите его.
-
URL в proxy_pass правильный? Проверьте опечатки в номере порта или IP-адресе. Частая ошибка: использование
https://для бэкенда, который работает только по HTTP. -
SELinux блокирует соединение? (RHEL/CentOS)
sudo setsebool -P httpd_can_network_connect 1
- Проверьте лог ошибок Nginx:
sudo tail -20 /var/log/nginx/error.log
Ищите connect() failed (111: Connection refused) или no live upstreams.
Чеклист для 504 Gateway Timeout
- Увеличьте proxy_read_timeout:
proxy_read_timeout 300s;
- Бэкенд работает медленно?
time curl http://127.0.0.1:3000/slow-endpoint
Если запрос длится дольше proxy_read_timeout, увеличьте таймаут или оптимизируйте бэкенд.
- Проверьте состояние upstream: если используются upstream-блоки, все серверы могут быть помечены как недоступные. Поищите в логе ошибок
no live upstreams while connecting to upstream.
Чтение логов Nginx
Nginx пишет ошибки в /var/log/nginx/error.log. Для мониторинга в реальном времени:
sudo journalctl -u nginx -f
Или смотрите лог напрямую:
sudo tail -f /var/log/nginx/error.log
Для отдельных логов по сайтам добавьте директивы access_log и error_log в серверный блок:
server {
listen 80;
server_name app.example.com;
access_log /var/log/nginx/app-access.log;
error_log /var/log/nginx/app-error.log;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
Это разделяет логи по приложениям и упрощает отладку проблем конкретного бэкенда.
Частые проблемы с заголовками
Если бэкенд получает 127.0.0.1 вместо реального IP клиента, не хватает директивы proxy_set_header X-Real-IP $remote_addr. Если приложение генерирует ссылки с http:// вместо https://, заголовок X-Forwarded-Proto не пробрасывается. Некоторые фреймворки (Express, Django, Rails) требуют явной настройки для доверия прокси-заголовкам. В Express:
app.set('trust proxy', 1);
В Django установите SECURE_PROXY_SSL_HEADER:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
Без этих настроек приложение игнорирует проброшенные заголовки, даже если Nginx отправляет их корректно.
Справочник: директивы проксирования
Для быстрого обращения -- минимальный набор директив для разных сценариев проксирования:
# Standard HTTP proxy
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;
}
# WebSocket proxy
location /ws/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
}
# Streaming proxy (SSE, LLM)
location /stream/ {
proxy_pass http://127.0.0.1:11434;
proxy_http_version 1.1;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 600s;
}
Для серверных блоков с несколькими доменами смотрите Серверные блоки Nginx. Для усиления безопасности, включая rate limiting и контроль доступа, смотрите .
Copyright 2026 Virtua.Cloud. Vse prava zashchishcheny.
Готовы попробовать?
Разверните свой сервер за секунды. Linux, Windows или FreeBSD.
Смотреть тарифы VPS