Как настроить обратный прокси Nginx на Ubuntu с поддержкой HTTPS и WebSocket 

Валерий Волков

Время прочтения 21 минута

В этом руководстве мы настроим Nginx на Ubuntu как reverse proxy между доменом и приложением, которое работает на локальном порту, например 127.0.0.1:3000.

Сначала установим Nginx, создадим отдельную конфигурацию сайта и настроим proxy_pass, чтобы запросы с домена уходили на приложение. Затем передадим нужные HTTP-заголовки, добавим поддержку WebSocket через Upgrade и Connection, а после подключим HTTPS с помощью Certbot.

Отдельно проверим конфигурацию через nginx -t, настроим автоматический редирект с HTTP на HTTPS и убедимся, что сертификаты могут продлеваться автоматически.

В конце разберем типичные причины 502 Bad Gateway: остановленное приложение, неправильный порт или адрес в proxy_pass, ошибки конфигурации и проблемы, которые можно найти в логах Nginx и самого приложения.

Что именно будем настраивать

Добро пожаловать, дорогой читатель!

Сегодня разберем довольно типичную серверную задачу. Представим, что на VPS уже работает приложение — например, на Node.js, Python или Go. Оно запущено, отвечает на запросы и слушает локальный порт, допустим: 127.0.0.1:3000

То есть внутри самого сервера всё уже работает.

Но есть проблема: пользователю совершенно не обязательно знать IP нашего VPS, внутренний порт приложения и тем более вводить в браузере что-то вроде: http://203.0.113.10:3000

Гораздо привычнее и удобнее открыть обычный адрес: https://example.com

Нам нужно каким-то образом связать эти две точки: принять запрос на домене и передать его приложению, которое работает внутри VPS.

Эту роль и возьмет на себя Nginx.

В итоге схема будет выглядеть так:

Nginx будет принимать внешние запросы, а затем перенаправлять их на локальный порт приложения. Такой режим работы называется reverse proxy, или обратным прокси.

Но одним перенаправлением дело не ограничится.

В этой статье мы:

  • Установим Nginx на Ubuntu;
  • Направим запросы с домена на локальный порт приложения;
  • Настроим proxy_pass;
  • Передадим нужные HTTP-заголовки;
  • Добавим поддержку WebSocket;
  • Подключим HTTPS через Certbot;
  • Проверим конфигурацию;
  • Разберемся, откуда берется 502 Bad Gateway и как искать причину.

С общей схемой разобрались. Но тут возникает логичный вопрос: если приложение и без Nginx умеет принимать HTTP-запросы, зачем вообще добавлять ещё один слой? 

Зачем приложению вообще нужен Nginx

На первый взгляд действительно кажется, что можно обойтись проще.

Приложение работает на порту 3000? Значит, можно открыть этот порт наружу и обращаться к сервису напрямую: http://203.0.113.10:3000

Технически такой вариант возможен.

Но для публичного приложения он быстро становится неудобным.

Во-первых, пользователю проще работать с обычным адресом https://example.com чем с: http://example.com:3000

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

Во-вторых, Nginx удобно использовать как точку входа для HTTPS.

Пользователь устанавливает защищенное соединение именно с Nginx:

Nginx работает с TLS-сертификатом, принимает HTTPS-запрос, а затем передает его приложению внутри VPS.

Это позволяет не заставлять каждое отдельное приложение самостоятельно заниматься сертификатами и внешним HTTPS.

В-третьих, Nginx помогает отделить публичную часть сервера от внутренней.

Само приложение может продолжать слушать только 127.0.0.1:3000, а наружу будут доступны стандартные порты: 80 и 443.

В итоге внешним трафиком управляет одна понятная точка — Nginx, а внутренние сервисы остаются за ней.

Кроме того, через Nginx удобно:

  • Проксировать несколько приложений на одном IP;
  • Передавать нужные HTTP-заголовки;
  • Более гибко настраивать доступ;
  • Работать с WebSocket;
  • перенаправлять HTTP на HTTPS;
  • Вести access- и error-логи;
  • Менять внутренний порт приложения без изменения публичного адреса.

То есть роли можно разделить довольно просто:

Приложение → выполняет свою основную работу

Nginx → принимает и направляет внешний трафик

Теперь у нас есть полная схема того, что именно мы собираемся построить. 

Готовим Ubuntu и приложение

Перед настройкой Nginx важно проверить две вещи: само приложение действительно работает на локальном порту, а домен уже указывает на наш VPS.

Звучит банально, но именно на этом этапе часто экономят пару минут, а потом полчаса ищут ошибку уже в Nginx.

Я знаю о чём говорю так как у самого был похожий случай. Приложение вроде запущено, а вроде и нет. Nginx настроен, домен подключен — а запросы не проходят. В итоге проблема оказалась не в reverse proxy, а в том, что сам сервис слушал не тот порт.

Поэтому сначала проверим фундамент, и только потом будем ставить Nginx поверх него.

Что должно быть готово заранее

