Для сайта на PHP-FPM нужны два независимых уровня настройки. HTTP server block на порту 80 должен вернуть постоянный редирект на канонический HTTPS-адрес и завершить обработку запроса. HTTPS server block на порту 443 принимает TLS, обслуживает файлы и передает PHP-запросы в PHP-FPM через FastCGI.
Минимальная схема выглядит так: return 301 https://example.com$request_uri; в HTTP-блоке, location ~ [.]php$ в HTTPS-блоке и согласованные параметры HTTPS, REQUEST_SCHEME, SERVER_PORT, SCRIPT_FILENAME и REQUEST_URI. Если TLS завершается на reverse proxy или балансировщике, локальный Nginx может видеть соединение по HTTP. В этом случае внешний HTTPS нужно передавать через доверенный X-Forwarded-Proto.
Ниже приведена базовая конфигурация для PHP 8.3 и Nginx. Путь к сокету, каталог сайта, имена доменов и пути к сертификатам нужно заменить на значения конкретного сервера. Общие принципы подходят для PHP 8.x и современных версий Nginx. Полная схема развертывания LEMP разобрана в руководстве по Nginx, PHP-FPM и MySQL/MariaDB.
Быстрая схема: HTTP на HTTPS, HTTPS в PHP-FPM
Как проходит запрос от браузера до PHP
- Браузер отправляет запрос на
http://example.com/pathпо порту 80. - Nginx выбирает HTTP server block по адресу, порту и заголовку
Host. - HTTP-блок возвращает код
301или308с заголовкомLocation: https://example.com/path. До PHP-FPM этот запрос не доходит. - Браузер повторяет запрос по HTTPS. TLS завершается в Nginx на порту 443.
- Для обычного URI Nginx применяет
try_files. Если приложение использует front controller, запрос передается в/index.php. - Для PHP-файла срабатывает
location ~ [.]php$. Nginx проверяет существование файла и передает параметры FastCGI процессу PHP-FPM. - PHP получает сведения о схеме, порте, хосте и URI через массив
$_SERVER. Приложение использует их при построении абсолютных ссылок, проверке защищенного соединения и собственных редиректах.
Nginx принимает решение о сетевом редиректе. PHP-FPM не перенаправляет HTTP-запросы сам по себе и не видит TLS без переданных FastCGI-параметров. Поэтому рабочая настройка состоит из отдельного HTTP-блока, HTTPS-блока и согласованного набора переменных окружения.
Какой результат считать корректным
- Запрос по HTTP получает один ответ
301или308. - Заголовок
Locationсодержит выбранный канонический домен и схемуhttps. - Путь и query-параметры сохраняются без потерь.
- Запрос к каноническому HTTPS-адресу не получает повторный редирект.
- Альтернативный host, например
www.example.com, переходит на основной домен одним переходом. - PHP видит
$_SERVER['HTTPS'] = 'on',$_SERVER['REQUEST_SCHEME'] = 'https'и порт443. - PHP-FPM отвечает без ошибок
404,500и502.
Как настроить редирект с HTTP на HTTPS в Nginx для PHP-FPM
HTTP server block: постоянный редирект на канонический HTTPS
HTTP-блок должен содержать минимум директиву return. Она завершает обработку запроса сразу и не передает его в PHP. Для обычных переходов используйте 301:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
Переменная $request_uri содержит исходный URI вместе с query-параметрами. Запрос /catalog/item?id=42 перейдет на https://example.com/catalog/item?id=42. Nginx не добавляет второй вопросительный знак и не теряет строку запроса.
Код 301 подходит для GET и HEAD, когда клиенту достаточно изменить адрес. Код 308 сохраняет HTTP-метод и тело запроса:
return 308 https://example.com$request_uri;
Это важно для POST, PUT и других методов, которые нельзя незаметно превращать в GET. Постоянный редирект лучше включать после проверки через 302 в тестовой среде или после подтверждения всей цепочки. Браузеры и поисковые роботы кэшируют ответы 301 и 308, поэтому ошибка в адресе может сохраняться после исправления конфигурации.
Для канонизации домена задавайте адрес явно. Вариант return 301 https://$host$request_uri; сохраняет любой принятый host и может отправить посетителя на нежелательный домен. Такая схема особенно рискованна, если сервер принимает неизвестные заголовки Host в default server block.
Если нужен переход с www на основной домен, настройте отдельный HTTPS-блок для альтернативного имени:
server {
listen 443 ssl;
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;
}
Сертификат альтернативного имени должен содержать www.example.com. Иначе TLS-соединение завершится раньше редиректа, и браузер покажет ошибку сертификата. Практические параметры сертификатов и проверку TLS разобраны в руководстве по установке SSL-сертификата на Nginx и Apache.
HTTPS server block: TLS и маршрутизация PHP-запросов
Основной HTTPS-блок обслуживает канонический домен. В примере try_files сначала ищет статический файл, затем отправляет неизвестный URI в front controller. Обработка PHP ограничена существующими файлами, поэтому Nginx не передает в PHP произвольный путь.
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com;
root /var/www/example.com/public;
index index.php index.html;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ [.]php$ {
try_files $uri =404;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS on;
fastcgi_param SERVER_PORT 443;
fastcgi_param HTTP_HOST $host;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
}
В HTTPS server block переменная $scheme равна https, а переменная $server_port обычно равна 443. В примере значения передаются явно, чтобы поведение не зависело от содержимого системного include-файла. В конфигурации с нестандартным портом передавайте фактический внешний порт.
Директива try_files $uri =404; внутри PHP location блокирует попытки передать в PHP несуществующий скрипт. Для CMS с front controller это не мешает работе: внутренний URI сначала меняется на существующий /index.php, после чего запрос проходит проверку.
Убедитесь, что каталог root совпадает с фактическим public-каталогом приложения. Если сайт хранится в /var/www/example.com, а документ-корень указан как /var/www/example.com/public, значение SCRIPT_FILENAME должно указывать на реальный файл внутри public-каталога.
Подключение PHP-FPM через Unix-сокет или TCP
Значение fastcgi_pass должно совпадать с активным способом прослушивания PHP-FPM. Для локального сервера чаще используют Unix-сокет:
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
Путь содержит версию PHP только как пример. На сервере может использоваться php8.2-fpm.sock, другой каталог или собственное имя pool. Проверить доступные сокеты можно командами:
ls -l /run/php/
systemctl status php8.3-fpm
ss -lx | grep php
При TCP-подключении Nginx обращается к адресу и порту, который указан в pool PHP-FPM:
fastcgi_pass 127.0.0.1:9000;
Ошибка 502 Bad Gateway появляется, если процесс PHP-FPM остановлен, Nginx использует устаревший путь к сокету, порт закрыт или права на Unix-сокет не позволяют worker-процессу Nginx подключиться к pool. Для проверки прав сравните пользователя Nginx, владельца сокета и группы доступа. В Ubuntu и Debian worker часто работает от имени www-data, но это значение нужно проверять в конфигурации конкретной системы.
Для тестового стенда с отдельным VDS можно использовать облачную инфраструктуру Timeweb Cloud: на чистом сервере проще воспроизвести конфигурацию, проверить версии PHP и отладить переход на HTTPS до изменения production-узла.
Какие fastcgi_param передать PHP для корректной схемы HTTPS
Параметры HTTPS, REQUEST_SCHEME и SERVER_PORT
PHP получает сведения о веб-запросе через FastCGI. Само подключение Nginx к PHP-FPM по Unix-сокету или TCP не сообщает PHP, что клиент использовал HTTPS. Это состояние передается отдельными параметрами.
| Параметр | Пример значения | Назначение |
|---|---|---|
HTTPS | on | Позволяет PHP и CMS определить защищенный запрос через $_SERVER['HTTPS']. |
REQUEST_SCHEME | https | Передает схему запроса приложениям, которые читают $_SERVER['REQUEST_SCHEME']. |
SERVER_PORT | 443 | Передает внешний порт для генерации абсолютных URL и проверок конфигурации. |
HTTP_HOST | example.com | Передает host, по которому приложение сопоставляет домен и формирует ссылки. |
Для Nginx, который сам завершает TLS, базовая связка выглядит так:
fastcgi_param HTTPS on;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param SERVER_PORT $server_port;
В отдельном HTTPS-блоке $scheme равен https, а $server_port равен 443. В PHP результат можно проверить временным диагностическим скриптом:
<?php
header('Content-Type: text/plain; charset=utf-8');
var_dump([
'HTTPS' => $_SERVER['HTTPS'] ?? null,
'REQUEST_SCHEME' => $_SERVER['REQUEST_SCHEME'] ?? null,
'SERVER_PORT' => $_SERVER['SERVER_PORT'] ?? null,
'HTTP_HOST' => $_SERVER['HTTP_HOST'] ?? null,
'REQUEST_URI' => $_SERVER['REQUEST_URI'] ?? null,
]);
После проверки удалите диагностический файл или закройте к нему доступ. Публичный вывод переменных окружения раскрывает сведения о сервере и маршрутизации.
Если PHP получает пустой HTTPS, приложение может считать запрос незашифрованным и отправлять новый редирект на тот же HTTPS-адрес. Если REQUEST_SCHEME остается равным http, фреймворк способен генерировать ссылки с неправильной схемой даже при корректном ответе Nginx.
SCRIPT_FILENAME, REQUEST_URI и HTTP_HOST
Параметр SCRIPT_FILENAME определяет файл, который запускает PHP-FPM:
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
При URI /index.php и корне /var/www/example.com/public PHP-FPM получает путь /var/www/example.com/public/index.php. Ошибка в этой строке дает 404, ошибку открытия файла или запуск не того скрипта.
REQUEST_URI сохраняет полный исходный путь вместе с query-параметрами. QUERY_STRING передает строку параметров отдельно. Для приложений, которые используют оба значения, передайте их явно:
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
HTTP_HOST нужен CMS и фреймворкам для выбора виртуального домена. В основном HTTPS-блоке безопаснее передавать нормализованный $host, а канонический домен хранить в настройках приложения:
fastcgi_param HTTP_HOST $host;
fastcgi_param SERVER_NAME $server_name;
Не стройте канонический URL на полном доверии входному заголовку Host. Разрешите конкретные server name, задайте основной домен в CMS или переменную базового URL фреймворка. Для запроса к example.com приложение должно создавать https://example.com/... независимо от случайного или неизвестного host.
Если fastcgi.conf в вашей системе уже содержит SCRIPT_FILENAME, не дублируйте его без проверки. На части дистрибутивов fastcgi_params содержит базовые CGI-параметры, а fastcgi.conf дополнительно задает путь к скрипту. Просмотрите фактическое содержимое include-файлов командой nginx -T и оставьте один источник для каждого параметра.
Почему один fastcgi_param HTTPS не решает все проблемы
Приложение может проверять несколько значений одновременно:
$_SERVER['HTTPS']показывает состояние HTTPS.$_SERVER['REQUEST_SCHEME']определяет схему URL.$_SERVER['SERVER_PORT']сообщает порт.$_SERVER['HTTP_HOST']содержит домен запроса.$_SERVER['HTTP_X_FORWARDED_PROTO'],HTTP_X_FORWARDED_HOSTиHTTP_X_FORWARDED_PORTописывают внешний запрос за proxy.- Переменная базового URL в CMS или фреймворке может полностью переопределять значения окружения.
Поэтому исправление одной строки fastcgi_param HTTPS on; устраняет лишь одну причину. Если приложение получает HTTPS=on, но REQUEST_SCHEME=http, его middleware может продолжить редирект. Если схема правильная, а host указывает на внутреннее имя балансировщика, абсолютные ссылки приведут пользователя к недоступному адресу.
Для прямого TLS в Nginx используйте единый набор:
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS on;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param HTTP_HOST $host;
Перед вставкой этого блока проверьте include-файл. Дублирование REQUEST_METHOD, QUERY_STRING и других параметров обычно не ломает простой запрос, но усложняет диагностику: итоговое значение зависит от порядка директив и обработки повторяющихся полей FastCGI.
HTTPS за reverse proxy или балансировщиком: как избежать цикла редиректов
Как возникает бесконечный HTTPS-редирект
Типичный цикл появляется при TLS termination на внешнем proxy:
- Браузер открывает
https://example.com. - Балансировщик принимает TLS и отправляет обычный HTTP-запрос на backend Nginx.
- Backend Nginx видит локальный
$scheme = http. - Правило HTTP на backend возвращает
Location: https://example.com. - Браузер снова подключается к балансировщику по HTTPS и повторяет тот же цикл.
Смена 301 на 302 или 307 не исправляет причину. Код ответа влияет на кэширование и метод запроса, но не меняет значение локального $scheme. Нужно передать backend внешний протокол и настроить приложение на доверие к этому сигналу.
В access log цикл обычно выглядит как серия одинаковых запросов с ответами 301 или 302. Внешний клиент видит повторяющийся URL, а backend каждый раз считает соединение HTTP.
Безопасная обработка X-Forwarded-Proto
На доверенном proxy заголовок нужно перезаписывать, а не передавать значение клиента:
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://backend;
}
Если внешний proxy принимает HTTPS, его $scheme равен https. Backend получает это значение в X-Forwarded-Proto, хотя транспорт между двумя узлами идет по HTTP. Внешний proxy должен находиться в закрытой сети или иметь сетевые ограничения, чтобы клиент не обращался к backend напрямую.
В backend Nginx можно нормализовать доверенный заголовок через map. Директивы map размещают в контексте http, не внутри server:
map $http_x_forwarded_proto $external_scheme {
default http;
~*^https$ https;
}
map $external_scheme $external_https {
default off;
https on;
}
map $http_x_forwarded_port $external_port {
default 80;
80 80;
443 443;
}
Значение default http означает, что неизвестная схема не получает статус HTTPS. Регулярное правило принимает только точное значение https, а строку вроде https,http оставляет в состоянии http. Такая нормализация не создает доверие сама по себе. Доступ к backend нужно ограничить адресами proxy на уровне firewall или сетевой политики.
В PHP location backend передавайте внешние значения:
fastcgi_param REQUEST_SCHEME $external_scheme;
fastcgi_param HTTPS $external_https;
fastcgi_param SERVER_PORT $external_port;
fastcgi_param HTTP_HOST $host;
fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto;
fastcgi_param HTTP_X_FORWARDED_HOST $http_x_forwarded_host;
fastcgi_param HTTP_X_FORWARDED_PORT $http_x_forwarded_port;
Последние три параметра передавайте приложению только при доверенной цепочке proxy. Если backend доступен из интернета, любой клиент сможет прислать собственный X-Forwarded-Proto и повлиять на генерацию URL или логику редиректа. Настройка trusted proxy в CMS или фреймворке должна соответствовать реальным адресам балансировщиков.
Где размещать редирект при TLS termination
Есть две устойчивые схемы.
| Схема | Где выполняется редирект | Что получает backend |
|---|---|---|
| Редирект на edge | Балансировщик или внешний Nginx перенаправляет HTTP на HTTPS. | До backend доходят только HTTPS-запросы с нормализованными proxy-заголовками. |
| Редирект на backend | Backend принимает запросы обоих типов и проверяет доверенный X-Forwarded-Proto. | PHP и Nginx получают внешнюю схему, host и порт. |
Редирект на edge обычно проще: внешний уровень видит реальную схему клиента и не создает лишний переход между proxy и backend. В этом случае backend не должен безусловно проверять свой локальный $scheme и отправлять HTTPS-запросы на HTTPS повторно.
Если редирект должен выполнять backend, условие нужно строить на нормализованной переменной:
if ($external_scheme = http) {
return 301 https://example.com$request_uri;
}
В Nginx конструкция if с единственной директивой return безопасна для такого сценария. Не смешивайте это правило с параллельным редиректом на балансировщике, иначе диагностика цепочки станет сложнее.
X-Forwarded-Host и X-Forwarded-Port нужны, когда приложение строит абсолютные URL, ссылки на callback или адреса API. Их должен задавать внешний proxy. Если публичный адрес всегда один, надежнее закрепить домен и порт в настройках приложения и использовать proxy-заголовки только после проверки доверенного источника. Практические схемы SSL termination, маршрутизации и поиска ошибок 502 разобраны в руководстве по reverse proxy на Nginx.
Как быстро проверить редирект в Nginx через curl
Минимальная команда curl -I для проверки заголовков
Начните с HTTP-ответа без загрузки тела:
curl -I http://example.com/path?x=1
Ожидаемый ответ содержит 301 или 308 и примерно такой заголовок:
HTTP/1.1 301 Moved Permanently
Location: https://example.com/path?x=1
Проверьте три значения: код ответа, схему и host в Location, сохранение пути и query-параметра x=1. Для просмотра заголовков без тела подойдет команда:
curl -sS -D - -o /dev/null http://example.com/path?x=1
curl -I отправляет HEAD. Некоторые приложения обрабатывают HEAD иначе, чем GET, поэтому после проверки заголовков выполните обычный запрос.
Как проверить цепочку через curl -L и обнаружить петлю
Команда с ограничением числа переходов показывает промежуточные ответы:
curl -IL --max-redirs 10 --silent --show-error http://example.com/path?x=1
При исправной схеме обычно видны два ответа: первый HTTP с 301 или 308, второй HTTPS с кодом приложения, например 200. Повтор одного и того же Location указывает на цикл. Повторение переходов между двумя host часто означает конфликт правил www и non-www.
Для итогового адреса и количества переходов используйте форматированный вывод:
curl -sS -o /dev/null -w 'final=%{url_effective} code=%{http_code} redirects=%{num_redirects}' -L http://example.com/path?x=1
Ожидаемый результат содержит канонический HTTPS-адрес, код приложения и одно перенаправление. Ошибка о превышении числа переходов подтверждает петлю, но для поиска причины нужно посмотреть каждый ответ без подавления заголовков.
Как проверить разные сценарии запроса
- HTTP основного домена:
curl -I http://example.com/. - HTTPS основного домена:
curl -I https://example.com/. - HTTP альтернативного домена:
curl -I http://www.example.com/. - Путь без завершающего слеша:
curl -I http://example.com/docs. - Путь с завершающим слешем:
curl -I http://example.com/docs/. - Query-параметры:
curl -I 'http://example.com/search?q=nginx&page=2'. - Обычный GET:
curl -sS -D - -o /dev/null http://example.com/. - POST для проверки сохранения метода при 308:
curl -sS -D - -o /dev/null -X POST --data 'a=1' http://example.com/form.
До изменения DNS можно направить домен на конкретный IP через --resolve:
curl --resolve example.com:443:203.0.113.10 -I https://example.com/path
curl --resolve example.com:80:203.0.113.10 -I http://example.com/path
Эта проверка сохраняет исходный host и имя для TLS, но подключается к указанному адресу. Она помогает тестировать новый сервер до переключения DNS.
Каким должен быть корректный ответ
Настройка прошла проверку, если выполняются все условия:
- HTTP дает один переход на HTTPS.
- Альтернативный host дает один переход на канонический домен.
Locationне содержит внутреннего имени, лишнего порта или двойного слеша.- Путь и query-параметры совпадают с исходным запросом.
- HTTPS отвечает ожидаемым кодом приложения, чаще
200,204или кодом авторизации. - В HTTPS-ответе нет второго
Location, если приложение не использует отдельную штатную переадресацию. - PHP-запрос доходит до рабочего pool PHP-FPM.
- В Nginx error log и PHP-FPM log не появились новые ошибки.
Проверка только кода 301 недостаточна. Неправильный домен, потерянные параметры и второй редирект обнаруживаются при просмотре Location и полной цепочки.
Типовые ошибки конфигурации Nginx и диагностика по логам
Ищите неисправность по слоям: выбранный server block, правило маршрутизации, FastCGI-параметры, доступность PHP-FPM, логика приложения. Такая последовательность сокращает число случайных изменений.
| Симптом | Вероятная причина | Проверка | Исправление |
|---|---|---|---|
HTTP отвечает 200 | Выбран другой server block, правило return отсутствует или запрос попал в default server. | nginx -T, заголовок Host, поле server_name в access log. | Активируйте нужный конфиг, уберите конфликтующие server blocks и задайте корректный listen. |
| Редирект ведет не на тот host | Используется $host без канонизации, неверно задан server_name или proxy передает внутренний host. | curl -I, итоговый конфиг, X-Forwarded-Host. | Укажите канонический домен явно и настройте доверенную передачу host от proxy. |
| Пропадают параметры запроса | В rewrite или return указан неправильный URI, приложение пересобирает URL без query string. | Проверка URI с ?x=1, заголовок Location. | Используйте $request_uri для полного исходного URI. |
| Бесконечный HTTPS-цикл | Backend видит HTTP после TLS termination и игнорирует доверенный X-Forwarded-Proto. | curl -IL, access log proxy и backend, значения scheme, HTTPS, REQUEST_SCHEME. | Оставьте редирект на edge или нормализуйте внешний протокол на backend. |
HTTPS отвечает 404 | Неверный root, ошибка try_files или неправильный SCRIPT_FILENAME. | Проверка файла на диске, nginx -T, error log. | Исправьте public-каталог, front controller и путь к PHP-файлу. |
HTTPS отвечает 500 | Ошибка PHP-кода, несовместимость версии или исключение приложения. | PHP-FPM log, журнал приложения, прямой тест существующего PHP-файла. | Исправьте код или зависимости, проверьте версию PHP и настройки pool. |
HTTPS отвечает 502 | PHP-FPM остановлен, сокет отсутствует, порт неверен или нет прав на подключение. | systemctl status, ls -l /run/php/, Nginx error log. | Запустите нужный pool и синхронизируйте fastcgi_pass с его listen-адресом. |
Location указывает не на тот адрес
Проверьте порядок и состав виртуальных хостов. Nginx выбирает server block по адресу и порту, затем сопоставляет server_name. Если домен не совпал, запрос может попасть в default server и получить чужой сертификат, страницу или редирект.
В HTTP-блоке проверьте:
- порт
80действительно прослушивается; - основной и альтернативный host перечислены в
server_name; - в
returnуказан канонический домен; - используется
$request_uri, если нужно сохранить путь и параметры; - нет второго правила, которое меняет host или добавляет путь.
Правила rewrite с регулярными выражениями часто создают лишние переходы и теряют query string. Для полного перехода домена используйте короткий return. Разбор таких правил приведен в инструкции по редиректам в Nginx без rewrite.
Редирект зациклился
Сначала определите, кто возвращает ответ. Выполните:
curl -IL --max-redirs 10 --silent --show-error https://example.com/
Затем сопоставьте каждый ответ со временем и URI в access log. Если ответ формирует edge, проверяйте правила балансировщика. Если ответ формирует backend Nginx, сравнивайте локальный $scheme с заголовком X-Forwarded-Proto. Если Nginx возвращает код 200, а затем редирект появляется в цепочке, источник находится в PHP-приложении.
В PHP проверьте значения:
$_SERVER['HTTPS']
$_SERVER['REQUEST_SCHEME']
$_SERVER['SERVER_PORT']
$_SERVER['HTTP_HOST']
$_SERVER['HTTP_X_FORWARDED_PROTO']
$_SERVER['HTTP_X_FORWARDED_HOST']
CMS и фреймворки часто требуют отдельной настройки trusted proxies. Без нее приложение может игнорировать корректный proxy-заголовок или, наоборот, принимать подмененное значение от клиента.
Проверьте, не настроен ли один и тот же переход сразу на CDN, балансировщике, Nginx и в CMS. Для каждой пары исходная схема и целевая схема должны быть определены один раз.
Вместо редиректа приходит 200, 404, 500 или 502
200 на HTTP. Запрос обслуживает неправильный server block или HTTP-редирект отсутствует. Проверьте активный конфигурационный файл и тестируйте запрос с явным Host, если DNS указывает на несколько узлов.
404 на HTTPS. Nginx не нашел статический файл или PHP-скрипт. Сравните root с реальным каталогом, проверьте существование index.php и значение SCRIPT_FILENAME. В front controller запрос должен доходить до существующего файла.
500 на HTTPS. Nginx уже передал запрос в PHP-FPM, поэтому ищите ошибку в PHP-коде, зависимостях, правах приложения или конфигурации pool. Код 500 не исправляется сменой fastcgi_pass, если соединение с PHP-FPM уже установлено.
502 на HTTPS. Nginx не получил корректный ответ от PHP-FPM. Сверьте версию сокета, состояние службы и права доступа. Для TCP проверьте, слушает ли процесс нужный адрес:
ss -ltnp | grep 9000
systemctl status php8.3-fpm
journalctl -u php8.3-fpm -n 100 --no-pager
Логи Nginx как подтверждение причины
До изменения конфигурации проверьте синтаксис и итоговый набор директив:
nginx -t
nginx -T
nginx -t проверяет синтаксис и доступность ссылок на конфигурацию. nginx -T выводит собранную конфигурацию вместе с include-файлами. Смотрите именно этот результат, потому что отдельный файл в каталоге sites-available не подтверждает его подключение.
Для временной диагностики можно добавить отдельный формат access log в контекст http:
log_format redirect_debug '$remote_addr request=$request status=$status host=$host scheme=$scheme port=$server_port uri=$request_uri location=$sent_http_location upstream=$upstream_status';
Подключите его к нужному server block или location, затем выполните reload:
nginx -t && systemctl reload nginx
В логах сопоставляйте status, host, scheme, port, uri и location. Не записывайте полные заголовки и тела запросов без необходимости: query string иногда содержит токены, идентификаторы и персональные данные.
В error log ищите ошибки выбора upstream, доступа к сокету, открытия PHP-файла и обработки правил. В PHP-FPM log ищите фатальные ошибки, аварийное завершение worker и сообщения о правах. Такая связка логов показывает, на каком переходе запрос перестал соответствовать ожидаемой схеме.
Итоговый чек-лист проверки редиректа и PHP-FPM
Проверка конфигурации и применения изменений
- Проверьте синтаксис командой
nginx -t. - Просмотрите итоговую конфигурацию через
nginx -T. - Убедитесь, что активен нужный файл виртуального хоста.
- Проверьте
listen 80,listen 443 sslи значенияserver_name. - Проверьте сертификат, приватный ключ и наличие нужных доменных имен в сертификате.
- Сверьте
rootс каталогом сайта. - Проверьте существование PHP-FPM-сокета или доступность TCP-порта.
- Сверьте пользователя Nginx с правами на Unix-сокет и файлы приложения.
- После успешной проверки выполните
systemctl reload nginx. - Проверьте статус службы PHP-FPM и отсутствие новых ошибок в журналах.
Проверка канонического URL и прокси-заголовков
- HTTP основного домена переходит на канонический HTTPS.
- HTTP альтернативного домена переходит на HTTPS основного домена.
- HTTPS основного домена не получает лишний редирект.
- Путь, завершающий слеш и query-параметры сохраняются.
Locationне использует внутренний host или адрес backend.- За reverse proxy внешний источник перезаписывает
X-Forwarded-Proto,X-Forwarded-HostиX-Forwarded-Port. - Backend принимает эти заголовки только от разрешенных proxy.
- Значения
REQUEST_SCHEME,HTTPSиSERVER_PORTсовпадают с внешней схемой. - Настройки trusted proxy в PHP-приложении соответствуют реальной топологии.
Что считать проверкой, завершенной успешно
Настройка готова, когда HTTP-запрос дает один переход на канонический HTTPS-адрес, URI и параметры остаются прежними, HTTPS возвращает ожидаемый код приложения, а PHP-FPM обрабатывает запрос без ошибок. За proxy backend должен видеть внешнюю схему через доверенный и нормализованный контекст.
Постоянный код 301 или 308 включайте после проверки тестовыми запросами. HSTS добавляйте после полной проверки HTTPS и всех нужных поддоменов: браузер начнет автоматически заменять HTTP на HTTPS и усложнит откат ошибочной настройки.
Для повторной проверки после переноса сайта или смены балансировщика используйте один набор тестов: curl -I для заголовков, curl -IL для цепочки, curl -L -w для конечного URL, nginx -t для синтаксиса и журналы Nginx и PHP-FPM для подтверждения причины.