ServerAID
Найти гайд, команду, тег… ⌘ K
Серверный стек

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: $schemehttp или 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 объявляется на уровне httpnginx.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:

  1. Запросит сертификат через ACME challenge (HTTP-01).
  2. Автоматически отредактирует ваш nginx-конфиг — добавит listen 443 ssl, директивы ssl_certificate/ssl_certificate_key и редирект http → https.
  3. Поставит 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. Это три самые частые поломки.

Похожие материалы

docker compose на Ubuntu 24.04: установка и рабочий compose.yaml
Серверный стек

docker compose на Ubuntu 24.04: установка и рабочий compose.yaml

docker compose v2 — официальный плагин Docker для запуска многоконтейнерных приложений одним конфигом. Разбираем установку через официальный репозиторий Docker на Ubuntu 24.04 LTS, базовый `compose.yaml` с volumes и healthcheck, рестарт-политики, профили и запуск compose-стека как systemd-сервиса.

Редакция
find — что это и зачем
Глоссарий

find — что это и зачем

find — стандартная команда Linux для поиска файлов и каталогов по имени, типу, правам, размеру, дате и десяткам других критериев. С помощью `-exec` поверх найденного можно сразу выполнять команды. Универсальный инструмент админа для массовой работы с файловой системой.

Редакция
snap — что это и зачем
Глоссарий

snap — что это и зачем

snap — система упаковки и установки приложений от Canonical, разработанная как альтернатива apt. Каждый snap — самодостаточный пакет со своими зависимостями, изолированный от системы через AppArmor. Используется на Ubuntu для Firefox, Chromium, microk8s и многих desktop-приложений.

Редакция
LVM — что это и зачем
Глоссарий

LVM — что это и зачем

LVM — слой абстракции между физическими дисками и файловыми системами в Linux. Объединяет диски в пулы (Volume Groups), нарезает их на логические тома (Logical Volumes) и позволяет изменять размер на лету. Альтернатива классическому partitioning, которая делает работу с дисками гибкой.

Редакция