Для дальнейшей настройки нам понадобится:

  • VPS с Ubuntu 24.04 или 26.04 LTS;
  • Пользователь с правами sudo;
  • Установленное и запущенное приложение;
  • Локальный порт, на котором оно слушает запросы;
  • Домен или поддомен;
  • A-запись домена, направленная на публичный IP VPS.

В статье будем считать, что приложение работает на: 127.0.0.1:3000

Домен для примеров будет: example.com

Это условные значения. В реальном проекте порт может быть, например, 8000, 5000, 8080 или любой другой.

Главное — точно знать, где именно приложение принимает запросы.

Перед установкой Nginx имеет смысл проверить версию Ubuntu: cat /etc/os-release

А также убедиться, что сервер вообще доступен и система работает штатно.

Если приложение запускается через systemd, PM2, Docker или другой менеджер процессов, желательно также убедиться, что его процесс действительно активен.

Проверяем, что приложение отвечает на локальном порту

Начнем с самого простого: curl http://127.0.0.1:3000

Если приложение работает, в ответ должны прийти данные самого сервиса.

Это может быть HTML:

    <h1>Hello from app</h1>

JSON:

    {"status":"ok"}

Или любой другой ожидаемый ответ.

Если же curl возвращает что-то вроде Connection refused, то до Nginx пока вообще нет смысла доходить.

Нужно сначала проверить:

  • Запущено ли приложение;
  • Правильный ли указан порт;
  • Не завершился ли процесс с ошибкой;
  • На каком адресе сервис действительно слушает соединения.

Посмотреть открытые TCP-порты можно командой: sudo ss -lntp

Например, нас интересует строка примерно такого вида: LISTEN 0 511 127.0.0.1:3000 0.0.0.0:*

Она означает, что процесс действительно слушает 127.0.0.1:3000.

Если приложение работает именно так — отлично.

Причем для reverse proxy это даже удобнее, чем слушать: 0.0.0.0:3000

Потому что 127.0.0.1 делает сервис доступным только внутри самого VPS. Внешний пользователь не сможет обратиться к нему напрямую, а весь публичный трафик пройдет через Nginx.

Итак, первая точка цепочки готова.

Теперь проверим вторую — домен.

Проверяем домен и DNS

Чтобы пользователь мог открыть https://example.com, домен должен указывать на публичный IP нашего VPS.

Обычно для этого в DNS создается A-запись: example.com → 203.0.113.10

Если используется поддомен: app.example.com → 203.0.113.10

Проверить, куда сейчас резолвится домен, можно, например, так: getent hosts example.com

Или: dig +short example.com

Если dig отсутствует, отдельно устанавливать его только ради этой проверки необязательно — getent обычно достаточно.

В результате должен отображаться публичный IP нашего VPS.

То есть ожидаем примерно такую картину:

Если домен указывает на другой IP, сначала нужно исправить DNS-запись.

Также учитывайте, что изменения DNS могут применяться не мгновенно. В зависимости от TTL и провайдера старое значение какое-то время может сохраняться в кеше.

Это особенно важно перед настройкой HTTPS: Мы будем использовать бесплатные сертификаты от Let's Encrypt, и Certbot должен иметь возможность проверить, что указанный домен действительно ведет на сервер, где запущен Nginx.

Итак, к этому моменту у нас должны выполняться два условия:

Теперь цепочка подготовлена с обеих сторон.

Остается поставить между доменом и приложением тот самый «ресепшен», о котором мы говорили в начале, — Nginx.

Устанавливаем Nginx

Установка через APT

На Ubuntu он устанавливается из стандартных системных репозиториев через APT, поэтому отдельные сторонние источники здесь подключать не потребуется.

Сначала обновим индекс пакетов: sudo apt update

Затем установим Nginx: sudo apt install -y nginx

APT скачает пакет, необходимые зависимости и установит веб-сервер в систему.

После установки Ubuntu обычно автоматически запускает сервис Nginx через systemd.

По итогу всё выглядит довольно просто и понятно:

Никакую reverse proxy-конфигурацию мы пока не создавали. На этом этапе Nginx просто установлен и работает со своей стандартной конфигурацией.

Теперь проверим, действительно ли сервис запустился.

Проверяем статус сервиса

Посмотрим состояние Nginx: sudo systemctl status nginx --no-pager

Нас интересует строка: Active: active (running)

Она означает, что процесс Nginx запущен и systemd считает сервис рабочим.

Для короткой проверки можно использовать: systemctl is-active nginx

Ожидаемый ответ: active

Заодно полезно проверить, включен ли Nginx в автозагрузку: systemctl is-enabled nginx

В нормальной ситуации получим: enabled

То есть после перезагрузки VPS Nginx запустится автоматически.

Если сервис по какой-то причине не работает, сначала попробуйте: sudo systemctl start nginx

А если он перешел в состояние failed, посмотреть причину можно через: sudo journalctl -u nginx --no-pager -n 50

Но если статус показывает active (running), значит можно идти дальше.

Сам факт работающего процесса еще не говорит нам, что Nginx действительно отвечает на HTTP-запросы. Поэтому сделаем еще одну простую проверку.

Проверяем стандартную страницу

Сначала обратимся к Nginx прямо с VPS: curl http://127.0.0.1

