Приложения Node.js, Python и другие сервисы: nginx как обратный прокси
Как опубликовать на 80/443 с HTTPS приложение, слушающее внутренний порт (3000, 8000), и запускать его автоматически через systemd.
На этой странице
Это руководство показывает, как опубликовать на портах 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 nginxAlmaLinux и 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.
Создайте отдельного пользователя без права входа в систему. Не запускайте приложение от
root: если приложение взломают, злоумышленник получит полный контроль над сервером.useradd --system --create-home --home-dir /srv/myapp --shell /usr/sbin/nologin myappСкопируйте файлы приложения в
/srv/myapp(Передача файлов на сервер (scp, rsync, SFTP)) и установите зависимости. Для Python создайте виртуальное окружение. В Ubuntu и Debian сначала установите пакетpython3-venv; в AlmaLinux и CentOS Stream модуль уже есть.apt install -y python3-venvpython3 -m venv /srv/myapp/venv /srv/myapp/venv/bin/pip install -r /srv/myapp/requirements.txt gunicornДля FastAPI установите
uvicornвместоgunicorn.Сделайте пользователя
myappвладельцем файлов:chown -R myapp:myapp /srv/myappВынесите переменные окружения и секреты (пароли, API-ключи) в отдельный файл. В AlmaLinux и CentOS Stream, если редактора
nanoнет, сначала установите его.dnf install -y nanonano /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Создайте файл службы:
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.jsDjango с gunicorn /srv/myapp/venv/bin/python -m gunicorn --workers 3 --bind 127.0.0.1:8000 project.wsgi:applicationFlask с gunicorn /srv/myapp/venv/bin/python -m gunicorn --workers 3 --bind 127.0.0.1:8000 app:appFastAPI с uvicorn /srv/myapp/venv/bin/python -m uvicorn main:app --host 127.0.0.1 --port 8000Загрузите новую службу, включите её (она будет запускаться и после перезагрузки сервера) и проверьте. В выводе
systemctl statusдолжно бытьactive (running).systemctl daemon-reload systemctl enable --now myapp systemctl status myappСледите за журналом приложения в реальном времени; выход — Ctrl+C.
journalctl -u myapp -f
После изменений:
# после обновления кода приложения
systemctl restart myapp
# после изменения файла myapp.service
systemctl daemon-reload
systemctl restart myappБезопасность Не прописывайте пароли и ключи прямо в файле службы (
Environment=): файл службы и вывод командыsystemctl showдоступны любому пользователю сервера. Храните секреты в файле с правамиchmod 600.
Шаг 3. Настройка nginx
Один раз на сервер, сколько бы приложений на нём ни было, создайте файл для WebSocket:
nano /etc/nginx/conf.d/connection-upgrade.confmap $http_upgrade $connection_upgrade { default upgrade; '' close; }Создайте файл сайта: в 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; } }Только в Ubuntu и Debian включите сайт:
ln -s /etc/nginx/sites-available/ДОМЕН /etc/nginx/sites-enabled/Проверьте конфигурацию и перезагрузите nginx. Команда
nginx -tдолжна вывести «syntax is ok» и «test is successful»; иначе исправьте файл до перезагрузки.nginx -t systemctl reload nginxОткройте
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/tcpAlmaLinux и 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
Установите certbot с плагином nginx, как описано в статье Веб-сервер с HTTPS: nginx или Apache и Let's Encrypt.
Получите сертификат:
certbot --nginx -d ДОМЕН -d www.ДОМЕНcertbot добавит в файл сайта настройки для порта 443 с сертификатом и перенаправление с HTTP на HTTPS. Строки
proxy_*останутся без изменений, а сертификат будет продлеваться автоматически.Откройте
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 |
Создайте A-запись для поддомена (
api→IP_СЕРВЕРА).Создайте новый файл для поддомена:
/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общий, второй раз его создавать не нужно.В Ubuntu и Debian включите сайт, как на шаге 3:
ln -s /etc/nginx/sites-available/api.ДОМЕН /etc/nginx/sites-enabled/Проверьте конфигурацию, перезагрузите 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-ассистента и нужные разрешения.