Руководства

NGINX как обратный прокси: настройка, проверка и устранение неполадок

Пошагово настраиваем NGINX как обратный прокси на Ubuntu/Debian, проверяем ответ бэкенда, заголовки и логи, разбираем ошибки 502/504 и отличие от исходящего прокси Socks5.IO.

Valerie QuinnValerie Quinn12 мин. чтения

Короткий ответ: чтобы использовать NGINX как обратный прокси, укажите управляемый сервер и блок location, направьте запросы к приложению через proxy_pass, задайте доверенные заголовки, проверьте конфигурацию и выполните reload. Проверяйте ответ приложения через NGINX, а не только результат nginx -t или приветственную страницу. Обратный прокси обслуживает входящие запросы к приложению; Socks5.IO относится к отдельному исходящему маршруту приложения.

В этом руководстве создаётся небольшой локальный HTTP-стенд для Ubuntu/Debian-подобной системы. NGINX принимает запросы на 127.0.0.1:8080 и передаёт их демонстрационному приложению на 127.0.0.1:3000. Публичный домен не нужен, существующий сайт не изменяется, открытый прокси наружу не создаётся. Перед публикацией сервиса сначала завершите локальную проверку.

Команды рассчитаны на Linux-сервер и не предназначены для Windows PowerShell. Результаты ниже взяты из тестовых записей исходного материала; при подготовке русской версии команды повторно не запускались.

Как работает обратный прокси NGINX?

NGINX принимает соединение клиента, выбирает виртуальный сервер и location, отправляет запрос выбранному upstream-приложению и возвращает его ответ. Клиенту не нужно знать внутренний порт или адрес бэкенда. Подробности описаны в официальном руководстве NGINX по reverse proxy.

texttext
Запрос:   клиент -> слушатель NGINX -> бэкенд приложения
Ответ:    клиент <- слушатель NGINX <- бэкенд приложения

Стенд из статьи:
curl -> 127.0.0.1:8080, Host proxy-demo.test -> 127.0.0.1:3000
inbound-and-outbound

Обратный прокси используют для маршрутизации разных путей, завершения клиентского TLS, распределения запросов между бэкендами и заданной политики кэширования. Эти возможности требуют отдельных настроек: добавление proxy_pass не включает их автоматически. Reverse proxy не устраняет уязвимости приложения, не превращается по умолчанию в WAF и не гарантирует, что исходный сервер нельзя достичь другим разрешённым маршрутом.

Обратный и прямой прокси: в чём разница?

Вопрос Обратный прокси NGINX Исходящий прокси приложения
Кто подключается первым Клиент обращается к приложению Приложение обращается к внешнему источнику
Что выбирается Управляемый вами бэкенд Поддерживаемый шлюз выхода и разрешённый адрес назначения
Кто настраивает Оператор приложения настраивает NGINX Сам клиент или приложение настраивает прокси
Что доказывает успех Запрос дошёл до нужного бэкенда через выбранный listener Конкретный исходящий клиент использовал проверенный маршрут

Прямой прокси может изменить адрес источника, который видит назначение. Это не означает полной анонимности всего соединения: обратный прокси, сервис выхода и конечный ресурс могут видеть разные части обмена. Сам факт использования прокси не даёт права доступа к данным.

Как настроить обратный прокси NGINX на Ubuntu или Debian

Используйте временный тестовый сервер или согласованный стенд с правами sudo. Ниже предполагается пакетная установка Ubuntu/Debian под управлением systemd, с каталогами /etc/nginx/sites-available и /etc/nginx/sites-enabled. В контейнерах, других дистрибутивах и управляемом хостинге пути и команды могут отличаться.

Не перезаписывайте рабочий сайт. Проверьте, свободны ли локальные порты 3000 и 8080. Если они заняты, выберите другие и одинаково измените все команды и настройки. Примеры ниже предназначены для Bash в Linux.

1. Установите инструменты и проверьте сервис

bashbash
sudo apt update
sudo apt install nginx python3 curl
nginx -v
sudo nginx -t
sudo systemctl status nginx --no-pager
sudo ss -ltnp '( sport = :3000 or sport = :8080 )'