Если установка прошла успешно, в ответ придет HTML стандартной страницы Nginx.

Можно проверить и через браузер, открыв http://203.0.113.10 или уже наш домен: http://example.com

Если DNS успел обновиться и порт 80 доступен извне, должна появиться стандартная страница Nginx.

Она означает сразу несколько вещей:

  • Пакет установлен;
  • Сервис запущен;
  • Nginx слушает HTTP-порт 80;
  • Запросы действительно доходят до сервера.

На этом этапе Nginx уже принимает внешние запросы, но пока показывает собственную стандартную страницу.

Следующая задача — заменить это поведение: вместо стандартного контента Nginx должен передавать запросы дальше, на наше приложение по адресу 127.0.0.1:3000.

Для этого и настроим reverse proxy.

Настраиваем Nginx как reverse proxy

Создаем конфигурацию сайта

На Ubuntu конфигурации сайтов обычно хранятся в каталоге: /etc/nginx/sites-available/

А активные конфигурации подключаются через: /etc/nginx/sites-enabled/

Можно представить это как два списка:

  • sites-available → все подготовленные конфигурации
  • sites-enabled   → те, которые Nginx реально использует

Создадим отдельный файл для нашего домена:

sudo nano /etc/nginx/sites-available/example.com

И добавим базовую конфигурацию:

    server {
    listen 80;
    server_name example.com www.example.com;
    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}

Это немного. Всего несколько строк, но уже этого достаточно, чтобы понять основную логику:

  • Listen 80 говорит Nginx принимать обычные HTTP-запросы на порту 80.
  • Server_name указывает, для какого домена предназначен этот блок.
  • А внутри location / мы описываем, что делать со всеми запросами, которые приходят на сайт.

И вот тут появляется главный параметр всей статьи — proxy_pass.

Настраиваем proxy_pass

Строка proxy_pass http://127.0.0.1:3000 говорит Nginx: «Все запросы, попавшие в этот location, передавай приложению на локальном порту 3000».

Например, пользователь открывает: http://example.com/profile

Nginx принимает этот запрос и передает его приложению примерно так:

То есть внешний адрес и внутренний адрес — разные, но пользователь этого не замечает.

Это удобно еще и потому, что приложение можно потом перенести на другой локальный порт.

Например, было: 127.0.0.1:3000

Cтало: 127.0.0.1:5000

Помимо порта можно менять и адрес - если например понадобится переносить приложение или его часть на другой VPS.

Пользователям при этом не нужно менять адрес сайта. Мы просто исправим одну строку в конфигурации Nginx.

Можно сравнить это с переадресацией внутреннего телефонного номера: внешний номер компании остается тем же, а сотрудника внутри можно перевести на другой кабинет или линию.

Но одного proxy_pass для нормальной работы обычно мало.

Приложению часто важно знать, какой домен запросил пользователь, с какого IP он пришел и использовался ли HTTPS.

Эту информацию передают через HTTP-заголовки.

Передаем нужные заголовки

Добавим в 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;
}

Теперь разберем, зачем они нужны:

Заголовок Что передает приложению 
Host Домен, по которому пользователь обратился к серверу 
X-Real-IP Реальный IP клиента 
X-Forwarded-For Цепочку IP-адресов, через которые прошел запрос 
X-Forwarded-Proto Протокол исходного запроса: http или https

Например, пользователь открывает https://example.com и приходит с IP: 198.51.100.25

Приложение за Nginx сможет получить информацию примерно такого смысла:

    Host: example.com
X-Real-IP: 198.51.100.25
X-Forwarded-Proto: https

Без этих заголовков приложение видит только соединение от самого Nginx и может потерять часть информации об исходном клиенте.

Это особенно важно для:

  • Логирования;
  • Определения реального IP пользователя;
  • Генерации правильных ссылок;
  • Редиректов;
  • Работы приложений, которые должны понимать, пришел запрос по HTTP или HTTPS.

