ESC

Start typing to search across invoices, services, domains, tickets, and more...

Search... Ctrl+K
Linux-сервер

Обратный прокси Nginx: Node/Python-приложение, WebSocket, реальный IP, HTTPS и ошибка 502

6 шагов 15 мин чтения 11 просмотров 0
Содержание

Приложения на Node.js, Python (Flask, Django, FastAPI) или Go обычно слушают локальный порт вроде 127.0.0.1:3000, и открывать его напрямую в интернет не стоит. В этой статье показано, как настроить обратный прокси Nginx для домена, правильно пробросить WebSocket, передать приложению реальный IP клиента, подключить бесплатный HTTPS через certbot и найти причину ошибки 502 Bad Gateway. Команды подходят для Debian/Ubuntu и Rocky Linux/AlmaLinux.

Шаг 1: Установите Nginx и проверьте backend-приложение

Сначала убедитесь, что приложение отвечает локально, иначе прокси будет возвращать только 502. Привязывайте приложение к 127.0.0.1, чтобы наружу смотрел только Nginx, и запускайте его через systemd или pm2 для автоматического перезапуска после сбоя.

# Debian / Ubuntu
apt update && apt install -y nginx
# Rocky Linux / AlmaLinux
dnf install -y nginx

systemctl enable --now nginx

# Make sure the backend app is listening locally (example: port 3000)
ss -lntp | grep 3000
curl -I http://127.0.0.1:3000

Шаг 2: Конфигурация обратного прокси Nginx с поддержкой WebSocket

Направьте DNS домена на IP сервера и создайте /etc/nginx/conf.d/app.conf. Блок map корректно обрабатывает и обычные запросы, и апгрейд до WebSocket; заголовки X-Forwarded-* передают приложению IP клиента и исходный протокол; увеличенный proxy_read_timeout не даёт обрывать долгие соединения. Для загрузки больших файлов увеличьте client_max_body_size.

# /etc/nginx/conf.d/app.conf  (included on Debian/Ubuntu and Rocky/AlmaLinux)
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    listen [::]:80;
    server_name app.example.com;

    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;

        # WebSocket support
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        proxy_connect_timeout 10s;
        proxy_read_timeout    300s;
    }
}

Шаг 3: Проверка конфигурации, перезагрузка Nginx и файрвол

Перед каждой перезагрузкой проверяйте синтаксис командой nginx -t. На Rocky/AlmaLinux SELinux запрещает Nginx подключаться к backend-портам, пока не включён httpd_can_network_connect. Откройте порты 80 и 443 (подробнее — в статье про файрвол Linux).

nginx -t && systemctl reload nginx
curl -I -H "Host: app.example.com" http://127.0.0.1

# Rocky/AlmaLinux (SELinux): allow Nginx to connect to backend ports
setsebool -P httpd_can_network_connect 1

# Open HTTP/HTTPS in the firewall
ufw allow 'Nginx Full'                                   # Debian/Ubuntu with ufw
firewall-cmd --permanent --add-service=http --add-service=https
firewall-cmd --reload                                    # Rocky/AlmaLinux

Шаг 4: Реальный IP клиента в приложении

За обратным прокси все запросы выглядят так, будто приходят с 127.0.0.1. Nginx уже передаёт настоящий адрес в X-Real-IP и X-Forwarded-For, осталось научить фреймворк доверять локальному прокси. Если перед Nginx стоит CDN или балансировщик, восстановите адрес модулем real_ip и доверяйте только диапазонам CDN, чтобы заголовок нельзя было подделать.

// Node.js / Express: trust the proxy on localhost
app.set('trust proxy', 'loopback');
// req.ip now returns the real client IP

# Python Flask / any WSGI app
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)

# Gunicorn
gunicorn --bind 127.0.0.1:8000 --forwarded-allow-ips="127.0.0.1" app:app

# Only if Nginx itself sits behind a CDN or load balancer (http or server block):
set_real_ip_from 203.0.113.0/24;     # the CDN / LB address ranges
real_ip_header X-Forwarded-For;
real_ip_recursive on;

Шаг 5: HTTPS для обратного прокси через certbot

Плагин certbot для Nginx получает сертификат Let's Encrypt, добавляет прослушивание 443 в server-блок и с ключом --redirect перенаправляет HTTP на HTTPS. Домен уже должен указывать на сервер, а порт 80 — быть доступен из интернета. Сертификат действует 90 дней и продлевается автоматически.

# Debian / Ubuntu
apt install -y certbot python3-certbot-nginx
# Rocky Linux / AlmaLinux (EPEL)
dnf install -y epel-release && dnf install -y certbot python3-certbot-nginx

certbot --nginx -d app.example.com --redirect -m [email protected] --agree-tos --no-eff-email

# Renewal runs from a systemd timer; test it:
certbot renew --dry-run
systemctl list-timers | grep -i certbot
Если после включения HTTPS приложение по-прежнему генерирует ссылки http://, оно не читает X-Forwarded-Proto. Настройте доверие прокси, как в шаге 4.

Шаг 6: Диагностика ошибки Nginx 502 Bad Gateway

502 означает, что Nginx не получил корректного ответа от backend. Начните с журнала ошибок Nginx: Connection refused — приложение остановлено или слушает другой порт, Permission denied — чаще всего SELinux. Учтите, что в Node 17+ «localhost» может разрешаться в IPv6 ::1: приложение слушает [::1]:3000, а Nginx стучится на 127.0.0.1.

tail -n 50 /var/log/nginx/error.log

# connect() failed (111: Connection refused)  -> app stopped or wrong port
# connect() ... (13: Permission denied)       -> SELinux: setsebool -P httpd_can_network_connect 1
# upstream prematurely closed connection      -> app crashed / restarted
# upstream timed out (110)                    -> app too slow: raise proxy_read_timeout or fix the app

systemctl status myapp
journalctl -u myapp -n 100 --no-pager
ss -lntp | grep -E ':3000|:8000'      # 127.0.0.1:3000 vs [::1]:3000 ?

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

WebSocket возвращает 400 или сразу отключается?

Проверьте proxy_http_version 1.1 и оба заголовка Upgrade и Connection. Если перед сервером есть CDN, включите WebSocket и там. Неактивные соединения по умолчанию закрываются через 60 секунд — увеличьте proxy_read_timeout или отправляйте heartbeat из приложения.

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

Да. Создайте по server-блоку на каждый домен с разными портами или распределяйте пути через location в рамках одного домена. Если каждому сайту нужен отдельный IP, смотрите статью про отдельный IP для каждого сайта в Nginx.

Вместо 502 появляется 504 Gateway Timeout?

504 означает, что backend отвечает слишком долго. Оптимизируйте медленный запрос или увеличьте proxy_read_timeout и проверьте в логах приложения, не зависает ли оно на базе данных или внешних API.

Если проблема не решена, создайте тикет — техническая поддержка IMIDC работает 24/7. Укажите IP сервера, версию ОС, выполненные команды и полный текст ошибки, чтобы мы быстрее нашли причину.

Помог ли вам данный ответ?

Похожие руководства