Инструкции Ubuntu доступны в официальном руководстве по установке NGINX. Установка пакета может автоматически запустить стандартный сервис. На общем сервере сначала оцените влияние на существующие сайты. Если NGINX остановлен, выясните причину и только затем при необходимости выполните sudo systemctl start nginx. Не отключайте firewall и не останавливайте чужой процесс ради прохождения лабораторной проверки.

2. Запустите бэкенд с различимым ответом

В рабочем каталоге обычного пользователя сохраните код под именем proxy_demo_backend.py. Он отдаёт один диагностический endpoint и привязывается только к loopback.

pythonpython
import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import urlsplit

class Handler(BaseHTTPRequestHandler):
    protocol_version = "HTTP/1.1"

    def do_GET(self):
        if urlsplit(self.path).path != "/healthz":
            self.send_error(404)
            return
        payload = {
            "backend": "proxy-demo-backend",
            "path": self.path,
            "host": self.headers.get("Host"),
            "x_real_ip": self.headers.get("X-Real-IP"),
            "x_forwarded_for": self.headers.get("X-Forwarded-For"),
            "x_forwarded_proto": self.headers.get("X-Forwarded-Proto"),
            "forwarded": self.headers.get("Forwarded"),
            "request_id": self.headers.get("X-Request-ID"),
        }
        body = json.dumps(payload).encode("utf-8")
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.send_header("Cache-Control", "no-store")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def log_message(self, format, *args):
        pass

if __name__ == "__main__":
    ThreadingHTTPServer(("127.0.0.1", 3000), Handler).serve_forever()

В терминале A запустите сервер и оставьте процесс работать:

bashbash
python3 proxy_demo_backend.py

Опция --fail-with-body доступна в curl 7.76.0 и новее. Проверьте версию командой curl --version; в старой версии замените её на --fail. При такой замене тело HTTP-ошибки не выводится.

В терминале B проверьте бэкенд напрямую:

bashbash
curl --noproxy '*' --fail-with-body --silent --show-error \\
  http://127.0.0.1:3000/healthz

Условие успеха: JSON содержит "backend": "proxy-demo-backend" и "path": "/healthz". При прямом базовом запросе поля forwarding обычно равны null, потому что клиент не передавал их. Этот Python-сервер предназначен для обучения, а не для production и не для проверки реальных зависимостей.

3. Добавьте отдельный тестовый сайт NGINX

Через sudoedit /etc/nginx/sites-available/proxy-demo создайте новый файл. Если имя уже занято, проверьте его содержимое и выберите другое, чтобы ничего не перезаписать. Фрагмент ниже размещается внутри существующего контекста http, а не на верхнем уровне.

nginxnginx
# /etc/nginx/sites-available/proxy-demo
# Фрагмент находится внутри существующего контекста http.
log_format proxy_demo '$request_id $remote_addr $request_method $uri '
                      'status=$status upstream=$upstream_addr '
                      'upstream_status=$upstream_status '
                      'upstream_time=$upstream_response_time total=$request_time';

server {
    listen 127.0.0.1:8080 default_server;
    server_name _;
    access_log off;
    return 404;
}