В итоге наш конфигурационный файл уже выглядит так:

    server {
    listen 80;
    server_name example.com www.example.com;
    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 пока о ней только «знает на диске». Нам еще нужно сделать ее активной.

Включаем конфигурацию

Для этого создадим символическую ссылку из sites-available в sites-enabled:

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/

Смысл здесь простой.

Файл /etc/nginx/sites-available/example.com хранит саму конфигурацию.

А ссылка в /etc/nginx/sites-enabled/ говорит Nginx, что эту конфигурацию нужно использовать.

На Ubuntu после установки также обычно активна стандартная конфигурация: /etc/nginx/sites-enabled/default

Если она нам больше не нужна, ее можно отключить: sudo rm /etc/nginx/sites-enabled/default

Сам файл из sites-available при этом можно не удалять — мы убираем только активную ссылку.

Теперь структура выглядит так:

На этом reverse proxy уже описан, но применять изменения вслепую мы не будем.

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

Проверяем конфигурацию и первый запрос через домен

Проверяем синтаксис через nginx -t

Запускаем: sudo nginx -t

Если всё в порядке, увидим что-то вроде:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Это означает, что Nginx смог прочитать основную конфигурацию и подключенные файлы без синтаксических ошибок.

Если же где-то допущена ошибка, команда обычно сразу укажет файл и строку.

Например:

    nginx: [emerg] unexpected "}" in /etc/nginx/sites-enabled/example.com:12

Тогда открываем указанный файл:

sudo nano /etc/nginx/sites-available/example.com

Исправляем проблему и снова выполняем: sudo nginx -t

Пока не получим: test is successful

Готово. Синтаксис проверен. Теперь можно применить настройки.

Перезагружаем Nginx

Чтобы Nginx перечитал конфигурацию, используем: sudo systemctl reload nginx

Обратите внимание: здесь именно reload, а не restart.

При reload Nginx перечитывает конфигурацию без полноценной остановки сервиса.

Условно:

restart

→ остановить Nginx

→ запустить заново

reload

→ оставить Nginx работающим

→ перечитать конфигурацию

Для обычного изменения server или location этого достаточно.

После reload можно еще раз проверить состояние: systemctl is-active nginx

Ожидаем: active

Теперь Nginx уже использует нашу конфигурацию:

Остается проверить, проходит ли реальный запрос через всю эту цепочку. Так сказать финальный чек-апп. 

Проверяем сайт через домен

Сначала можно сделать запрос прямо с VPS: curl http://example.com

Если всё настроено правильно, в ответ должен прийти уже не стандартный HTML Nginx, а содержимое нашего приложения.

Допустим, ранее локальная проверка curl http://127.0.0.1:3000 возвращала: Hello from app

Теперь тот же ответ должен прийти и через домен:

    curl http://example.com

Hello from app

Это важный момент.

До настройки путь выглядел так:

Теперь:

Само приложение при этом не менялось. Мы просто поставили перед ним новую публичную точку входа.

После этого открываем в браузере: http://example.com

Если приложение отображается корректно, reverse proxy уже работает.

Если вместо приложения снова появляется стандартная страница Nginx, скорее всего, используется не тот server-блок или осталась активна стандартная конфигурация default.

Если проще: Nginx выбирает конфигурацию по домену, на который пришел запрос. Для example.com он должен найти наш блок с server_name example.com. Если вместо него срабатывает стандартная конфигурация default, Nginx честно показывает свою приветственную страницу — он просто «не понял», что запрос нужно отправить нашему приложению.

Это примерно как прийти в бизнес-центр и назвать компанию на ресепшене. Если администратор не нашел нужную запись, он не отправит вас в нужный офис, а оставит в общем холле. Здесь таким «общим холлом» и выступает стандартная страница Nginx.

В таком случае полезно проверить ls -l /etc/nginx/sites-enabled/ и убедиться, что там действительно подключен: /etc/nginx/sites-enabled/example.com

Если же Nginx отвечает ошибкой 502 Bad Gateway, это уже другая ситуация: сам reverse proxy принимает запрос, но не может нормально связаться с приложением.

К диагностике 502 мы еще вернемся отдельно.

На этом этапе главное другое: обычный HTTP-запрос уже проходит через Nginx к локальному приложению.

Следующий шаг сложнее. Если приложение использует WebSocket, одного proxy_pass уже недостаточно — нужно отдельно передать параметры, которые позволяют HTTP-соединению переключиться в WebSocket-режим.

Добавляем поддержку WebSocket

Почему обычного proxy_pass недостаточно

Допустим, приложение поддерживает WebSocket по адресу: /ws

Пользователь подключается к: wss://example.com/ws

Запрос сначала приходит в Nginx, а тот должен передать его дальше на: 127.0.0.1:3000

Проблема в том, что WebSocket начинается как обычный HTTP-запрос, но затем клиент просит сервер переключить протокол соединения.

В запросе появляется специальный заголовок: Upgrade: websocket

По смыслу клиент говорит: «Мы начали разговор по HTTP, но теперь давай оставим соединение открытым и продолжим уже по WebSocket».

Nginx должен передать этот запрос приложению правильно.

Если этого не сделать, может получиться странная ситуация:

  • Обычный сайт работает
  • API работает
  • HTTPS работает
  • WebSocket — нет

Именно поэтому проблемы с WebSocket иногда сбивают с толку: кажется, что reverse proxy уже полностью настроен, ведь обычные страницы прекрасно открываются.

Но для WebSocket нужны дополнительные параметры.

Передаем Upgrade и Connection

Вернемся к нашему 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;
}

Теперь добавим поддержку WebSocket:

    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 Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

Здесь появились три важные строки:

  • proxy_http_version 1.1;
  • proxy_set_header Upgrade $http_upgrade;
  • proxy_set_header Connection "upgrade";

Разберем их:

Параметр Зачем нужен 
proxy_http_version 1.1 Использует HTTP/1.1 между Nginx и приложением 
Upgrade $http_upgrade Передает приложению запрос клиента на переключение протокола 
Connection "upgrade" Сообщает, что соединение должно перейти в новый режим 

Можно представить это как пересадку на поезд.

Обычный HTTP-запрос доезжает до станции Nginx, а затем клиент говорит: «Дальше я еду по другой линии».

