BLACKBOXЦентр помощи

Приложения Node.js, Python и другие сервисы: nginx как обратный прокси

Как опубликовать на 80/443 с HTTPS приложение, слушающее внутренний порт (3000, 8000), и запускать его автоматически через systemd.

общееОбновлено 2026-10-09~10 мин чтения
На этой странице

Это руководство показывает, как опубликовать на портах 80 и 443 с HTTPS приложение, которое слушает внутренний порт: например, Node.js на 3000 или Python (gunicorn, uvicorn) на 8000. Приложение работает как служба systemd, а nginx принимает запросы из интернета и передаёт их приложению. Вы работаете в терминале сервера, подключившись по SSH под пользователем root (Подключение к серверу по SSH (Windows, macOS, Linux)). IP-адрес сервера указан в карточке на странице «Мои серверы» панели app.blackbox.md (app.blackbox.md/my-servers). Настроек для приложений, веб-серверов или портов в панели нет.

Доступно: всем, у кого есть root-доступ по SSH к серверу BLACKBOX с Ubuntu 24.04, 22.04 или 20.04, Debian 12 или 11, AlmaLinux 9 либо CentOS Stream 9.

В примерах приложение называется myapp, лежит в /srv/myapp и слушает порт 3000. Замените эти значения и ДОМЕН (ваш домен без www) на свои.

Зачем ставить nginx перед приложением

Входящий трафик к серверу проходит через сетевую фильтрацию, которой управляет BLACKBOX. На новом сервере она пропускает только SSH (22), HTTP (80), HTTPS (443) и ping. Приложение, которое слушает порт 3000, 8000 или 8080, недоступно из интернета, даже если порт разрешён в ufw или firewalld. Вместо того чтобы просить открыть дополнительные порты, опубликуйте приложение через nginx:

посетитель ── HTTPS (443) ──> nginx ── HTTP ──> 127.0.0.1:3000 (приложение)
  • Приложение слушает только 127.0.0.1, поэтому напрямую из интернета к нему не подключиться.
  • HTTPS берёт на себя nginx: сертификат Let's Encrypt выпускается и продлевается автоматически с помощью certbot.
  • На одном сервере можно запустить несколько приложений, каждое на своём домене или поддомене, и все они будут работать на 80 и 443.

Перед началом

Условие Подробности
Домен указывает на сервер A-запись для ДОМЕН (и для www.ДОМЕН, если он нужен) указывает на IP_СЕРВЕРА: Как направить домен на сервер
Приложение запускается зависимости установлены, а приложение запускается вручную из терминала без ошибок
Порты 80 и 443 свободны их не слушает другой веб-сервер

Если у вас тариф «VPS hosting», не устанавливайте nginx, не посоветовавшись с поддержкой: панель управления, установленная на сервере, использует собственный веб-сервер на портах 80 и 443. В поддержке подскажут, как опубликовать приложение: Тарифы «VPS hosting» с панелью управления.

Установите nginx, если его ещё нет. Ubuntu и Debian:

apt update
apt install -y nginx

AlmaLinux и CentOS Stream:

dnf install -y nginx
systemctl enable --now nginx

Подробнее об установке nginx: Веб-сервер с HTTPS: nginx или Apache и Let's Encrypt.

Шаг 1. Приложение слушает только 127.0.0.1

Настройте приложение так, чтобы оно слушало 127.0.0.1 (только подключения с самого сервера), а не 0.0.0.0 (все адреса). Пишите именно 127.0.0.1, а не localhost: в некоторых системах localhost означает IPv6-адрес ::1, и nginx не найдёт приложение.

Приложение Как слушать 127.0.0.1
Node.js (http, Express) в коде: app.listen(3000, "127.0.0.1") (Express) или server.listen(3000, "127.0.0.1") (http)
Python с gunicorn (Django, Flask) параметр --bind 127.0.0.1:8000
Python с uvicorn (FastAPI) параметры --host 127.0.0.1 --port 8000
Контейнер Docker -p 127.0.0.1:3000:3000 в docker run или "127.0.0.1:3000:3000" в ports: в Docker Compose