server {
    listen 127.0.0.1:8080;
    server_name proxy-demo.test;

    access_log /var/log/nginx/proxy-demo.access.log proxy_demo;
    error_log /var/log/nginx/proxy-demo.error.log warn;
    add_header X-Request-ID $request_id always;

    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 $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Request-ID $request_id;
    proxy_set_header Forwarded "";

    proxy_connect_timeout 3s;
    proxy_read_timeout 15s;
    proxy_send_timeout 15s;

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

Upstream здесь — локальное приложение, а не шлюз исходящего прокси. В простой location-структуре форма proxy_pass без URI сохраняет входящий путь. Сервер по умолчанию отклоняет неизвестные Host на выделенном loopback-listener и не меняет другие сайты.

config-routing

Явный HTTP/1.1 не позволяет зависеть от значения по умолчанию конкретной версии. Таймауты выбраны для стенда: proxy_read_timeout и proxy_send_timeout ограничивают паузы между операциями, а не общий срок выполнения приложения. Перед изменением проверьте справочник модуля NGINX proxy.

4. Включите сайт, проверьте конфигурацию и выполните reload

Сначала убедитесь, что в sites-enabled нет файла или ссылки с таким именем:

bashbash
sudo ln -s /etc/nginx/sites-available/proxy-demo /etc/nginx/sites-enabled/proxy-demo
sudo nginx -t

Не повторяйте команду создания ссылки и не используйте принудительную перезапись. Если тест конфигурации завершился ошибкой, исправьте её до следующего шага. Успешный nginx -t подтверждает загрузку конфигурации, но не живое соединение с бэкендом.

bashbash
sudo systemctl reload nginx
sudo systemctl status nginx --no-pager

Для работающего сервиса обычно достаточно reload, а не restart. Подробности есть в руководстве NGINX для начинающих.

syntax-and-scope

Как проверить работу обратного прокси NGINX

Проверяйте маршрут по слоям: прямое соединение с бэкендом, выбранный listener NGINX, содержимое ответа и связанные записи логов. Один только nginx -t не показывает, дошёл ли запрос до приложения.

Проверьте бэкенд через настроенный виртуальный хост

bashbash
curl --noproxy '*' --fail-with-body --silent --show-error --include \\
  --resolve proxy-demo.test:8080:127.0.0.1 \\
  http://proxy-demo.test:8080/healthz

Опция --resolve задаёт адрес только для этого запроса, не меняя DNS и hosts-файл. --noproxy исключает случайный внешний forward proxy из локальной проверки.

Условия успеха:

  • HTTP-статус 200, а в теле есть proxy-demo-backend.
  • path равен /healthz, host — proxy-demo.test, а x_forwarded_proto в этом HTTP-стенде равен http.
  • x_real_ip и x_forwarded_for показывают фактический loopback-адрес клиента, а не произвольное значение из заголовка посетителя.
  • X-Request-ID в ответе совпадает с request_id в JSON бэкенда и выделенной access-записью.

В конфигурации Host задаётся через $host, поэтому порт клиента не сохраняется. Если приложение формирует абсолютные URL, отдельно задайте политику канонического host/port.

backend-response

Проверьте границу доверия forwarding-заголовков

Повторите запрос с намеренно поддельными входящими заголовками:

bashbash
curl --noproxy '*' --fail-with-body --silent --show-error \\
  --resolve proxy-demo.test:8080:127.0.0.1 \\
  -H 'X-Forwarded-For: 198.51.100.77' \\
  -H 'X-Real-IP: 198.51.100.77' \\
  -H 'X-Forwarded-Proto: https' \\
  -H 'Forwarded: for=198.51.100.77;proto=https' \\
  http://proxy-demo.test:8080/healthz

Условие успеха: бэкенд по-прежнему получает loopback-адрес клиента и http, а forwarded равен null. В реальном приложении доверяйте только тем proxy-hop и заголовкам, которыми вы управляете: эти поля не являются учётными данными.

header-trust-boundary

Если перед NGINX есть доверенный CDN или балансировщик, настройте доверенные диапазоны и заголовок через модуль real-IP, где он доступен. Не доверяйте всем адресам из интернета право объявлять собственную идентичность.

Проверьте логи и безопасно воспроизведите ошибку

bashbash
sudo tail -n 20 /var/log/nginx/proxy-demo.access.log
sudo tail -n 20 /var/log/nginx/proxy-demo.error.log

Access-лог содержит upstream-адрес, статусы и время ответа. Ограничьте доступ и срок хранения: пути, адреса и error-лог могут содержать чувствительные сведения, поэтому перед публикацией маскируйте их.

request-log-correlation

Только в этом стенде остановите демонстрационный бэкенд через Ctrl+C и повторите curl-запрос. При отсутствии upstream ожидается 502 и ошибка соединения в логе. Запустите python3 proxy_demo_backend.py снова и убедитесь, что возвращается 200. Не останавливайте production-приложение ради эксперимента.

failure-and-recovery

Проверка неизвестного Host должна вернуть 404 и не передать запрос приложению:

bashbash
curl --noproxy '*' --silent --show-error --include \\
  -H 'Host: other.example' http://127.0.0.1:8080/healthz

Этот тест проверяет маршрутизацию Host на локальном listener, но не TLS и не публичную доступность. curl -I отправляет HEAD, а маленькое учебное приложение реализует GET; не принимайте неподдержанный HEAD за поломку reverse proxy.

Диапазон исходной проверки: в предоставленном материале указаны 12 локальных проверок с официальной portable-версией NGINX 1.31.8 на Windows и отдельной top-level-конфигурацией, которая подключает тот же фрагмент. Проверялись ответы, замена заголовков, отклонение Host, связь request ID с логом, префиксы пути и восстановление после 502. Ubuntu-пакеты, systemd, публичные DNS/TLS/firewall и реальный маршрут Socks5.IO этой loopback-проверкой не подтверждены.

Проверьте преобразование пути proxy_pass перед добавлением /api/

Для входящего /api/healthz?check=1 завершающий URI в proxy_pass меняет путь бэкенда:

Location и upstream Путь, который получает бэкенд
location /api/, proxy_pass http://127.0.0.1:3000; /api/healthz?check=1
location /api/, proxy_pass http://127.0.0.1:3000/; /healthz?check=1

Это два альтернативных примера, а не инструкция вставлять одинаковые location одновременно. В тестовой ветке добавьте один вариант, сохраните политику заголовков, проверьте и перезагрузите NGINX. При удалении префикса запрос достигнет /healthz; при его сохранении учебный бэкенд вернёт 404.

proxy-pass-paths

Перед публикацией: HTTPS, origin и особый трафик

Loopback-стенд не является публичным HTTPS-развёртыванием. Перед публикацией приложения:

  1. Создайте согласованный публичный virtual host с реальным доменом и намеренно выбранными listener. Тестовый loopback-сайт оставьте отдельно или удалите после проверки.
  2. Получите действующий сертификат и приватный ключ по утверждённой процедуре. Настройте клиентский TLS и проверьте его; не отключайте проверку сертификата.
  3. Оставьте бэкенд на loopback, если он работает на том же хосте, либо ограничьте его частный listener маршрутом прокси. В контейнере 127.0.0.1 указывает на этот контейнер.
  4. Защитите upstream-соединение через недоверенную сеть: задайте имя TLS-сервера, проверку сертификата и доверенный CA bundle. Публичный HTTPS не шифрует автоматически HTTP-соединение к бэкенду. См. руководство NGINX по защите upstream-трафика.
  5. Отдельно настройте доверенные proxy, аутентификацию, загрузки и таймауты фреймворка. Проверяйте redirect, Secure Cookie, тело запроса и публичные пути, а не только /healthz.

Для WebSocket явно обработайте Upgrade-заголовки и проверьте настоящее обновление соединения; см. документацию NGINX по WebSocket. Для streaming/SSE может понадобиться отдельная политика буферизации. Кэширование в этом стенде отключено: не применяйте общий кэш к авторизованным или персонализированным ответам.

В upstream-группу балансировки добавляют endpoints приложений. Состояние сессий и отказоустойчивость проектируются отдельно; добавление второго адреса само по себе не создаёт высокую доступность. Не добавляйте residential proxy-узлы в upstream приложения как будто это его взаимозаменяемые серверы.

Может ли NGINX использовать Socks5.IO для исходящих запросов?

Разделяйте компоненты по направлению трафика:

texttext
Входящий:  клиент -> reverse proxy NGINX -> ваше приложение
Исходящий: приложение -> совместимый forward proxy Socks5.IO -> разрешённый внешний источник

Обычный HTTP proxy_pass не является SOCKS5-клиентом. Указание коммерческого HTTP-прокси также не добавляет автоматически формат forward-запроса или HTTPS CONNECT. Upstream-группа не выполняет преобразование протоколов. proxy_bind выбирает локальный адрес источника и не выдаёт удалённый residential IP.

Исходящий прокси обычно задаётся в HTTP/browser-клиенте приложения либо в специально спроектированном компоненте выхода. Возможности NGINX зависят от продукта и модулей; эта лаборатория не подтверждает универсальную совместимость с любым внешним прокси.

Для разрешённой интеграции, которой нужен исходящий трафик через резидентные IP, в клиентском компоненте можно рассмотреть динамические резидентные прокси Socks5.IO. Актуальные адреса подключения, протокол и параметры сессии проверяйте в аккаунте. Не помещайте секреты прокси в публичную конфигурацию NGINX или диагностический ответ.

Выбирайте протокол с учётом возможностей клиента. Подробнее — в руководстве «SOCKS5 или HTTP: чем отличаются прокси и какой выбрать». Маркировка SOCKS5 не превращает любой браузер или reverse proxy в аутентифицированный SOCKS-клиент. Проверяйте исходящий маршрут из самого приложения и отдельно подтверждайте разрешённый адрес назначения.

Например, внутренний API отчётности может принимать входящие запросы через NGINX, а фоновый worker — обращаться к разрешённому внешнему товарному фиду через отдельный выход. Успешный /healthz доказывает только входящий маршрут и ничего не говорит об IP worker. Это архитектурный пример, а не клиентский бенчмарк Socks5.IO.

Устранение неполадок: 502, 504, неверная страница и IP клиента

При ошибке проверяйте клиент, listener NGINX и upstream-бэкенд по отдельности: настройки, ответ и логи. Правила сети меняйте только после установления причины.

Симптом Что проверить сначала Чего избегать
Ошибка nginx -t Файл и строку ошибки, контекст, дублирующий listener, синтаксис и права Делать reload при ошибке или публиковать вывод nginx -T с секретами
502 Bad Gateway Listener бэкенда, протокол upstream, отказ соединения и error-лог Считать каждый 502 блокировкой firewall
504 Gateway Timeout Хоп с таймаутом, задержку бэкенда и нужный timeout Увеличивать все таймауты без диагностики
Приветственная страница или неожиданный 200 Адрес, Host, выбранный listener и тело ответа Считать любой 200 доказательством правильного маршрута
/api/ возвращает 404 Сохранение или удаление префикса и маршрут приложения Менять слэши наугад
Приложение видит адрес NGINX Политику доверенных прокси/заголовков и прямую доступность бэкенда Доверять произвольному X-Forwarded-For посетителя
Цикл redirect или неверная схема Хопы TLS, доверенную схему и canonical URL приложения Безусловно доверять X-Forwarded-Proto клиента
Публичный запрос не работает, локальный работает DNS, выбранный IP/listener, согласованные firewall/security-group и TLS Отключать firewall или публиковать порт 3000

Один timeout не доказывает блокировку firewall. Тестируйте каждый хоп и проверяйте правила до их изменения. В этом стенде публичное правило UFW не нужно: оба listener работают на loopback. Порты 80/443 проверяются отдельно, не блокируя SSH.

Итог: проверяйте входящий и исходящий маршруты отдельно

При использовании NGINX как reverse proxy отдельно проектируйте выбор listener, маршрутизацию к upstream и доверие приложения к заголовкам. Создайте различимый бэкенд, проверьте конфигурацию, безопасно выполните reload, а затем сопоставьте реальный ответ с логами. HTTPS, специальные протоколы и эксплуатационные ограничения добавляйте только после определения критериев успеха.

Исходящий прокси настраивается отдельно. Socks5.IO может быть вариантом для разрешённых исходящих соединений совместимого приложения, но не заменяет бэкенд. Успешная проверка reverse proxy не подтверждает внешний маршрут приложения.

Часто задаваемые вопросы

Он принимает запросы к приложению и передаёт их настроенным бэкендам. Типовые задачи — маршрутизация, завершение TLS, логирование и отдельные политики производительности. Каждая функция требует собственной настройки и проверки: один proxy_pass не включает кэш, высокую доступность или WAF. Перед публикацией проверьте реальный ответ приложения и ограничьте доступ к бэкенду предусмотренным маршрутом.

Последние статьи

Иллюстрация глобальной прокси-сети

Подключитесь к глобальной прокси-сети — каждое соединение будет быстрее и стабильнее

Начни бесплатно