nginx reverse proxy на Ubuntu: рабочий конфиг с заголовками и SSL
Reverse proxy — фронт, который принимает запросы из интернета и передаёт их вашему Node.js, Python или Docker-контейнеру. Разбираем `proxy_pass`, правильные заголовки `X-Forwarded-For` и `Host`, балансировку через `upstream`, прокси к Docker, WebSocket-апгрейд и SSL termination через certbot. Конфиги под Ubuntu 24.04 LTS.
nginx reverse proxy: настройка с proxy_pass, X-Forwarded-* и certbot
Reverse proxy — фронт, который принимает запросы из интернета и передаёт их вашему Node.js, Python или Docker-контейнеру. Разбираем proxy_pass, правильные заголовки X-Forwarded-For и Host, балансировку через upstream, прокси к Docker, WebSocket-апгрейд и SSL termination через certbot. Конфиги под Ubuntu 24.04 LTS.
Зачем reverse proxy перед бэкендом
Прямой ответ: чтобы один порт 443 на сервере обслуживал десятки приложений и сервисов. nginx как reverse proxy решает сразу несколько задач:
- Один TLS-сертификат на фронте, бэкенды живут в plain HTTP внутри сети.
- Маршрутизация по домену и пути —
app.example.comидёт на Node.js,api.example.com— на Python,/static/— отдаётся nginx-ом напрямую с диска. - Скрытие бэкенда от интернета — он слушает только
127.0.0.1, наружу не торчит. - Балансировка между несколькими инстансами одного приложения через
upstream. - Кэширование статики и иногда — ответов API.
- Защита от лёгких атак — лимиты на размер тела, rate limiting, базовая фильтрация.
Альтернативы — Caddy, Traefik, HAProxy. Все рабочие; nginx остаётся стандартом по умолчанию из-за объёма документации, готовых конфигов и зрелости.
Ставим nginx на Ubuntu
Базовая установка из репозитория Ubuntu:
sudo apt update
sudo apt install -y nginx
sudo systemctl enable --now nginx
sudo systemctl status nginx
После установки откройте http://<ваш-ip> — увидите дефолтную страницу «Welcome to nginx!». Если открыли UFW — не забудьте пробросить:
sudo ufw allow 'Nginx Full'
Подробнее про правила UFW — в гайде по настройке UFW на Ubuntu.
Структура конфигов в Ubuntu:
/etc/nginx/nginx.conf— главный файл, его обычно не правят./etc/nginx/sites-available/— конфиги виртуальных хостов./etc/nginx/sites-enabled/— симлинки на активные хосты./etc/nginx/conf.d/*.conf— дополнительные блоки (например, общиеproxy_params).
Соглашение Ubuntu: пишете конфиг в sites-available/<домен>, потом делаете симлинк в sites-enabled/.
Делаем базовый proxy_pass
Допустим, у вас Node.js-приложение слушает 127.0.0.1:3000, и вы хотите, чтобы app.example.com ходил на него. Создаём конфиг:
# /etc/nginx/sites-available/app.example.com
server {
listen 80;
listen [::]:80;
server_name app.example.com;
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 X-Forwarded-Host $host;
}
}
Включаем и применяем:
sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
sudo nginx -t # проверить синтаксис конфига
sudo systemctl reload nginx
nginx -t — обязательная проверка перед reload. Если она падает, reload не применит сломанный конфиг, но привыкайте проверять явно.
Передаём правильные заголовки
Без правильных заголовков ваш бэкенд не узнает реального IP клиента и оригинальный домен — он будет видеть только 127.0.0.1 и порт. Разбираем зачем нужен каждый:
Host: $host— оригинальный заголовок Host от клиента. Без этого бэкенд получитHost: 127.0.0.1:3000, и любой framework сломается на проверке домена (CORS, редиректы, генерация абсолютных URL).X-Real-IP: $remote_addr— IP клиента. Это IP, с которого пришёл запрос в nginx.X-Forwarded-For: $proxy_add_x_forwarded_for— цепочка прокси. nginx добавит свой адрес в существующий заголовок (если запрос пришёл через CDN типа Cloudflare).X-Forwarded-Proto: $scheme—httpилиhttps. Бэкенд сам не знает, шёл ли запрос изначально через TLS — без этого редиректы будут уходить на http.X-Forwarded-Host: $host— оригинальный домен. Дублирует Host, но некоторые фреймворки смотрят именно наX-Forwarded-Host.
Чтобы не копировать этот блок в каждый сайт, вынесите его в /etc/nginx/conf.d/proxy_headers.conf:
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 X-Forwarded-Host $host;
И в каждом site-конфиге:
location / {
include /etc/nginx/conf.d/proxy_headers.conf;
proxy_pass http://127.0.0.1:3000;
}
В Django, FastAPI, Express и подобных фреймворках для использования этих заголовков нужно включить «trusted proxy» режим — иначе они проигнорируют X-Forwarded-* из соображений безопасности.
Балансируем через upstream
Когда бэкенд запущен в нескольких инстансах (для надёжности или нагрузки), используйте блок upstream:
upstream app_backend {
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;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://app_backend;
include /etc/nginx/conf.d/proxy_headers.conf;
}
}
Что важно:
- По умолчанию nginx использует round-robin. Опции —
least_conn(на самый свободный),ip_hash(sticky-сессии по IP клиента). max_fails=3 fail_timeout=30s— если 3 запроса подряд упали, на 30 секунд исключаем сервер из ротации.backup— резервный, получает трафик только когда основные недоступны.
Для sticky-сессий по куки (если бэкенд хранит сессию в памяти) есть только в nginx Plus или через ip_hash — последний работает грубо: пользователи за NAT попадают на один инстанс.
Проксируем к Docker-контейнеру
Самый частый сценарий — docker-compose с приложением и nginx как фронт. Два варианта.
Вариант 1: nginx на хосте, контейнер слушает на хосте.
# docker-compose.yml
services:
app:
image: myapp:latest
ports:
- "127.0.0.1:3000:3000" # биндим только на localhost
В nginx: proxy_pass http://127.0.0.1:3000;. nginx живёт на хосте, контейнер виден через localhost. Безопасно — порт 3000 не торчит наружу благодаря биндингу на 127.0.0.1.
Вариант 2: nginx тоже в Docker.
services:
app:
image: myapp:latest
networks:
- web
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./certs:/etc/letsencrypt:ro
networks:
- web
depends_on:
- app
networks:
web:
В этом случае в nginx-конфиге proxy_pass http://app:3000; — Docker DNS резолвит имя сервиса в IP контейнера. Это удобно, но сертификаты придётся обновлять отдельно (см. ниже про certbot и DNS-01).
Прокси к контейнеру по имени сервиса — частый источник «502 Bad Gateway»: контейнер ещё не поднялся, а nginx уже стартанул и закэшировал DNS. Помогает resolver в конфиге:
resolver 127.0.0.11 valid=10s;
set $upstream "http://app:3000";
location / {
proxy_pass $upstream;
}
127.0.0.11 — встроенный Docker DNS. Через переменную nginx ресолвит DNS на каждом запросе, а не один раз при старте.
Поднимаем WebSocket-апгрейд
Стандартный proxy_pass ломает WebSocket — нужно прокинуть заголовки Upgrade и Connection:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
server_name ws.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_read_timeout 3600s; # WebSocket-сессии живут долго
}
}
Блок map объявляется на уровне http (в nginx.conf или отдельном файле в /etc/nginx/conf.d/). Без него браузер получит «WebSocket connection failed» — апгрейд не сработает.
Включаем HTTPS через certbot
Самый быстрый путь к HTTPS на nginx — Let's Encrypt через certbot:
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d app.example.com -d www.app.example.com
certbot:
- Запросит сертификат через ACME challenge (HTTP-01).
- Автоматически отредактирует ваш nginx-конфиг — добавит
listen 443 ssl, директивыssl_certificate/ssl_certificate_keyи редиректhttp → https. - Поставит systemd-таймер
certbot.timerдля автообновления раз в день.
Если nginx живёт в Docker (вариант 2 выше), HTTP-01 не сработает — нужен DNS-01 challenge:
sudo apt install -y certbot python3-certbot-dns-cloudflare
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /root/.cloudflare.ini \
-d app.example.com
Сертификаты лягут в /etc/letsencrypt/live/app.example.com/. Их пробрасываем в контейнер nginx через volume.
Минимальные TLS-настройки в server-блоке:
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
Современные рекомендации — оставить только TLS 1.2 и 1.3, отключить SSLv3/TLSv1.0/1.1.
Разбираем типичные поломки
502 Bad Gateway. Бэкенд не отвечает или отвечает не так. Смотрим:
sudo tail -f /var/log/nginx/error.log
Чаще всего — бэкенд не запущен (Connection refused), не на том порту, или таймаут (upstream timed out).
504 Gateway Timeout. Бэкенд отвечает, но дольше дефолтных 60 секунд. Увеличиваем:
proxy_connect_timeout 30s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
413 Request Entity Too Large. Клиент отправил слишком большой файл. Поднимаем лимит:
client_max_body_size 100M;
Mixed content. Браузер ругается, что страница загружена по HTTPS, а внутри ссылки на http. Виноват бэкенд: он не учитывает X-Forwarded-Proto. Нужно включить trusted-proxy режим во фреймворке.
Бесконечный редирект 301. Часто бывает когда бэкенд видит http (так как nginx общается с ним по http) и редиректит на https, который nginx опять прокинет на http. Решение — X-Forwarded-Proto + правильная настройка фреймворка, чтобы он доверял этому заголовку.
504 на WebSocket после минуты. Не настроены заголовки Upgrade/Connection или дефолтный proxy_read_timeout=60s рвёт длинные сессии. См. блок про WebSocket выше.
Частые вопросы
Что такое reverse proxy простыми словами
Reverse proxy — это сервер-«фронт», который принимает запросы от пользователей и перенаправляет их одному или нескольким бэкенд-приложениям. В отличие от обычного (forward) прокси, который защищает клиента, reverse proxy защищает и абстрагирует серверы. Браузер общается только с reverse proxy (nginx), а тот уже решает, кому отдать запрос.
Чем reverse proxy отличается от обычного web-сервера
Обычный web-сервер сам генерирует или отдаёт ответ (например, отдаёт статичные HTML-файлы). Reverse proxy не генерирует ответ — он передаёт запрос дальше, бэкенду. nginx умеет работать в обоих режимах: отдавать /static/ напрямую с диска и одновременно проксировать /api/ в Node.js.
Как настроить nginx reverse proxy
Минимум: установить nginx, в /etc/nginx/sites-available/<домен> написать server-блок с proxy_pass http://127.0.0.1:<порт>; и набором правильных заголовков (Host, X-Real-IP, X-Forwarded-For, X-Forwarded-Proto). Сделать симлинк в sites-enabled/, проверить через nginx -t, перечитать конфиг через sudo systemctl reload nginx.
Почему nginx reverse proxy fails с 502 Bad Gateway
Чаще всего — бэкенд не работает или работает не на том порту. Проверьте: curl -v http://127.0.0.1:3000 с самого сервера; sudo tail -f /var/log/nginx/error.log — там будет точная причина (Connection refused, connection timed out, no such device or address). Реже — SELinux/AppArmor запрещают nginx ходить в backend; на Ubuntu это редкость, но проверьте sudo journalctl -t apparmor.
Как проксировать nginx к Docker-контейнеру
Два пути. Если nginx на хосте — биндите порт контейнера на 127.0.0.1:<порт> и пишите proxy_pass http://127.0.0.1:<порт>;. Если nginx тоже в Docker — поместите оба контейнера в одну сеть и используйте имя сервиса как hostname: proxy_pass http://app:3000;. Во втором случае добавьте resolver 127.0.0.11 valid=10s;, чтобы DNS не кэшировался при перезапусках контейнера.
Какие заголовки обязательны при reverse proxy
Host — без него бэкенд не узнает домен. X-Real-IP или X-Forwarded-For — реальный IP клиента (без них в логах будет 127.0.0.1). X-Forwarded-Proto — иначе бэкенд не узнает, что запрос пришёл по https, и сломаются редиректы и абсолютные URL.
Что такое upstream в nginx
upstream — именованная группа бэкенд-серверов для балансировки. nginx по умолчанию раскидывает запросы между ними по round-robin. Поддерживаются least_conn (на самый свободный сервер), ip_hash (sticky по IP), max_fails/fail_timeout (убрать упавший сервер из ротации) и backup (резервный).
Что запомнить
- Базовый рецепт:
proxy_pass http://127.0.0.1:<порт>;плюс блок из 5 заголовков (Host,X-Real-IP,X-Forwarded-For,X-Forwarded-Proto,X-Forwarded-Host). Вынесите их вconf.d/proxy_headers.confиinclude. - Перед
reload— всегдаsudo nginx -t. Если не пройдёт, конфиг не применится, но привыкайте к привычке. - Для Docker используйте
resolver 127.0.0.11 valid=10s;и переменную вproxy_pass— иначе nginx закэширует IP и упадёт при рестарте контейнера. - WebSocket требует
proxy_http_version 1.1+ пробросUpgrade/Connectionчерезmap. И поднятьproxy_read_timeout. - HTTPS — через
certbot --nginx. Один сертификат может покрывать несколько доменов через-d a.example.com -d b.example.com. - 502 — бэкенд не работает; 504 — бэкенд медленный; mixed content — бэкенд не доверяет
X-Forwarded-Proto. Это три самые частые поломки.