Редирект с HTTP на HTTPS в Nginx: безопасная настройка для сайта и API | AdminWiki

Редирект с HTTP на HTTPS в Nginx: безопасная настройка для сайта и API

05 сентября 2026 15 мин. чтения
Содержание статьи

Как настроить редирект с 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-переход оставляют для совместимых сценариев.

Поведение заголовка 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 string301, Location на HTTPS с тем же URI
HTTP wwwКаноническое имя и сертификатОдин переход на выбранный HTTPS-домен
HTTP API GETКод и сохранение маршрута308 или 301 по принятой политике
HTTP API POSTМетод, тело и Authorization308, повторный POST по HTTPS или документированный отказ
HTTPS сайтСертификат, контент, canonical200 либо ожидаемый ответ приложения
HTTPS APIUpstream и forwarded-заголовкиОтвет приложения без повторного цикла
ACME pathChallenge-файл извне200 и содержимое файла
Health checkТребования балансировщика200 или допустимый 3xx
Неизвестный Hostdefault server444, 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.
Поделиться:
Сохранить гайд? В закладки браузера