Nginx как reverse proxy для приложения

Размещение backend-приложения за Nginx: локальный порт приложения, proxy_pass, заголовки, systemd-служба и диагностика ошибок 502.

Цели занятия

На этом занятии Nginx используется не только для отдачи статических файлов, но и как reverse proxy. Это нужно, когда сайт или API запускается отдельным приложением на локальном порту.

Тема

Reverse proxy

Nginx принимает внешний запрос и передаёт его внутреннему приложению.

Пример

Локальный backend

Приложение работает на 127.0.0.1:3000, а пользователи заходят на домен.

Результат

Публикация приложения

Наружу открыт только Nginx, внутренний порт приложения закрыт.

После занятия студент должен уметь:

1. Схема работы reverse proxy

Reverse proxy — это сервер, который принимает запрос от пользователя и передаёт его другому серверу или приложению.

Браузер пользователя
        ↓
https://example.com
        ↓
      Nginx
        ↓ proxy_pass
http://127.0.0.1:3000
        ↓
Backend-приложение

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

Пользователь не должен подключаться напрямую к порту приложения. Внешняя точка входа — это Nginx.

2. Тестовое приложение

Для практики можно использовать простой сервер на Python. Он будет слушать локальный порт 3000.

Создание директории

sudo mkdir -p /opt/student-app
sudo chown -R $USER:$USER /opt/student-app
cd /opt/student-app

Создание файла приложения

nano app.py

Содержимое файла app.py

from http.server import BaseHTTPRequestHandler, HTTPServer

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/html; charset=utf-8")
        self.end_headers()
        self.wfile.write("<h1>Приложение работает через Nginx</h1>".encode("utf-8"))

server = HTTPServer(("127.0.0.1", 3000), Handler)
server.serve_forever()

Проверка запуска вручную

python3 app.py

В другом терминале нужно проверить ответ:

curl http://127.0.0.1:3000
Приложение слушает 127.0.0.1, поэтому оно доступно только внутри сервера. Это безопаснее, чем слушать 0.0.0.0 без необходимости.

3. Создание systemd-службы для приложения

Приложение не должно запускаться вручную после каждого перезапуска сервера. Для этого создаётся systemd-служба.

Файл службы

sudo nano /etc/systemd/system/student-app.service

Содержимое файла

[Unit]
Description=Student test web application
After=network.target

[Service]
WorkingDirectory=/opt/student-app
ExecStart=/usr/bin/python3 /opt/student-app/app.py
Restart=always
User=www-data
Group=www-data

[Install]
WantedBy=multi-user.target

Права на директорию приложения

sudo chown -R www-data:www-data /opt/student-app

Запуск службы

sudo systemctl daemon-reload
sudo systemctl enable student-app
sudo systemctl start student-app
sudo systemctl status student-app

Проверка приложения

curl http://127.0.0.1:3000
Если приложение не отвечает, сначала проверяется sudo systemctl status student-app, затем логи через journalctl.

4. Настройка Nginx для проксирования

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

Создание конфига сайта

sudo nano /etc/nginx/sites-available/student-app

Конфигурация reverse proxy

server {
    listen 80;
    server_name 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;
    }
}

Включение сайта

sudo ln -s /etc/nginx/sites-available/student-app /etc/nginx/sites-enabled/

Проверка и применение

sudo nginx -t
sudo systemctl reload nginx

Проверка через Nginx

curl http://example.com
Если всё настроено верно, ответ будет приходить от приложения, но пользователь будет обращаться только к Nginx.

5. Proxy-заголовки

Когда Nginx передаёт запрос приложению, он может добавить служебные заголовки. Они помогают приложению понимать, какой домен запрашивал пользователь, какой был IP клиента и какой протокол использовался.

Заголовок Назначение
Host Передаёт домен, на который пришёл запрос.
X-Real-IP Передаёт реальный IP клиента.
X-Forwarded-For Передаёт цепочку IP-адресов клиента и прокси.
X-Forwarded-Proto Передаёт исходный протокол: HTTP или HTTPS.

Базовый набор заголовков

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;

6. Firewall при reverse proxy

При правильной схеме наружу открываются только порты Nginx. Внутренний порт приложения не открывается в UFW.

Правильный набор правил

sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status

Что не нужно открывать

sudo ufw allow 3000/tcp
Порт 3000 не нужно открывать наружу, если к приложению обращается Nginx через 127.0.0.1.

Проверка слушающего адреса

sudo ss -tulpn | grep ':3000'

Безопасный вариант:

127.0.0.1:3000

Более рискованный вариант:

0.0.0.0:3000

7. Диагностика reverse proxy

Самая частая ошибка при reverse proxy — 502 Bad Gateway. Она означает, что Nginx получил запрос, но не смог получить корректный ответ от backend-приложения.

Проблема Возможная причина Проверка
502 Bad Gateway Приложение не запущено sudo systemctl status student-app
502 Bad Gateway Неверный порт в proxy_pass sudo ss -tulpn | grep ':3000'
Nginx не запускается Ошибка в конфигурации sudo nginx -t
Сайт не открывается снаружи Закрыт порт 80 или 443 sudo ufw status
Приложение работает локально, но не через домен Не включён server block или неверный server_name ls -l /etc/nginx/sites-enabled/

Команды диагностики

sudo nginx -t
sudo systemctl status nginx
sudo systemctl status student-app
sudo journalctl -u student-app -n 50
sudo tail -n 100 /var/log/nginx/error.log
sudo ss -tulpn | grep ':3000'
curl http://127.0.0.1:3000
curl http://example.com
Проверка идёт от внутреннего к внешнему: сначала приложение на локальном порту, потом Nginx, потом firewall и домен.

8. Практическая работа

Задание 1. Создайте директорию приложения и файл app.py.
sudo mkdir -p /opt/student-app
sudo chown -R $USER:$USER /opt/student-app
cd /opt/student-app
nano app.py
Задание 2. Запустите приложение вручную и проверьте ответ с локального порта.
python3 app.py
curl http://127.0.0.1:3000
Задание 3. Создайте systemd-службу student-app, запустите её и включите автозагрузку.
sudo systemctl daemon-reload
sudo systemctl enable student-app
sudo systemctl start student-app
sudo systemctl status student-app
Задание 4. Настройте Nginx как reverse proxy на http://127.0.0.1:3000.
sudo nano /etc/nginx/sites-available/student-app
sudo ln -s /etc/nginx/sites-available/student-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Задание 5. Проверьте, что порт приложения не открыт наружу через UFW.
sudo ufw status
sudo ss -tulpn | grep ':3000'
Задание 6. Смоделируйте ошибку 502 Bad Gateway: остановите приложение и попробуйте открыть сайт через Nginx. Затем найдите причину в логах и запустите приложение обратно.
sudo systemctl stop student-app
curl http://example.com
sudo tail -n 50 /var/log/nginx/error.log
sudo systemctl start student-app

9. Мини-проверка

Вопросы

  1. Что такое reverse proxy?
  2. Зачем ставить Nginx перед backend-приложением?
  3. Что делает директива proxy_pass?
  4. Почему приложение лучше слушать на 127.0.0.1?
  5. Зачем нужна systemd-служба для приложения?
  6. Какая команда проверяет конфигурацию Nginx?
  7. Какие порты нужно открыть наружу для сайта?
  8. Нужно ли открывать порт 3000 в UFW?
  9. Что означает ошибка 502 Bad Gateway?
  10. С какой проверки нужно начинать диагностику 502?

Ответы для самопроверки

  1. Сервер, который принимает внешний запрос и передаёт его внутреннему приложению.
  2. Чтобы принимать запросы на стандартных портах, использовать HTTPS и не открывать внутренний порт приложения.
  3. Передаёт запросы на указанный backend-адрес.
  4. Так приложение доступно только внутри сервера.
  5. Чтобы приложение запускалось как служба и автоматически поднималось после перезагрузки.
  6. sudo nginx -t.
  7. 80/tcp и 443/tcp, а также SSH-порт для администрирования.
  8. Нет, если Nginx обращается к приложению локально.
  9. Nginx не смог получить корректный ответ от backend-приложения.
  10. С проверки статуса приложения и локального ответа на 127.0.0.1:3000.

10. Итоговый порядок публикации приложения

1. Запустить приложение локально:
   curl http://127.0.0.1:3000

2. Создать systemd-службу:
   sudo systemctl enable student-app
   sudo systemctl start student-app

3. Проверить службу:
   sudo systemctl status student-app

4. Настроить Nginx:
   proxy_pass http://127.0.0.1:3000;

5. Проверить Nginx:
   sudo nginx -t
   sudo systemctl reload nginx

6. Проверить firewall:
   sudo ufw status

7. Проверить сайт через домен:
   curl http://example.com

8. При ошибке смотреть:
   sudo journalctl -u student-app -n 50
   sudo tail -n 100 /var/log/nginx/error.log
Главный вывод: Nginx принимает внешние запросы, а приложение работает внутри сервера на локальном порту.