Upgrade сообщает, на какую линию нужно переключиться, а Connection: upgrade подтверждает, что переключение действительно требуется.

Если приложение использует отдельный WebSocket-путь, например: /ws

можно настроить отдельный 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_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;
}

Так обычные HTTP-запросы и WebSocket можно разделить логически.

После изменения конфигурации снова действуем по уже знакомой схеме: sudo nginx -t

Если всё успешно: sudo systemctl reload nginx

Мы уже делали это раньше, поэтому здесь ничего нового: сначала проверяем, потом применяем.

Остается убедиться, что WebSocket действительно проходит через Nginx.

Проверяем WebSocket-соединение

Тут важно понимать одну вещь: обычный curl не всегда удобен для полноценной проверки WebSocket.

Он отлично подходит для HTTP, но WebSocket лучше тестировать клиентом, который умеет работать именно с таким типом соединения.

Если в приложении уже есть интерфейс с WebSocket-функцией — например, чат или live-уведомления — проще всего проверить ее прямо в браузере.

Откройте DevTools → NetworkWS.

После подключения должна появиться WebSocket-сессия.

Успешное переключение протокола обычно сопровождается статусом: 101 Switching Protocols

Это хороший знак.

Он означает:

Можно посмотреть и заголовки ответа:

Upgrade: websocket

Connection: upgrade

Если соединение сразу закрывается или вообще не появляется, проверьте:

  • Действительно ли приложение поддерживает WebSocket;
  • Правильный ли указан путь, например /ws;
  • Передаются ли Upgrade и Connection;
  • Не перепутан ли порт приложения;
  • Что пишут логи Nginx и самого приложения.

Для Nginx: sudo tail -f /var/log/nginx/error.log

Если приложение ведет собственные логи, параллельно полезно смотреть и их.

Если в DevTools виден статус 101 Switching Protocols, значит Nginx успешно пропускает не только обычные HTTP-запросы, но и постоянное WebSocket-соединение.

Теперь внешний трафик проходит через Nginx уже в двух режимах.

Остается добавить то, без чего публичный сайт сегодня лучше не оставлять, — HTTPS. Для этого подключим TLS-сертификат через Certbot и заставим Nginx принимать защищенные соединения на порту 443. Буквально крайний этап остался.

Подключаем HTTPS через Certbot

Для тех, кто не знает зачем это надо и что это такое, то проще говоря — HTTPS шифрует соединение между браузером и сервером. Благодаря этому данные не передаются по сети в открытом виде, а пользователь может убедиться, что действительно подключился к нужному домену.

Если продолжить нашу аналогию с ресепшеном, то Nginx уже умеет принимать посетителей и отправлять их в нужный кабинет. Теперь мы просто ставим на входе нормальную систему контроля доступа, чтобы никто по дороге не мог подслушать разговор.

Для сертификата будем использовать Certbot и бесплатные сертификаты Let's Encrypt.

Устанавливаем Certbot

На Ubuntu Certbot можно поставить через APT.

Сначала обновим индекс пакетов: sudo apt update

Теперь установим сам Certbot и плагин для Nginx:

sudo apt install -y certbot python3-certbot-nginx

Здесь:

Пакет Для чего нужен 
certbot Получает и обновляет TLS-сертификаты 
python3-certbot-nginx Позволяет Certbot автоматически работать с конфигурацией Nginx 

Проверить установку можно командой: certbot --version

Если версия отображается, инструмент готов.

Но перед получением сертификата стоит еще раз проверить две вещи.

  • Во-первых, домен должен уже указывать на этот VPS.
  • Во-вторых, Nginx должен отвечать на HTTP-запросы по порту 80.

Мы оба условия проверяли ранее, поэтому можем идти дальше.

Получаем сертификат для домена

Запросим сертификат для нашего примера:

sudo certbot --nginx -d example.com -d www.example.com

Если поддомен www.example.com вы не используете и DNS-записи для него нет, добавлять его в команду не нужно: sudo certbot --nginx -d example.com

Дальше Certbot попросит указать email, принять условия использования и выполнит проверку домена.

Let's Encrypt должен убедиться, что человек, который запрашивает сертификат для example.com, действительно контролирует сервер, на который этот домен указывает.

Процесс выглядит так:

Плагин --nginx удобен тем, что Certbot не только получает сертификат, но и может автоматически изменить конфигурацию Nginx.

В результате в ней появится обработка HTTPS на порту 443 и пути к сертификату.

По смыслу конфигурация станет выглядеть примерно так:

    listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

Вручную прописывать эти пути в нашем случае обычно не требуется — Certbot сделает это сам.

После успешного завершения он сообщит, где хранится сертификат и до какой даты он действует.

Теперь можно проверить сайт: https://example.com

Если всё прошло успешно, браузер должен открыть приложение уже по HTTPS.

Но есть еще один важный момент. Мы не хотим, чтобы пользователи продолжали случайно заходить на старый HTTP-адрес.

Проверяем редирект на HTTPS

После настройки Certbot обычно предлагает перенаправлять HTTP-запросы на HTTPS.