Проверьте после запуска приложения. В выводе должно быть 127.0.0.1:3000; 0.0.0.0:3000 или *:3000 означает, что приложение слушает все адреса.

ss -tlnp | grep ':3000'

Внимание Docker обходит файрвол системы: порт, опубликованный как -p 3000:3000 (без 127.0.0.1), нельзя закрыть с помощью ufw или firewalld. Публикуйте контейнеры только на 127.0.0.1: Установка Docker и Docker Compose.

Шаг 2. Автозапуск через systemd

systemd запускает приложение вместе с сервером и перезапускает его, если оно завершилось с ошибкой. Если приложение работает в контейнере Docker с restart: unless-stopped, переходите сразу к шагу 3.

  1. Создайте отдельного пользователя без права входа в систему. Не запускайте приложение от root: если приложение взломают, злоумышленник получит полный контроль над сервером.

    useradd --system --create-home --home-dir /srv/myapp --shell /usr/sbin/nologin myapp
  2. Скопируйте файлы приложения в /srv/myapp (Передача файлов на сервер (scp, rsync, SFTP)) и установите зависимости. Для Python создайте виртуальное окружение. В Ubuntu и Debian сначала установите пакет python3-venv; в AlmaLinux и CentOS Stream модуль уже есть.

    apt install -y python3-venv
    python3 -m venv /srv/myapp/venv
    /srv/myapp/venv/bin/pip install -r /srv/myapp/requirements.txt gunicorn

    Для FastAPI установите uvicorn вместо gunicorn.

  3. Сделайте пользователя myapp владельцем файлов:

    chown -R myapp:myapp /srv/myapp
  4. Вынесите переменные окружения и секреты (пароли, API-ключи) в отдельный файл. В AlmaLinux и CentOS Stream, если редактора nano нет, сначала установите его.

    dnf install -y nano
    nano /etc/myapp.env

    Пишите по одной переменной на строку, в виде ИМЯ=значение, без export. Имена переменных зависят от вашего приложения, например:

    NODE_ENV=production
    DATABASE_URL=postgresql://myapp:ПАРОЛЬ@127.0.0.1:5432/myapp
    SECRET_KEY=СЕКРЕТНОЕ_ЗНАЧЕНИЕ

    Оставьте доступ к файлу только для root. systemd читает его до запуска приложения и передаёт переменные приложению.

    chmod 600 /etc/myapp.env
  5. Создайте файл службы:

    nano /etc/systemd/system/myapp.service
    [Unit]
    Description=myapp
    After=network.target
    
    [Service]
    User=myapp
    Group=myapp
    WorkingDirectory=/srv/myapp
    EnvironmentFile=/etc/myapp.env
    ExecStart=/usr/bin/node /srv/myapp/server.js
    Restart=on-failure
    RestartSec=5
    
    [Install]
    WantedBy=multi-user.target

    В строке ExecStart указывайте полный путь к программе. Для Node.js его покажет command -v node. Примеры для других приложений (замените project.wsgi, app:app и main:app на модуль вашего приложения):

    Приложение ExecStart=
    Node.js /usr/bin/node /srv/myapp/server.js
    Django с gunicorn /srv/myapp/venv/bin/python -m gunicorn --workers 3 --bind 127.0.0.1:8000 project.wsgi:application
    Flask с gunicorn /srv/myapp/venv/bin/python -m gunicorn --workers 3 --bind 127.0.0.1:8000 app:app
    FastAPI с uvicorn /srv/myapp/venv/bin/python -m uvicorn main:app --host 127.0.0.1 --port 8000
  6. Загрузите новую службу, включите её (она будет запускаться и после перезагрузки сервера) и проверьте. В выводе systemctl status должно быть active (running).

    systemctl daemon-reload
    systemctl enable --now myapp
    systemctl status myapp
  7. Следите за журналом приложения в реальном времени; выход — Ctrl+C.

    journalctl -u myapp -f

