NginxSSLDevOps

Nginx как reverse proxy и Let's Encrypt: конфиг и автопродление SSL

5 мин чтения

Приложение слушает 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 держать не нужно.

Читать дальше