Reverse proxy на nginx: настройка с нуля

Проксирование в nginx: настройка reverse proxy с нуля

Марина
Марина
📅 18 сентября 2026
Проксирование в nginx: настройка reverse proxy с нуля

Проксирование в nginx — это когда веб-сервер принимает запрос из интернета на порт 80 или 443, а затем передаёт его дальше, вашему приложению, которое слушает локальный порт вроде 3000 или 8000. Само приложение при этом закрыто от внешнего мира, а nginx берёт на себя HTTPS, сжатие ответов, отдачу картинок и распределение нагрузки между несколькими копиями кода. Настраивается схема одной директивой proxy_pass и четырьмя строками заголовков, а работает даже на самом дешёвом VPS с nginx: хватает 1 ядра и 1 ГБ памяти.

Материал для тех, кто уже написал сайт на Node.js, Python или PHP-фреймворке и запустил его на сервере. Осталось непонятным одно: как открыть его миру по нормальному домену вместо адреса с портом. Разберём минимальный конфиг, обязательные заголовки, сертификат, WebSocket, балансировку и ошибки 502 и 504.

Что такое обратный прокси и что он даёт приложению

Обратный прокси — это сервер-посредник, который стоит перед вашим приложением и принимает на себя все входящие запросы. Клиент из браузера видит только его, о существовании приложения на localhost:3000 он не знает вообще. Обратный он потому, что защищает и обслуживает сторону сервера, а не сторону клиента. В связке участвуют три стороны: браузер, nginx на портах 80 и 443 и приложение на локальном порту 3000.

Встроенные веб-серверы фреймворков — Gunicorn у Python, http-модуль у Node.js — плохо переносят медленных клиентов и потоки статики. Nginx спроектирован ровно под это: один рабочий процесс держит тысячи одновременных соединений при потреблении в 10–20 МБ памяти.

Второй аргумент — безопасность. Приложение слушает 127.0.0.1, то есть внутренний интерфейс сервера, и снаружи к нему подключиться нельзя: открытым остаётся один порт 443. Третий — гибкость: под одним доменом собирают несколько приложений, отдавая путь /api на порт 8000, а остальное на 3000. И наконец, прокси — единственная точка, где удобно держать сертификат и кеш.

Минимальный конфиг: одна директива proxy_pass

Конфигурация живёт в каталоге /etc/nginx. Виртуальные хосты (в терминологии nginx — блоки server) кладут отдельными файлами в /etc/nginx/sites-available и включают символической ссылкой из /etc/nginx/sites-enabled. Если веб-сервер ещё не стоит, начните с установки nginx на Ubuntu.

Самый короткий рабочий вариант выглядит так:

server {
    listen 80;
    server_name example.ru www.example.ru;

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}

Директива listen 80 велит принимать обычные HTTP-запросы, server_name задаёт домены блока, а proxy_pass пересылает запрос приложению. Адрес пишем как 127.0.0.1, а не localhost: во втором случае nginx может пойти по IPv6-адресу ::1, где приложение не слушает, и вернуть 502.

Обратите внимание на слеш в конце адреса в proxy_pass. Без слеша путь уходит как есть: /users придёт бэкенду как /users. А запись со слешем внутри блока location /api/ отрежет префикс /api — приложение получит просто /. Это самая частая причина внезапных ошибок 404 после переезда. Если сомневаетесь, начинайте без слеша и смотрите в логи приложения, какой путь до него доходит.

Схема обратного прокси перед приложением на порту 3000
Наружу открыт единственный порт 443, а приложение слушает внутренний интерфейс и снаружи недоступно вовсе.

Заголовки: без них приложение видит вместо клиента сам nginx

Конфиг выше уже работает, но приложение в такой схеме слепо: оно видит запрос с адреса 127.0.0.1, домен 127.0.0.1:3000 и протокол http. В логах будет один IP на все посещения, аналитика сломается, а редиректы уведут пользователя на несуществующий адрес.

Чинится это директивами proxy_set_header: каждая добавляет к запросу служебный заголовок с настоящими данными клиента. Переменные вроде $host и $remote_addr nginx подставляет сам.

Строка конфига Что передаёт бэкенду Что сломается без неё
proxy_set_header Host $host; Домен из адресной строки Редиректы и cookie уводят на 127.0.0.1:3000
proxy_set_header X-Real-IP $remote_addr; IP-адрес посетителя В логах приложения один адрес на всех
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; Цепочку адресов, если прокси несколько Не работают лимиты и баны по IP
proxy_set_header X-Forwarded-Proto $scheme; Протокол: http или https Приложение строит ссылки на http, браузер ругается на смешанный контент