В результате пользователь, открывший http://example.com автоматически попадет на https://example.com

Проверить это удобно через: curl -I http://example.com

Параметр -I показывает только HTTP-заголовки ответа.

Если редирект работает, увидим статус вроде HTTP/1.1 301 Moved Permanently или другой redirect-status, а среди заголовков будет: Location: https://example.com/

То есть происходит примерно следующее:

Теперь даже пользователь, который вручную ввел старый HTTP-адрес, автоматически перейдет на защищенное соединение.

После этого полезно еще раз проверить уже HTTPS-ответ: curl -I https://example.com

Если сервер отвечает ожидаемым статусом, значит HTTPS работает не только визуально в браузере, но и на уровне HTTP.

Остается последний вопрос: сертификат выдан не навсегда. Что произойдет, когда срок его действия подойдет к концу?

Автопродление сертификата

Сертификаты Let's Encrypt имеют ограниченный срок действия, поэтому их нужно регулярно обновлять.

К счастью, Certbot умеет делать это автоматически.

На Ubuntu вместе с Certbot обычно устанавливается systemd timer, который периодически запускает проверку сертификатов.

Посмотреть его можно командой: systemctl list-timers | grep certbot

Но полагаться только на наличие таймера не стоит. Лучше заранее проверить, сможет ли Certbot действительно обновить сертификат.

Для этого есть безопасный тест: sudo certbot renew --dry-run

Поясняю. --dry-run имитирует реальное продление, но не заменяет действующий сертификат.

Проще говоря, это генеральная репетиция:

Если команда завершается без ошибок, значит механизм автоматического продления настроен корректно.

Проверить уже установленные сертификаты можно отдельно: sudo certbot certificates

Команда покажет:

  • Домены;
  • Путь к сертификату;
  • Срок действия;
  • Дату окончания.

После реального продления Certbot также должен заставить Nginx использовать обновленный сертификат, поэтому вручную менять пути в конфигурации каждый раз не требуется.

На этом публичная часть схемы уже выглядит полноценной:

Теперь осталось разобрать ситуацию, которая встречается при настройке reverse proxy особенно часто: Nginx сам работает, домен открывается, но вместо приложения пользователь получает 502 Bad Gateway.

Посмотрим, что эта ошибка вообще означает и где искать ее причину.

Если получаем 502 Bad Gateway

На первый взгляд ошибка 502 Bad Gateway  выглядит так, будто сломался сам Nginx. Но чаще ситуация другая:

    Nginx жив, запрос получил, а вот до приложения нормально достучаться не смог

.

То есть цепочка ломается где-то здесь:

Поэтому погнали проверять всё по порядку — от самого приложения к Nginx.

Приложение не запущено

Самая простая причина — сервис, на который смотрит Nginx, вообще не работает.

Допустим, в конфигурации указано: proxy_pass http://127.0.0.1:3000;

Но приложение на этом адресе не запущено.

Проверяем напрямую: curl http://127.0.0.1:3000

Если получаем «Connection refused» значит проблема пока вообще не в Nginx.

Nginx просто приходит по адресу, который мы ему дали, стучится в дверь, а за дверью никого нет.

Дальше уже смотрим, каким способом запускается приложение:

  1. Если через systemd: sudo systemctl status myapp --no-pager
  2. Если через PM2: pm2 status
  3. Если через Docker: docker ps -a

Если приложение действительно остановлено, его нужно запустить тем же способом, которым оно обычно управляется:

  1. Для systemd: sudo systemctl start myapp
  2. Для PM2: pm2 start APP_NAME
  3. Если процесс уже зарегистрирован в PM2, но остановлен: pm2 restart APP_NAME
  4. Для Docker: docker start CONTAINER_NAME
  5. А если проект запускается через Docker Compose: docker compose up -d

После запуска снова проверяем: curl http://127.0.0.1:3000

Если приложение отвечает локально, возвращаемся к домену и проверяем запрос уже через Nginx.

После запуска приложения снова проверяем: curl http://127.0.0.1:3000

И только если локальный запрос начал работать, есть смысл снова открывать домен.

В proxy_pass указан неправильный порт

Следующий сценарий коварнее: приложение работает, но не там, где его ищет Nginx.

Представим, что приложение слушает 127.0.0.1:5000 а в конфигурации осталось: proxy_pass http://127.0.0.1:3000;

Для Nginx это совершенно разные адреса.

Он не умеет угадывать: «Наверное, разработчик имел в виду порт 5000».

Он идет ровно туда, куда мы указали.

Посмотреть, какие порты действительно слушаются на сервере, можно так: sudo ss -lntp

Например, увидели: LISTEN 0 511 127.0.0.1:5000 0.0.0.0:*

Значит, исправляем Nginx: proxy_pass http://127.0.0.1:5000;

После изменения обязательно sudo nginx -t и затем sudo systemctl reload nginx

Здесь полезно держать в голове простое правило: порт в proxy_pass = порт, который реально слушает приложение.

Приложение слушает не тот интерфейс

Иногда порт указан правильно, но Nginx всё равно не может достучаться до приложения.

