Приложение слушает localhost:3000, а наружу нужно отдать его на 80 и 443, с доменом и HTTPS. Эту задачу решает reverse proxy, и в России это почти всегда nginx. Поставить его несложно — сложнее не наступить на главную мину: сертификат, который однажды тихо не продлился и положил прод среди ночи.
Разберём рабочий конфиг nginx как reverse proxy, выпуск сертификата Let's Encrypt и — самое важное — настройку автопродления, которое не ломается.
Зачем reverse proxy
Приложение можно было бы выставить наружу напрямую, но reverse proxy перед ним делает сразу несколько полезных вещей:
- терминирует TLS (HTTPS снаружи, обычный HTTP до приложения по localhost);
- маршрутизирует домены и пути на разные сервисы;
- отдаёт статику, не нагружая приложение;
- добавляет заголовки, лимиты, сжатие и буферизацию.
Базовый конфиг proxy_pass
Минимальный server-блок, который проксирует домен на приложение по localhost:3000:
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;
}
}
Заголовки proxy_set_header — не формальность. Без них приложение увидит IP самого nginx вместо реального клиента и решит, что работает по HTTP, даже когда снаружи HTTPS, — это ломает редиректы и генерацию ссылок. Проверить и перезагрузить конфиг:
sudo nginx -t # проверка синтаксиса
sudo systemctl reload nginx
SSL через certbot
Бесплатный сертификат от Let's Encrypt выпускается утилитой certbot. Плагин для nginx сам пропишет TLS в конфиг:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
certbot проверит, что домен указывает на этот сервер (HTTP-01 challenge по порту 80), выпустит сертификат, добавит в server-блок listen 443 ssl, пути к сертификату и редирект с HTTP на HTTPS. После этого сайт открывается по HTTPS.
Автопродление, которое не ломается
Сертификаты Let's Encrypt живут 90 дней. Это сделано намеренно, чтобы продление было автоматическим, а не ручным раз в год. certbot при установке заводит systemd-таймер (или cron-job), который дважды в день пытается продлить всё, чему осталось меньше 30 дней.
Проверить, что механизм жив:
systemctl list-timers | grep certbot
sudo certbot renew --dry-run
--dry-run прогоняет продление вхолостую против тестового сервера. Если он проходит — настоящее продление, скорее всего, тоже пройдёт. Если падает — лучше узнать об этом сейчас, а не за день до истечения.
Почему автопродление всё-таки ломается чаще всего:
- закрыт порт 80. HTTP-01 challenge ходит на 80; если файрвол его закрыл «за ненадобностью», продление молча падает.
- nginx не перезагрузился после обновления сертификата — новый файл выпущен, но процесс держит в памяти старый. Лечится deploy-хуком:
certbot renew --deploy-hook "systemctl reload nginx". - никто не смотрит на результат. Падение продления ничем себя не проявляет, пока сертификат не протухнет. Стоит повесить внешний мониторинг срока действия сертификата и алерт за неделю до истечения.
WebSocket и long-polling
Если приложение использует WebSocket (чаты, real-time, часть ботов), стандартного proxy_pass мало — нужно разрешить апгрейд протокола:
location / {
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_read_timeout 3600s; # не рвать долгие соединения
}
Без заголовков Upgrade/Connection соединение установится как обычный HTTP и тут же оборвётся, а в логах будет загадочный 400.
Типичные ошибки
- 502 Bad Gateway — nginx жив, а приложение за ним лежит или слушает другой порт. Смотреть надо на upstream, а не на nginx.
- Цикл редиректов — приложение не видит
X-Forwarded-Proto, считает соединение незащищённым и снова шлёт на HTTPS. - Mixed content — страница по HTTPS грузит ресурсы по HTTP; помогает корректный
X-Forwarded-Protoи относительные пути. - Тихо протухший сертификат — самый дорогой случай: всё работало месяцами, продление сломалось незаметно, прод встал. Поэтому
--dry-runи внешний мониторинг важнее, чем сам выпуск.
Чеклист
- В
proxy_passпроброшены заголовкиHost,X-Real-IP,X-Forwarded-For,X-Forwarded-Proto. - Сертификат выпущен и HTTP редиректит на HTTPS.
certbot renew --dry-runпроходит без ошибок.- Порт 80 открыт для HTTP-01 challenge.
- Настроен deploy-хук, перезагружающий nginx после продления.
- Есть внешний мониторинг срока действия сертификата.
- Для WebSocket добавлены заголовки
Upgrade/Connectionи увеличенproxy_read_timeout.
Reverse proxy и сертификаты — классический пример работы, которую делаешь один раз, а потом годами надеешься, что автопродление не сломается. На managed-платформе этот слой закрыт по умолчанию: на Hostim маршрутизация и SSL поднимаются автоматически (Traefik + Let's Encrypt), сертификаты продлеваются сами, а WebSocket работает без ручной правки конфигов — отдельный nginx и certbot держать не нужно.