Эти четыре строки добавляют внутрь блока location, сразу после proxy_pass. Чтобы не копировать их в каждый виртуальный хост, вынесите их в файл /etc/nginx/proxy_params и подключайте строкой include proxy_params;. Само приложение тоже надо научить доверять заголовкам: в Django это USE_X_FORWARDED_HOST, в Express — app.set('trust proxy', 1).

Пошагово: настраиваем проксирование на Ubuntu за 20 минут

Ниже маршрут от чистого сервера до работающего домена. Предполагаем Ubuntu 24.04, приложение уже запущено как служба systemd и слушает порт 3000. Systemd — штатный менеджер служб в Linux: он сам поднимает программу после перезагрузки сервера. Команды выполняйте от root (пользователя с полными правами) или через sudo.

Проверяйте каждый шаг отдельно, а не пишите весь конфиг сразу. Если сервера ещё нет, подойдёт любой тариф из подборки VPS с Ubuntu 24.04: 1 ядра и 1 ГБ памяти хватит для сайта с посещаемостью до нескольких тысяч человек в сутки.

  1. Проверьте, что приложение слушает локальный порт: ss -tlnp | grep 3000. Команда показывает, какие программы заняли какие порты. В выводе должен быть адрес 127.0.0.1:3000, а не 0.0.0.0:3000.
  2. Установите веб-сервер: apt update && apt install nginx -y. Проверьте, что он поднялся: systemctl status nginx.
  3. Откройте в файрволе (сетевом фильтре сервера) только веб-порты: ufw allow 80,443/tcp. Порт 3000 наружу открывать не нужно.
  4. Создайте файл /etc/nginx/sites-available/app.conf с блоком server из примеров выше — с proxy_pass и четырьмя заголовками.
  5. Включите сайт и уберите дефолтный: ln -s /etc/nginx/sites-available/app.conf /etc/nginx/sites-enabled/ и rm /etc/nginx/sites-enabled/default.
  6. Проверьте синтаксис командой nginx -t — она покажет строку с ошибкой. Только после успешной проверки применяйте: systemctl reload nginx.
  7. Направьте A-запись домена на IP сервера. A-запись — строка в настройках домена, которая говорит браузеру, на какой адрес идти. Подождите 5–30 минут и откройте сайт в браузере.

Команда reload отличается от restart тем, что не рвёт активные соединения: старые процессы дорабатывают запросы, новые стартуют с обновлённым конфигом.

HTTPS на фронте: сертификат живёт в nginx, а не в приложении

Когда nginx стоит перед приложением, шифрование завершают на нём. Схема называется SSL-termination: снаружи идёт защищённое соединение по SSL/TLS, а внутри сервера трафик бежит по обычному http на 127.0.0.1 и за пределы машины не выходит. Приложению про сертификаты знать не нужно.

Бесплатный сертификат Let’s Encrypt выдаётся на 90 дней и продлевается автоматически. Ставят его утилитой certbot: её плагин для nginx сам допишет в конфиг пути к сертификату и редирект с 80 порта на 443. Пошаговый разбор — в руководстве про установку SSL от Let’s Encrypt.

После установки блок сервера будет содержать listen 443 ssl;, строки ssl_certificate и ssl_certificate_key, а также отдельный блок с listen 80; и постоянным редиректом. Не забудьте добавить proxy_set_header X-Forwarded-Proto $scheme; — без него приложение продолжит считать, что работает по http. Заодно включите HTTP/2 директивой http2 on;: на странице с 40–60 мелкими файлами он экономит заметную долю времени загрузки.

WebSocket и долгие соединения через прокси

Чаты, уведомления и панели мониторинга держат постоянное соединение по протоколу WebSocket. Оно начинается как обычный HTTP-запрос, но потом клиент просит сервер переключить протокол заголовком Upgrade. По умолчанию nginx его бэкенду не передаёт, и соединение обрывается с ошибкой 400 — типичная жалоба «локально чат работает, на сервере нет».

Лечится тремя строками в блоке location, который отвечает за сокеты:

location /ws/ {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 3600s;
}

Строка proxy_http_version 1.1 обязательна: по умолчанию к бэкенду nginx ходит по HTTP/1.0, а тот переключение протокола не поддерживает. Директива proxy_read_timeout задаёт, сколько ждать данных от приложения — стандартные 60 секунд для чата мало, разумно ставить от 600 до 3600 секунд.

Для длинных потоков ответов (стриминг текста, server-sent events) дополнительно отключают буферизацию строкой proxy_buffering off;. Без этого nginx копит ответ и отдаёт его целиком, из-за чего текст появляется не по мере генерации, а разом в конце.

Балансировка: блок upstream и распределение между копиями приложения

Когда одного процесса перестаёт хватать, запускают несколько копий приложения на портах 3000, 3001, 3002 и распределяют запросы между ними. В nginx это делает блок upstream: он описывает группу серверов, а proxy_pass ссылается на неё по имени.