Причина может быть в том, на каком именно адресе приложение принимает соединения.

Например, в proxy_pass у нас указано: 127.0.0.1:3000

То есть Nginx пытается обратиться к приложению через локальный адрес самого VPS.

Проверим, где приложение действительно слушает порт 3000: sudo ss -lntp | grep 3000

Допустим, увидели: 192.168.1.10:3000

Это значит, что приложение принимает соединения на другом адресе сервера.

В таком случае запрос к 127.0.0.1:3000 может не сработать, даже если порт 3000 сам по себе выбран правильно.

Проще говоря:

  • Nginx ищет приложение здесь: 127.0.0.1:3000
  • А приложение ждет запросы здесь: 192.168.1.10:3000

Адреса не совпадают — поэтому соединение не устанавливается.

Есть и другой распространенный вариант: 0.0.0.0:3000

Он означает, что приложение принимает соединения на всех доступных IPv4-адресах VPS.

В таком случае Nginx обычно сможет обратиться к нему и через: 127.0.0.1:3000

Но если приложение должно быть доступно только через reverse proxy, чаще удобнее и безопаснее запускать его именно на: 127.0.0.1:3000

Тогда пользователь не сможет напрямую обращаться к внутреннему порту приложения, а весь внешний трафик будет проходить через Nginx.

Ошибка в конфигурации Nginx

Допустим, само приложение работает нормально и curl http://127.0.0.1:3000 возвращает ожидаемый ответ.

Но через домен сайт всё равно не открывается или Nginx показывает 502 Bad Gateway.

Значит, пора проверить уже сам Nginx.

Первым делом выполним: sudo nginx -t

Эта команда проверяет, может ли Nginx вообще прочитать нашу конфигурацию.

Например, если мы забыли ;, закрывающую скобку или допустили другую ошибку в записи, Nginx обычно сразу покажет файл и строку, где искать проблему.

Но здесь есть важный нюанс.

nginx -t проверяет только то, правильно ли написана конфигурация с точки зрения самого Nginx.

Он не знает, правильно ли мы указали адрес приложения.

Например: proxy_pass http://127.0.0.1:300;

Для Nginx эта строка совершенно нормальная.

Никакой ошибки в синтаксисе нет, поэтому проверка может спокойно вернуть:

syntax is ok

test is successful

Но наше приложение на самом деле работает здесь: 127.0.0.1:3000

Получается простая ситуация:

  • В конфигурации: 127.0.0.1:300
  • Приложение: 127.0.0.1:3000

Nginx всё понял правильно — просто мы сами отправили его не по тому адресу.

Это как правильно заполнить адрес на конверте, но перепутать номер дома. Почтальон работу выполнит без ошибок, а письмо всё равно придет не туда.

Поэтому после успешного sudo nginx -t отдельно проверяем адрес из proxy_pass: curl http://127.0.0.1:3000

Если приложение отвечает локально и nginx -t тоже проходит успешно, значит проблема уже не в очевидной ошибке конфигурации.

Тогда остается посмотреть, что именно происходит во время запроса. Для этого переходим к логам Nginx и самого приложения.

Смотрим логи Nginx и приложения

Основной журнал ошибок Nginx находится здесь: /var/log/nginx/error.log

Посмотреть последние строки: sudo tail -n 50 /var/log/nginx/error.log

А следить за ошибками в реальном времени: sudo tail -f /var/log/nginx/error.log

После этого можно снова открыть сайт в браузере.

Если Nginx не может подключиться к приложению, в логе часто будет сообщение вроде: connect() failed (111: Connection refused) while connecting to upstream

Ключевое здесь: Connection refused

То есть Nginx действительно попытался обратиться к upstream — нашему приложению — но соединение никто не принял.

Слово upstream в данном случае означает сервер или приложение, которому Nginx передает запрос.

В нашей схеме upstream — это: 127.0.0.1:3000

Но смотреть только логи Nginx недостаточно.

Само приложение тоже может запускаться, принимать соединение, а затем завершать запрос с ошибкой.

Поэтому параллельно проверяем его собственные логи.

  • Для systemd-сервиса: sudo journalctl -u myapp --no-pager -n 100
  • Для PM2: pm2 logs
  • Для Docker Compose: docker compose logs

В итоге диагностику 502 удобно проводить по одной и той же последовательности:

Главное — не воспринимать 502 Bad Gateway как отдельную поломку Nginx.

Чаще всего это сообщение означает вполне конкретную вещь:

    Nginx получил запрос от пользователя, но что-то помешало ему нормально передать этот запрос следующему звену цепочки.

А когда мы проверяем цепочку по одному участку за раз, причина обычно находится довольно быстро.

На этом основная настройка закончена. Осталось в последний раз проверить весь маршрут целиком — от работающего Nginx и валидной конфигурации до HTTPS и ответа самого приложения.

Финальная проверка

Основная настройка готова. Теперь быстро пройдемся по всей цепочке и убедимся, что каждый ее участок работает так, как задумано.

Начнем с самого Nginx:

Что проверяем Команда Что ожидаем 
Конфигурация корректна sudo nginx -t test is successful 
Nginx запущен systemctl is-active nginx active 
Nginx включен в автозагрузку systemctl is-enabled nginx enabled 

Теперь проверим путь от приложения до пользователя: 

Что проверяем Команда / способ Что ожидаем 
Приложение отвечает локально curl http://127.0.0.1:3000 Ответ приложения 
HTTP перенаправляется на HTTPS curl -I http://example.com Redirect на https://example.com
HTTPS работает curl -I https://example.com Успешный HTTP-ответ 
WebSocket работает DevTools → Network → WS 101 Switching Protocols 

Если все проверки проходят, схема работает целиком:

То есть Nginx принимает внешний трафик, работает с HTTPS и WebSocket, а само приложение при этом остается на локальном порту внутри VPS.

Заключение

На этом reverse proxy полностью настроен.

Мы установили Nginx, связали домен с приложением через proxy_pass, передали необходимые заголовки, добавили поддержку WebSocket и подключили HTTPS через Certbot. Заодно разобрали, как проверять конфигурацию и что делать, если вместо приложения появляется 502 Bad Gateway.

В итоге приложение может спокойно работать на локальном порту, а всю внешнюю работу — домен, HTTPS и маршрутизацию запросов — берет на себя Nginx. Это простая и удобная схема, которую затем можно расширять под несколько приложений, API, отдельные поддомены и другие сервисы.

FAQ

Можно ли использовать Nginx как reverse proxy сразу для нескольких приложений?

Да. Это один из самых распространенных сценариев.

Например:

  • api.example.com → 127.0.0.1:3000
  • admin.example.com → 127.0.0.1:4000
  • example.com → 127.0.0.1:5000

Для каждого домена или поддомена можно создать отдельный server-блок и указать свой proxy_pass. Пользователь будет видеть обычные домены, а Nginx сам решит, какому приложению передать запрос.

Что будет, если приложение перезапустится, а Nginx продолжит работать?

Nginx останется доступен, но пока приложение не вернется в работу, запросы к нему могут получать 502 Bad Gateway.

Именно поэтому само приложение обычно запускают через systemd, PM2, Docker или другой менеджер процессов с автозапуском и перезапуском после сбоя.

Nginx отвечает за доставку запроса, но он не может запустить приложение вместо вас.

Нужно ли открывать порт приложения, например 3000, в firewall?

Если приложение должно быть доступно только через Nginx и слушает 127.0.0.1:3000, открывать этот порт наружу обычно не требуется.

Пользователь обращается к Nginx через 80 или 443, а Nginx уже связывается с приложением внутри VPS.

Внешняя схема получается такой:

Порт 3000 остается внутренней частью инфраструктуры.

Почему в логах приложения вместо IP пользователя может отображаться IP Nginx?

Потому что с точки зрения приложения непосредственное соединение устанавливает Nginx.

Чтобы передать информацию об исходном клиенте дальше, мы используем заголовки вроде:

  • proxy_set_header X-Real-IP $remote_addr;
  • proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

Переменная $proxy_add_x_forwarded_for сохраняет существующую цепочку X-Forwarded-For и добавляет к ней адрес клиента.

Но само приложение тоже должно быть настроено так, чтобы доверять proxy-заголовкам только от известного reverse proxy.

Почему WebSocket может отключаться примерно через минуту, хотя сначала работает нормально?

Причина может быть в таймауте.

По документации Nginx, если проксируемый WebSocket-сервер некоторое время не передает данные, соединение по умолчанию может быть закрыто примерно через 60 секунд. Для долгоживущих соединений можно увеличить proxy_read_timeout или настроить приложение так, чтобы оно периодически отправляло WebSocket ping-фреймы.

Например: proxy_read_timeout 300s;

Но увеличивать значение вслепую не стоит — сначала убедитесь, что проблема действительно связана с таймаутом.

Почему для WebSocket нужно отдельно передавать Upgrade и Connection?

Потому что эти заголовки относятся к соединению между двумя соседними участниками HTTP-цепочки и не пересылаются reverse proxy автоматически.

Поэтому Nginx нужно явно сказать передать запрос на смену протокола приложению:

  • proxy_set_header Upgrade $http_upgrade;
  • proxy_set_header Connection "upgrade";

Если приложение согласится перейти на WebSocket, успешный handshake обычно завершится ответом 101 Switching Protocols.

Нужно ли вручную получать новый HTTPS-сертификат каждые несколько месяцев?

Обычно нет.

Certbot поддерживает автоматическое продление сертификатов, а проверить механизм заранее можно через: sudo certbot renew --dry-run

renew предназначен в том числе для автоматического использования и обновляет сертификаты, когда они приближаются к окончанию срока действия. --dry-run позволяет проверить будущую процедуру без замены действующего сертификата.

Список источников

  1. NGINX Documentation — Module ngx_http_proxy_module
  2. NGINX Documentation — WebSocket proxying
  3. Certbot Documentation — User Guide
  4. Certbot — Nginx installation instructions

Подпишитесь на нашу рассылку и получайте статьи и новости

    Ознакомьтесь с другими нашими материалами