После изменений:

# после обновления кода приложения
systemctl restart myapp
# после изменения файла myapp.service
systemctl daemon-reload
systemctl restart myapp

Безопасность Не прописывайте пароли и ключи прямо в файле службы (Environment=): файл службы и вывод команды systemctl show доступны любому пользователю сервера. Храните секреты в файле с правами chmod 600.

Шаг 3. Настройка nginx

  1. Один раз на сервер, сколько бы приложений на нём ни было, создайте файл для WebSocket:

    nano /etc/nginx/conf.d/connection-upgrade.conf
    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }
  2. Создайте файл сайта: в Ubuntu и Debian — /etc/nginx/sites-available/ДОМЕН, в AlmaLinux и CentOS Stream — /etc/nginx/conf.d/ДОМЕН.conf. Содержимое одинаковое:

    server {
        listen 80;
        server_name ДОМЕН www.ДОМЕН;
    
        client_max_body_size 20m;
    
        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 $connection_upgrade;
        }
    }
  3. Только в Ubuntu и Debian включите сайт:

    ln -s /etc/nginx/sites-available/ДОМЕН /etc/nginx/sites-enabled/
  4. Проверьте конфигурацию и перезагрузите nginx. Команда nginx -t должна вывести «syntax is ok» и «test is successful»; иначе исправьте файл до перезагрузки.

    nginx -t
    systemctl reload nginx
  5. Откройте http://ДОМЕН в браузере: должно ответить ваше приложение. В AlmaLinux и CentOS Stream сначала выполните шаг 4.

Директива Назначение
proxy_pass адрес и порт приложения; должны совпадать с указанными на шаге 1 (для gunicorn или uvicorn из примеров — http://127.0.0.1:8000)
Host приложение получает домен, который запросил посетитель, а не 127.0.0.1:3000
X-Real-IP, X-Forwarded-For реальный адрес посетителя; без них приложение видит все запросы как пришедшие с 127.0.0.1
X-Forwarded-Proto http или https, в зависимости от того, как запрос пришёл в nginx
proxy_http_version 1.1, Upgrade, Connection нужны для WebSocket (чаты, уведомления в реальном времени)
client_max_body_size максимальный размер запроса, например загружаемого файла; по умолчанию nginx принимает 1 МБ

Шаг 4. SELinux и файрвол системы

AlmaLinux и CentOS Stream: SELinux

По умолчанию SELinux не позволяет nginx подключаться к приложениям на портах вроде 3000 или 8000. Без настройки ниже сайт отвечает 502 Bad Gateway, а в /var/log/nginx/error.log появляется «(13: Permission denied) while connecting to upstream». Разрешите подключение; -P сохраняет настройку после перезагрузки.

setsebool -P httpd_can_network_connect 1
getsebool httpd_can_network_connect

Вторая команда должна вывести httpd_can_network_connect --> on.

Файрвол системы

На новом сервере ufw и firewalld выключены или не установлены. Если вы включили файрвол, разрешите HTTP и HTTPS. Порт приложения (3000) разрешать не нужно: nginx подключается к приложению локально.

Ubuntu и Debian (ufw):

ufw allow 80,443/tcp

AlmaLinux и CentOS Stream (firewalld):

firewall-cmd --permanent --add-service=http --add-service=https
firewall-cmd --reload

Внимание Веб- и VNC-консоли для клиентов нет: сервером управляют только по SSH. Не включайте файрвол только ради этого руководства. Если всё же включаете, сначала разрешите SSH (порт 22) и держите открытой вторую SSH-сессию: Файрвол: сетевая фильтрация и ufw/firewalld на сервере.

Шаг 5. HTTPS с Let's Encrypt

  1. Установите certbot с плагином nginx, как описано в статье Веб-сервер с HTTPS: nginx или Apache и Let's Encrypt.

  2. Получите сертификат:

    certbot --nginx -d ДОМЕН -d www.ДОМЕН
  3. certbot добавит в файл сайта настройки для порта 443 с сертификатом и перенаправление с HTTP на HTTPS. Строки proxy_* останутся без изменений, а сертификат будет продлеваться автоматически.

  4. Откройте https://ДОМЕН и проверьте замок в браузере.

Приложение должно знать, что работает по HTTPS

Между nginx и приложением трафик остаётся HTTP. Если приложение не учитывает заголовок X-Forwarded-Proto, оно считает, что работает по HTTP: формирует ссылки и перенаправления на http://, cookie с флагом «secure» не работают, а иногда возникает бесконечный цикл перенаправлений. Настройте приложение так, чтобы оно доверяло заголовкам, которые nginx передаёт с 127.0.0.1:

Приложение Настройка
Express (Node.js) app.set("trust proxy", "loopback")
Django в settings.py: SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https"); добавьте домен и в ALLOWED_HOSTS
Flask app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1), с from werkzeug.middleware.proxy_fix import ProxyFix
FastAPI с uvicorn ничего менять не нужно: uvicorn по умолчанию читает заголовки, пришедшие с 127.0.0.1 (параметры --proxy-headers и --forwarded-allow-ips)