upstream app_backend {
    least_conn;
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
    server 127.0.0.1:3002 backup;
    keepalive 32;
}

server {
    listen 80;
    location / {
        proxy_pass http://app_backend;
        include proxy_params;
    }
}

По умолчанию действует round-robin: запросы уходят серверам по очереди поровну. Остальные режимы включаются одной строкой внутри блока.

Режим Строка в upstream Как распределяет Когда выбирать
Round-robin ничего писать не нужно По очереди, поровну Одинаковые копии, быстрые ответы
Least connections least_conn; Тому, у кого меньше активных соединений Отчёты, выгрузки, долгие запросы
IP hash ip_hash; Один клиент всегда на один сервер Сессии хранятся в памяти процесса
Веса server …:3001 weight=3; Пропорционально числу, тут 3 к 1 Серверы разной мощности
Резерв server …:3002 backup; Только когда основные не отвечают Страховка на время обновления

Строка keepalive 32 держит до 32 постоянных соединений с бэкендом и снимает по 1–3 мс с каждого запроса. Если балансировать нужно между разными машинами, вместо 127.0.0.1 указывают их внутренние адреса; для сложных сценариев с проверками здоровья чаще берут HAProxy.

Ошибки 502 и 504: буферы, таймауты и размер тела запроса

Почти все проблемы проксирования сводятся к четырём кодам ответа. Логи лежат в /var/log/nginx/error.log — открывайте их командой tail -n 50 /var/log/nginx/error.log сразу после того, как воспроизвели ошибку, и читайте последнюю строку.

Ключевое различие: 502 означает, что nginx не смог достучаться до приложения вообще, а 504 — что достучался, но не дождался ответа. Первое чинят на стороне приложения, второе — таймаутами или оптимизацией кода.

Код Что произошло Что делать
502 Bad Gateway Приложение упало или слушает другой порт Проверить systemctl status и ss -tlnp
504 Gateway Time-out Ответ шёл дольше 60 секунд по умолчанию Поднять proxy_read_timeout до 120–300 секунд
413 Request Entity Too Large Файл больше лимита в 1 МБ Задать client_max_body_size 50m;
400 Bad Request на сокетах Не переданы Upgrade и Connection Добавить три строки из раздела выше

Ещё один симптом — предупреждение в логе о том, что ответ записан во временный файл: он не поместился в буфер по умолчанию (8 блоков по 4 или 8 КБ). Лечится строкой proxy_buffers 16 16k;.

Таблица ошибок 502, 504, 413 и 400 при проксировании
Различить два главных кода просто: 502 значит, что nginx не достучался до приложения, 504 — что достучался, но не дождался ответа.

Статика мимо приложения и кеширование ответов

Отдавать картинки, шрифты и скрипты через приложение расточительно: каждый такой запрос занимает процесс, который мог бы считать бизнес-логику. Nginx отдаёт файл с диска на порядок дешевле, поэтому статику перехватывают отдельным блоком location до общего правила.

location /static/ {
    alias /var/www/app/static/;
    expires 30d;
    access_log off;
}

Директива expires 30d добавляет к ответу заголовок с датой устаревания, и браузер 30 дней берёт файл из своего кеша, не спрашивая сервер. Чтобы обновления не залипали у пользователей, к именам файлов при сборке добавляют хеш — app.4f21c8.js. Подробнее механика разобрана в справке про кеширование.

Отдельно существует кеш самих ответов приложения — proxy_cache. Он складывает готовый HTML на диск и следующие запросы обслуживает без обращения к бэкенду. Для блога или витрины магазина кеш на 60 секунд снимает с приложения до 90 % нагрузки, а для личных кабинетов и корзины его включать нельзя: один пользователь увидит страницу другого.

Если поток запросов заметный, nginx становится и первым рубежом фильтрации: limit_req ограничивает частоту обращений с одного адреса, а серьёзные атаки отсекают на стороне провайдера — такие тарифы собраны в подборке защита от DDoS для nginx.

Сколько ресурсов и денег нужно под связку nginx и приложение

Сам nginx почти ничего не потребляет: процесс с сотней активных соединений укладывается в 15–25 МБ памяти. Основной аппетит у приложения. Ориентир: визитке и небольшому API хватает 1 ядра и 1 ГБ, магазину на PHP или Django — 2 ядер и 2–4 ГБ, проекту с очередями и базой на том же сервере — 4 ядер и 8 ГБ.

Диск берите NVMe: на HDD логи и кеш ответов тормозят отдачу. В разделе каталога собраны 44 провайдера с поддержкой nginx, ниже — их стартовые тарифы.

