Как настроить редирект с HTTP на HTTPS в Nginx
Для перенаправления всего HTTP-трафика создайте отдельный блок server на порту 80. Он должен вернуть ответ 301 с адресом того же хоста, пути и query string в схеме HTTPS. TLS-сертификат, сайт, reverse proxy и логика приложения остаются в отдельном блоке на порту 443.
Минимальная схема для сайта выглядит так: listen 80, явный список доменов в server_name и return 301 https://$host$request_uri;. Такой redirect server не обрабатывает файлы и не подключается к upstream.
Для API код 301 подходит не всегда. При постоянном переходе на HTTPS используйте 308, если клиенты должны сохранить метод и тело запроса. Перед публикацией проверьте поддержку 307 или 308 в SDK, webhook-поставщиках и промежуточных прокси.
Минимальный server block на 80 порту
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
listen 80;принимает обычные HTTP-соединения.server_nameперечисляет имена, для которых работает правило. Имяwww.example.comне считается автоматически включенным вexample.com.return 301сразу завершает запрос ответом с постоянным редиректом.$hostсохраняет имя хоста, а$request_uriпереносит путь и параметры запроса.
При наличии IPv6 добавьте отдельную строку listen [::]:80;. Она нужна, если у домена есть AAAA-запись и сервер действительно принимает IPv6-соединения. Без такой строки клиент по IPv6 может попасть на другой сервер или получить ошибку соединения.
При безусловном return на уровне server блок location / не нужен. Не размещайте в redirect server root, proxy_pass, авторизацию и правила приложения. Чем меньше логики выполняется до перехода на HTTPS, тем проще проверить результат.
Конфигурация HTTPS-блока для обычного сайта
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/example.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
Блок на 443 принимает TLS, проверяет сертификат и отдает содержимое сайта. Сертификат должен покрывать все имена из server_name. Для SPA, CMS или приложения с backend правило внутри location / меняется под конкретную архитектуру.
Полный порядок ручной настройки сертификата, включая проверку цепочки и параметры TLS, разобран в руководстве по установке SSL-сертификата на Nginx. Редирект с HTTP не заменяет выпуск, продление и проверку сертификата.
Для отдельного тестового стенда удобно использовать VPS с доступными портами 80 и 443. Облачная инфраструктура Timeweb Cloud подходит для проверки конфигурации Nginx, API и DNS до переноса правила на рабочий сервер.
Проверка результата через curl
Проверяйте редирект с реальным DNS-именем. Запрос к localhost может выбрать другой виртуальный хост, если заголовок Host не совпадает с ожидаемым server_name.
curl -I http://example.com/path?check=1
curl -IL http://example.com/path?check=1
В первом ответе ожидайте статус 301 и заголовок Location:
HTTP/1.1 301 Moved Permanently
Location: https://example.com/path?check=1
Проверьте четыре значения: схема должна стать https, имя хоста должно быть ожидаемым, путь /path должен сохраниться, параметр check=1 не должен исчезнуть. Команда curl -IL проходит всю цепочку и показывает финальный ответ HTTPS.
До включения постоянного кода определите технические исключения. ACME HTTP-01 использует путь /.well-known/acme-challenge/, health check и мониторинг могут требовать ответ 200 вместо 3xx. Поисковый бот обычно корректно обрабатывает постоянный редирект, но файл robots.txt, canonical и sitemap должны быть доступны по HTTPS.
Как сохранить URL и избежать лишних редиректов
Исходный адрес http://host/path?query должен преобразоваться в https://host/path?query за один ответ. Потеря пути или параметров ломает callback-адреса, ссылки с фильтрами, API-маршруты и служебные идентификаторы.
Роль $host и $request_uri
Переменная $host содержит нормализованное имя хоста текущего запроса. В отличие от $http_host, она не переносит порт из заголовка Host. Это удобно для стандартной схемы HTTPS на порту 443.
$request_uri содержит исходный URI вместе с query string. Для запроса /api/v1/items?id=10 значение переменной равно /api/v1/items?id=10. Поэтому правило сохраняет маршрут и параметр без ручного разбора строки.
return 301 https://$host$request_uri;
Если сайт использует один канонический домен, задайте его явно:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
Этот вариант сразу отправляет оба имени на https://example.com. Сертификат HTTPS все равно должен покрывать www.example.com, поскольку клиент сначала устанавливает TLS-соединение с исходным именем.
Используйте явный домен, когда политика сайта требует единственного host. Не направляйте неизвестные значения Host через $host на рабочий сайт: для них нужен отдельный default_server с понятной политикой.
301, 302, 307 и 308: какой код выбрать
| Код | Назначение | Поведение метода и тела |
|---|---|---|
| 301 | Постоянный переход сайта на HTTPS | Клиент может изменить POST на GET, поведение зависит от реализации |
| 302 | Временная проверка или короткое обслуживание | Сохранение метода не гарантируется для всех клиентов |
| 307 | Временный переход API с сохранением запроса | Метод и тело должны сохраниться |
| 308 | Постоянный переход API на HTTPS | Метод и тело должны сохраниться, нужна проверка совместимости |
Для публичного сайта после проверки обычно выбирают 301. Браузеры, поисковые системы и CDN воспринимают его как постоянную замену адреса и могут кэшировать результат.
Для API с POST, PUT, PATCH или DELETE сначала проверьте 307. Он временный и подходит для теста. После подтверждения совместимости можно заменить его на 308. 301 рискован для таких запросов: часть клиентов повторяет запрос как GET или не отправляет тело повторно.
Код 302 удобен во время короткой проверки, но его не следует оставлять постоянной политикой перехода на HTTPS. Он хуже описывает канонический адрес и не дает надежной гарантии сохранения метода.
Как не получить цепочку HTTP -> www -> HTTPS
Цепочка из двух редиректов увеличивает время ответа и добавляет точку отказа. Она возникает, когда HTTP сначала отправляет запрос на www, а второй блок меняет схему на HTTPS, либо когда сначала меняется домен, а потом схема.
Для единственного канонического домена отправляйте все варианты сразу в конечный адрес:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
Нужно отдельно обработать запрос, который уже пришел на HTTPS с неканоническим именем:
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
return 301 https://example.com$request_uri;
}
Сертификат для этого блока должен быть выдан на оба имени. Основной HTTPS-блок для example.com обслуживает сайт и не делает дополнительный переход.
Отдельная настройка канонического домена с www и без www приведена в статье о редиректе с www на без-www в Nginx. Если адрес нужно менять вместе с портом, учитывайте разницу между HTTP-редиректом и проксированием, описанную в материале о перенаправлении портов в Nginx.
Редирект HTTP на HTTPS для API
API лучше публиковать сразу по HTTPS, а HTTP оставить переходным endpoint. Такой endpoint нужен для контролируемой миграции клиентов, но он не должен обрабатывать чувствительные данные и подменять полноценную настройку TLS, авторизации, CORS или rate limit.
Базовый redirect server для API
server {
listen 80;
server_name api.example.com;
return 308 https://$host$request_uri;
}
Ответ 308 указывает клиенту постоянный адрес и требует повторить тот же HTTP-запрос по HTTPS. Для GET и HEAD результат обычно очевиден. Для POST, PUT, PATCH и DELETE нужно проверить конкретный клиент, размер тела, повторную отправку и защиту от дублирования операции.
Временный вариант для проверки выглядит так:
server {
listen 80;
server_name api.example.com;
return 307 https://$host$request_uri;
}
Сделайте несколько тестов с безопасным endpoint, затем проверьте интеграцию с реальным SDK. Не отправляйте production-токены в диагностических командах.
Когда API лучше не перенаправлять
Редирект не подходит, если старый клиент не умеет обрабатывать 307 или 308, webhook-поставщик теряет тело запроса, прокси меняет метод или операция имеет побочный эффект при повторении. Проблема встречается и у нестандартных HTTP-методов, для которых клиент не реализовал повторное выполнение.
В таких случаях заранее измените endpoint в клиенте или настройках интеграции. Для старого адреса можно вернуть документированный ответ об обязательном HTTPS, например 426, и не принимать бизнес-запрос. Адрес webhook нужно обновить у поставщика, а не рассчитывать на автоматическое перенаправление.
Клиент, который отправляет секреты на HTTP-адрес, уже передает их без шифрования до получения ответа 3xx. Сам редирект не защищает первый запрос. Поэтому безопасная миграция начинается с изменения endpoint в клиенте, а HTTP-переход оставляют для совместимых сценариев.
Что происходит с токенами, Cookie и заголовками
Поведение заголовка Authorization при редиректе зависит от клиента. SDK может удалить его при смене origin или host и не восстановить при повторном запросе. Автоматически считать credentials сохраненными нельзя.
Cookie с флагом Secure браузер не отправляет через HTTP. Cookie без этого флага может попасть в первый незашифрованный запрос. Для сессионных cookie задайте Secure, а HttpOnly используйте для ограничения доступа из JavaScript. Параметр SameSite регулирует cross-site-поведение и не заменяет шифрование.
Не помещайте токены в query string. URI попадает в access log, историю браузера, системы мониторинга и иногда в заголовок Referer. Передавайте секреты в заголовках HTTPS-запроса и настройте маскирование чувствительных полей в логах.
HTTPS endpoint и reverse proxy для upstream
В этом примере TLS завершается в Nginx, а приложение слушает локальный порт 8080. В proxy_pass не указан URI после порта, поэтому Nginx передает исходный путь upstream без дополнительного удаления префикса.
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
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 https;
}
}
Hostсообщает приложению исходное имя API.X-Real-IPпередает адрес клиента, видимый Nginx.X-Forwarded-Forсохраняет цепочку адресов прокси.X-Forwarded-Proto httpsпозволяет приложению строить HTTPS-ссылки и не запускать повторный redirect.
Приложение должно доверять forwarded-заголовкам только от известного reverse proxy. Редирект Nginx не заменяет настройку CORS, авторизации, ограничений запросов и проверки входных данных.
Домены, поддомены и порядок выбора server block
Nginx выбирает виртуальный хост по адресу и порту, затем сопоставляет имя запроса с server_name. Для HTTPS сертификат выбирается еще на этапе TLS по SNI, поэтому DNS, список имен и сертификат должны описывать одну и ту же схему.
Как перечислить домены в server_name
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 80;
server_name api.example.com;
return 308 https://$host$request_uri;
}
Основной домен и www можно объединить, если у них одинаковая политика редиректа. API-поддомен лучше вынести в собственный блок, когда для него нужен код 308, отдельный сертификат, другой upstream или отдельное логирование.
Wildcard *.example.com покрывает поддомены первого уровня, но не включает сам example.com. Проверьте DNS для каждого имени, SAN или wildcard в сертификате и наличие нужного блока на 80 порту. Широкий wildcard может случайно открыть redirect policy для нового поддомена, поэтому список имен должен быть осознанным.
Для нескольких доменов и поддоменов с общим правилом пригодится схема централизованного редиректа в Nginx. В ней разобраны wildcard, отдельные виртуальные хосты и проверка DNS.
Зачем нужен отдельный default_server
default_server принимает запросы, которые не совпали с конкретным server_name. Это защита от случайной выдачи первого сайта для неизвестного Host.
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 444;
}
Код 444 не входит в стандарт HTTP. Nginx закрывает соединение без обычного ответа. Если клиентам нужен диагностический ответ, используйте отдельную политику, например 400, но не перенаправляйте неизвестные домены на рабочий сайт без явной причины.
На одном адресе и порту может быть только один default server. Перед добавлением блока проверьте существующие файлы, иначе Nginx не примет конфигурацию или начнет обслуживать неизвестные запросы неожиданным виртуальным хостом.
Проверка активной конфигурации
Файл в sites-available не влияет на работу Nginx, пока его содержимое не подключено. В Debian-подобных системах проверьте симлинк в sites-enabled; в других дистрибутивах изучите директивы include в основном файле.
sudo nginx -T | less
Команда nginx -T выводит объединенную конфигурацию, которую процесс действительно загрузит. Найдите все listen 80, server_name и default_server, проверьте дубли блоков и порядок подключения файлов. После исправления нужен reload, иначе работающий процесс продолжит использовать старую схему.
Исключения для ACME, health check и служебных запросов
Общий return 301 на уровне server перехватывает все запросы этого блока. Если нужен ACME HTTP-01 или health check с ответом 200, используйте отдельные точные location, а редирект поместите в location /.
ACME HTTP-01 и Let's Encrypt
server {
listen 80;
server_name example.com www.example.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
try_files $uri =404;
}
location / {
return 301 https://$host$request_uri;
}
}
При webroot-сценарии certbot должен создавать challenge-файлы внутри /var/www/certbot/.well-known/acme-challenge/. Проверьте тестовый файл по HTTP с внешнего адреса и убедитесь, что его не закрывает Basic authentication, ACL или firewall.
Если используется standalone-режим certbot, клиент временно занимает порт 80. В таком случае настройка challenge отличается, а Nginx может потребоваться остановить на время проверки. Выберите один способ получения сертификата и проверьте его командой или журналом конкретного клиента.
Не оставляйте серверный return рядом с этим исключением: он сработает раньше и не даст Nginx отдать challenge-файл через location.
Health check и endpoint мониторинга
server {
listen 80;
server_name example.com;
location = /healthz {
default_type text/plain;
return 200 "ok";
}
location / {
return 301 https://$host$request_uri;
}
}
Такой endpoint подходит для проверки доступности самого Nginx. Он не должен раскрывать состояние базы данных, версии ПО, секреты или внутренние адреса.
Сначала определите ожидание конкретного load balancer, CDN или системы мониторинга: одни компоненты следуют за 301, другие считают любой 3xx ошибкой и требуют 200 на HTTP. Если проверка должна подтверждать именно TLS, направьте ее сразу на HTTPS.
HTTP для legacy-интеграций
Не оставляйте общий HTTP-доступ ради одного старого клиента. Зафиксируйте его IP, путь и методы, обновите endpoint и поставьте срок удаления исключения.
Временный legacy-доступ нужно ограничить отдельным точным location и сетевыми правилами. Авторизацию, платежные данные, персональные данные и другие секреты через HTTP не обслуживайте.
Поисковым ботам не требуется отдельный обход по User-Agent. Для сайта отдавайте 301, обслуживайте финальную страницу по HTTPS и обновите canonical, sitemap и абсолютные ссылки. Если robots.txt доступен только по HTTP, разрешите редирект и отдельно проверьте, что бот получает корректный файл по HTTPS. Исключение с ответом 200 добавляйте только при подтвержденном требовании конкретного технического клиента.
Типичные ошибки редиректа в Nginx
Редирект срабатывает только для одного домена
Чаще всего причина находится за пределами строки return: у www нет DNS-записи, AAAA ведет на другой сервер, поддомен не перечислен в server_name или исправленный файл не подключен.
curl -I http://example.com/
curl -I http://www.example.com/
curl -4 -I http://example.com/
curl -6 -I http://example.com/
Сравните Location и сервер, который отвечает на каждый запрос. Проверяйте реальный Host, A-запись и AAAA-запись. IPv6 нужно тестировать отдельно: запрос по AAAA может попадать в другую инфраструктуру с собственной конфигурацией Nginx.
Бесконечный redirect loop
Цикл возникает, когда CDN или load balancer завершает TLS, а к Nginx подключается по HTTP. Публичный запрос уже пришел по HTTPS, но origin видит обычный HTTP и снова отправляет клиента на HTTPS.
curl -IL --max-redirs 5 https://example.com/
Проверьте всю цепочку, TLS termination и настройку протокола между CDN и origin. Если CDN должен подключаться к Nginx по HTTPS, включите HTTPS на origin. Если Nginx получает HTTP после TLS на внешнем прокси, используйте доверенный X-Forwarded-Proto и настройте приложение так, чтобы оно считало запрос HTTPS.
Не принимайте X-Forwarded-Proto от любого внешнего клиента. Прокси должен удалять или заменять этот заголовок, а Nginx и приложение должны доверять ему только от известных адресов.
Потеря пути или query string
Сравните исходный URL с заголовком Location. Для простого перехода на HTTPS достаточно return 301 https://$host$request_uri;. Ручной rewrite с лишним знаком вопроса, подмена URI в proxy_pass или сборка параметров по частям может удалить query string или изменить путь.
Проверяйте закодированные символы, callback-параметры и API-маршруты отдельными запросами:
curl -I 'http://example.com/api/v1/items?id=10&sort=desc'
Фрагмент URL после символа # браузер не отправляет серверу, поэтому Nginx не может перенести его через заголовок Location. Это нормальное поведение клиента.
301 закэшировался во время теста
Браузер, CDN и промежуточный прокси могут запомнить 301. После исправления конфигурации браузер продолжит автоматически использовать старый адрес, хотя Nginx уже возвращает другой ответ.
Для первичной проверки используйте curl, временно выбирайте 302 или 307 и тестируйте отдельный hostname. Проверяйте заголовки CDN и cache-control. HSTS хранится отдельно и может заставлять браузер обращаться к HTTPS еще до запроса к порту 80, поэтому включайте эту политику после проверки всех доменов и поддоменов.
API-клиент получает 4xx или повторяет запрос некорректно
Проверьте поддержку 307 или 308, сохранение тела, заголовков Authorization и Cookie. Отдельно протестируйте GET, POST, PUT, PATCH и DELETE. Для webhook проверьте фактический повторный запрос у поставщика, а не только ответ команды curl.
curl -v -X POST \
-H 'Content-Type: application/json' \
--data-binary @payload.json \
http://api.example.com/v1/ping
Логи Nginx покажут первый HTTP-запрос, но не всегда объяснят, что сделал клиент после 308. Логи SDK, API gateway и приложения нужны для проверки всей цепочки. Если клиент не повторяет запрос, замените endpoint в его настройках или временно верните документированный ответ без приема бизнес-операции.
Проверка и ввод в эксплуатацию
Перед изменением сохраните копию конфигурации, проверьте синтаксис, изучите активные server block и только после этого выполните reload. Перезапуск процесса для обычного изменения redirect server не требуется.
Команды перед reload
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak-$(date +%F)
sudo nginx -t
sudo nginx -T > /tmp/nginx-active.conf
sudo systemctl reload nginx
nginx -t проверяет синтаксис и доступность подключенных файлов. nginx -T позволяет проверить фактически собранную конфигурацию. systemctl reload nginx перечитывает настройки без остановки уже обслуживаемых соединений.
После reload следите за логами:
sudo tail -f /var/log/nginx/access.log /var/log/nginx/error.log
Пути к логам могут отличаться из-за настроек дистрибутива и виртуального хоста. В access log ищите статус 301 или 308, нужный Host и URI. В error log проверяйте ошибки bind, сертификата, include и доступа к challenge-файлу.
Матрица проверок для сайта и API
| Сценарий | Что проверить | Ожидаемый результат |
|---|---|---|
| HTTP основной домен | GET с путем и query string | 301, Location на HTTPS с тем же URI |
| HTTP www | Каноническое имя и сертификат | Один переход на выбранный HTTPS-домен |
| HTTP API GET | Код и сохранение маршрута | 308 или 301 по принятой политике |
| HTTP API POST | Метод, тело и Authorization | 308, повторный POST по HTTPS или документированный отказ |
| HTTPS сайт | Сертификат, контент, canonical | 200 либо ожидаемый ответ приложения |
| HTTPS API | Upstream и forwarded-заголовки | Ответ приложения без повторного цикла |
| ACME path | Challenge-файл извне | 200 и содержимое файла |
| Health check | Требования балансировщика | 200 или допустимый 3xx |
| Неизвестный Host | default server | 444, 400 или другая заданная политика |
Что проверить после включения HTTPS
- Уберите смешанный контент: изображения, скрипты, стили и API-запросы не должны обращаться к HTTP.
- Замените абсолютные HTTP-ссылки, callback-адреса, webhook URL и адреса в API-документации.
- Проверьте Cookie с флагами
Secure,HttpOnlyи подходящимSameSite. - Обновите canonical, sitemap и ссылки в
robots.txt, чтобы они указывали на HTTPS. - Проверьте внешние интеграции, которые могут не следовать за 307 или 308.
- Добавляйте HSTS только после проверки всех нужных доменов и поддоменов. Параметр
includeSubDomainsприменяйте, когда каждый поддомен действительно доступен по HTTPS.
Краткая памятка по редиректу HTTP на HTTPS в Nginx
- Создайте отдельный
serverна порту 80 и оставьте в нем минимальное правилоreturn. - Перечислите все реальные имена в
server_name, включаяwww, API и нужные поддомены. - Для сайта используйте 301 после проверки, для API выберите 307 или 308, если требуется сохранить метод и тело.
- Сохраняйте исходный адрес через
$request_uri; для канонического домена задавайте host явно. - Не смешивайте редирект с
proxy_pass, TLS и логикой приложения. - Проверьте ACME
/.well-known/acme-challenge/, health check, мониторинг и требования legacy-интеграций. - Перед reload выполните
nginx -tиnginx -T, затем проверьте каждый домен командамиcurlи изучите access log.