В Express, Flask и uvicorn эта же настройка позволяет приложению видеть реальный адрес посетителя. В Django реальный адрес находится в заголовке X-Real-IP, в коде он доступен как request.META["HTTP_X_REAL_IP"].

Несколько приложений на одном сервере

У каждого приложения своя служба systemd, свой внутренний порт и отдельный блок server в nginx со своим доменом или поддоменом:

Адрес Служба systemd proxy_pass
ДОМЕН, www.ДОМЕН myapp http://127.0.0.1:3000
api.ДОМЕН myapi http://127.0.0.1:8000
  1. Создайте A-запись для поддомена (api → IP_СЕРВЕРА).

  2. Создайте новый файл для поддомена: /etc/nginx/sites-available/api.ДОМЕН в Ubuntu и Debian, /etc/nginx/conf.d/api.ДОМЕН.conf в AlmaLinux и CentOS Stream. Поместите в него блок из шага 3 с server_name api.ДОМЕН; и proxy_pass http://127.0.0.1:8000;. Не копируйте файл первого сайта после того, как его изменил certbot: в нём окажется сертификат другого домена. Файл connection-upgrade.conf общий, второй раз его создавать не нужно.

  3. В Ubuntu и Debian включите сайт, как на шаге 3:

    ln -s /etc/nginx/sites-available/api.ДОМЕН /etc/nginx/sites-enabled/
  4. Проверьте конфигурацию, перезагрузите nginx и получите сертификат:

    nginx -t
    systemctl reload nginx
    certbot --nginx -d api.ДОМЕН

Сервис, которому нужен другой публичный порт

Через nginx публикуются веб-приложения: HTTP, HTTPS и WebSocket. Сервису с собственным протоколом (игровому серверу, MQTT-брокеру, своему TCP- или UDP-сервису) нужен отдельный публичный порт, а сетевая фильтрация по умолчанию его отсекает. Прежде чем устанавливать такой сервис, напишите в поддержку: укажите IP-адрес сервера, номер заказа (BBX-…), порт, протокол (TCP или UDP) и для чего он нужен. Каналы связи: Как связаться с поддержкой BLACKBOX. Как устроена фильтрация: Файрвол: сетевая фильтрация и ufw/firewalld на сервере.

Для баз данных публичные порты не запрашивайте: подключайтесь через SSH-туннель (Базы данных: MariaDB/MySQL и PostgreSQL).