Провайдер Стартовая цена Базовая конфигурация Тарифов Рейтинг
Cloudcore от 59 ₽/мес 1 ядро, 1 ГБ, 10 ГБ NVMe 12 3.3
Hosting-Russia от 99 ₽/мес уточняется у провайдера 3.0
HostVDS от 127 ₽/мес 1 ядро, 1 ГБ, 10 ГБ NVMe 12 3.3
RuVDS от 139 ₽/мес 1 ядро, 0,5 ГБ, 10 ГБ HDD 440 4.3
Timeweb от 180 ₽/мес 15 ГБ NVMe 77 4.3
Selectel от 200 ₽/мес 1 ядро, 1 ГБ, 10 ГБ NVMe 69 4.5
AdminVPS от 299 ₽/мес 1 ядро, 1 ГБ, 15 ГБ NVMe 149 4.5

Данные проверены 04.09.2026.

Тариф за 59 ₽ годится, чтобы потренироваться на учебном проекте. Для боевого сайта берите минимум 2 ГБ памяти: при 1 ГБ сборка фронтенда упирается в потолок памяти, и система убивает процесс. Переход с тарифа за 139 ₽/мес на конфигурацию с 2 ГБ обойдётся дороже примерно на 150–300 ₽/мес.

Частые ошибки при настройке проксирования

Ошибки почти всегда одни и те же, и все они видны либо в nginx -t, либо в первых строках error.log. Пробегитесь по списку ниже, прежде чем искать проблему в коде приложения. И помните: после каждой правки конфига нужна пара команд nginx -t и systemctl reload nginx — правка без перезагрузки просто не применяется.

  • Забыли proxy_set_header Host $host; — приложение шлёт редиректы на 127.0.0.1:3000, и пользователь попадает в никуда.
  • Указали proxy_pass http://localhost:3000; — при включённом IPv6 запрос уходит на ::1 и возвращается ошибкой 502.
  • Приложение слушает 0.0.0.0 вместо 127.0.0.1 — порт 3000 доступен снаружи, и весь смысл прокси теряется.
  • Перепутали слеш в конце proxy_pass внутри location с префиксом — бэкенд получает обрезанный или задвоенный путь и отвечает 404.
  • Не подняли client_max_body_size — загрузка файла тяжелее 1 МБ падает с ошибкой 413.
  • Оставили в sites-enabled дефолтный конфиг — он перехватывает запросы к серверу по IP и показывает приветствие вместо сайта.

Частые вопросы

Нужен ли отдельный сервер под nginx? Нет. В большинстве проектов nginx и приложение живут на одной машине и общаются через localhost — так быстрее и дешевле. Разносить их стоит, только когда за одним прокси стоит несколько независимых бэкендов.

Что выбрать — nginx или Apache? Apache тоже умеет работать обратным прокси через mod_proxy, но на статике и большом числе соединений проигрывает: поток на каждое соединение съедает память быстрее. Сравнение с цифрами — в разборе nginx или Apache.

Почему после включения HTTPS сайт ругается на смешанный контент? Приложение не знает, что снаружи шифрование, и генерирует ссылки на http. Добавьте proxy_set_header X-Forwarded-Proto $scheme; и включите доверие к этому заголовку в настройках фреймворка — после перезапуска ссылки станут защищёнными.

Сколько сайтов можно проксировать с одного сервера? Ограничение задаёт не nginx, а память под сами приложения: на тарифе с 4 ГБ живут 5–8 небольших сайтов на PHP или 3 приложения на Node.js. Каждый домен описывается своим блоком server и своим сертификатом.

Обязательно ли держать приложение как службу systemd? Практически да. Запуск из терминала умрёт вместе с закрытием сессии, а служба поднимется сама после перезагрузки сервера и после падения процесса. Без этого любая ошибка в коде превращается в 502 до тех пор, пока вы вручную не зайдёте на сервер.

Подойдёт ли обычный виртуальный хостинг вместо VPS? Нет: там нет доступа к конфигам nginx и нельзя держать свой процесс на произвольном порту. Как только приложению нужен собственный демон, требуется сервер с root-доступом — от 59 ₽/мес.

Итог

Рабочая схема собирается из четырёх элементов: приложение слушает 127.0.0.1:3000, nginx принимает запросы на 443, четыре директивы proxy_set_header передают данные клиента, а сертификат Let’s Encrypt продлевается каждые 90 дней. Балансировку добавит блок upstream, а разбор проблем всегда начинается с nginx -t и последних 50 строк error.log.

Дальше остаётся выбрать площадку: конфигурации и цены 44 провайдеров собраны в разделе VPS для nginx — от 59 ₽ за учебный сервер до тарифов с 8 ГБ памяти.

Поделиться:
👁 36 просмотров
Марина
Редактор · FREEHOSTING
Главный редактор FREEHOSTING. С 2020 года тестирует VDS, VPS и хостинг-провайдеров — арендует серверы, нагружает их реальными проектами и пишет честные обзоры по итогам. Помогает читателям выбирать хостинг под свои задачи: от Telegram-бота до production-сайта.