Частые проблемы

Ошибки nginx записываются в /var/log/nginx/error.log. Последние строки журнала:

tail -n 50 /var/log/nginx/error.log
Что вы видите Вероятная причина Что делать
502 Bad Gateway; в error.log: «connect() failed (111: Connection refused) while connecting to upstream» приложение не запущено или слушает не тот порт или адрес, что указан в proxy_pass проверьте systemctl status myapp, ss -tlnp и journalctl -u myapp -n 50; исправьте порт в приложении или в proxy_pass
502 Bad Gateway в AlmaLinux или CentOS Stream; в error.log: «(13: Permission denied)» подключение блокирует SELinux setsebool -P httpd_can_network_connect 1 (шаг 4)
504 Gateway Time-out; в error.log: «upstream timed out (110: Connection timed out)» приложение отвечает дольше 60 секунд — это стандартный лимит nginx ускорьте операцию (например, вынесите её в фоновую задачу) или добавьте proxy_read_timeout 300s; в location /, затем nginx -t и перезагрузите nginx
413 Request Entity Too Large при загрузке файла запрос больше, чем client_max_body_size увеличьте значение (например, до 100m), затем nginx -t и перезагрузите nginx
После включения HTTPS приложение перенаправляет на http:// или уходит в цикл перенаправлений приложение не учитывает X-Forwarded-Proto настройка из таблицы на шаге 5
В журналах приложения у всех посетителей адрес 127.0.0.1 приложение не читает X-Real-IP или X-Forwarded-For настройка из шага 5; в Django читайте заголовок X-Real-IP
WebSocket не подключается или закрывается через минуту без трафика нет заголовков Upgrade и Connection или истёк proxy_read_timeout проверьте файл connection-upgrade.conf и строки для WebSocket из шага 3; отправляйте ping из приложения или увеличьте proxy_read_timeout
systemctl status myapp показывает status=203/EXEC, status=200/CHDIR или status=217/USER программа из ExecStart, папка из WorkingDirectory или пользователь из User= не существует или недоступен проверьте путь с помощью command -v node или ls -l; Node.js, установленный через nvm в /root, пользователь myapp запустить не сможет — установите Node.js на уровне системы
Django отвечает 400 Bad Request домена нет в ALLOWED_HOSTS добавьте ДОМЕН и www.ДОМЕН, затем перезапустите службу
http://IP_СЕРВЕРА:3000 не открывается в браузере приложение слушает 127.0.0.1, а порт 3000 отсекает сетевая фильтрация так и должно быть: открывайте приложение по адресу https://ДОМЕН

Другие причины, по которым сайт не открывается: Сайт не открывается.

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

Можно ли использовать Apache вместо nginx? Да, с модулями Apache proxy и proxy_http и директивой ProxyPass. В AlmaLinux и CentOS Stream нужна та же настройка SELinux httpd_can_network_connect. В этом руководстве описан только nginx.

Можно ли использовать PM2 вместо systemd? Да. PM2 запускает приложения Node.js после перезагрузки сервера через собственную службу systemd, которую создаёт команда pm2 startup. Не запускайте одно и то же приложение и через PM2, и через службу из этого руководства: второй экземпляр не сможет занять тот же порт.

Запустится ли приложение само после нажатия «Перезагрузить» в панели? Да, если служба включена командой systemctl enable --now (шаг 2). Команда ниже должна вывести enabled. О перезагрузке: Перезагрузка, остановка и запуск сервера.

systemctl is-enabled myapp

Может ли AI-ассистент выполнить эту настройку? Да, если у его токена есть разрешения «Выполнять любые команды от root на моих серверах» (servers:exec) и «Читать список и статус моих серверов» (servers:read). См. Примеры задач для AI-ассистента и нужные разрешения.

Не нашли ответ?Напишите нам — служба поддержки ответит. support@blackbox.md
Открыть тикет