Ви розгортаєте чистий сервер Debian 13, ставите Nginx, додаєте «простий» редирект HTTP→HTTPS, і раптом браузер
викидає вам у відповідь «Забагато перенаправлень». Ви чистите куки. Спробуєте в інкогніто. Звинувачуєте браузер.
Потім звинувачуєте Nginx. Винна тільки ваша конфігурація.
Петлі перенаправлень рідко бувають «помилкою Nginx». Вони майже завжди — це розбіжність у тому, якою має бути канонічна URL-адреса
(хост, схема або обидва), посилена зворотним проксі або CDN, який іноді «бреше» — інколи ввічливо — про те, чи був запит HTTPS.
Виправимо це правильно, з доказами, а не копіпастом чужих фрагментів.
Що насправді означає «Забагато перенаправлень»
Ваш браузер (або клієнт) запросив URL. Сервер відповів редиректом (3xx) на інший URL. Клієнт перейшов.
Той сервер відповів ще одним редиректом. Повторюйте, поки клієнт не досягне максимуму переходів і не зупиниться.
В Nginx петлі зазвичай бувають:
- Петлі за схемою: HTTP→HTTPS→HTTP→… тому що щось попереду/позаду не погоджується щодо схеми.
- Петлі за хостом: example.com→www.example.com→example.com→… тому що кілька шарів «канонізують» по-різному.
- Плутанина порт/схеми: редирект на https://host:80 або http://host:443 випадково, часто через змінні.
- Редиректи на рівні додатку: додаток редиректить на HTTPS, Nginx редиректить на HTTP (або інший хост) — вони конфліктують.
- Прилипання через cookie/HSTS: ваші тести відрізняються, бо браузер закешував політику або cookie.
Виправлення — не «додати більше умов», поки перестане. Потрібно визначити саме одну авторитетну точку для канонічного хоста й схеми,
потім змусити інші шари слідувати їй. У стеку має бути одна думка, не комітет.
Жарт №1: Петлі редиректів — як наради про наради — усі «передають відповідальність», і ніхто нікуди не приходить.
Факти про редиректи і історія, що важлива в продакшені
Кілька коротких контекстних пунктів, що здаються академічними, поки не вдарять вас о 02:00 у вівторок:
- HTTP-редиректи старші за «сучасний веб». Коди 301/302 були в ранніх специфікаціях HTTP; кеші та клієнти досі трактують їх по-різному.
- 301 може кешуватися агресивно. Браузери й проміжні вузли можуть кешувати 301 довше, ніж ви очікуєте, тож «я поправив» може не відобразитися на вашому ноутбуці.
- 302 історично означав «тимчасово», але клієнти стали креативні. Ця плутанина — одна з причин існування 307 і 308: вони краще зберігають семантику методу.
- HSTS змінює гру. Як тільки браузер вивчив «завжди використовувати HTTPS для цього хоста», він може взагалі не пробувати HTTP, що плутає налагодження.
- Зворотні проксі зробили питання «це HTTPS?» неоднозначним. Якщо TLS завершується нагорі, ваш Nginx бачить простий HTTP і доводиться покладатися на заголовки або PROXY протокол.
- Заголовок Host — і потужний, і небезпечний. Він дозволяє virtual hosting, але довіряти йому бездумно може призвести до open redirect або канонічного хаосу.
- CDN ввели режими «Flexible SSL». В таких режимах CDN говорить HTTPS з браузером, але HTTP з origin — відмінна зона для HTTP→HTTPS петель.
- Вибір сервера за замовчуванням — мовчазна пастка. Nginx вибере default_server, якщо нічого не співпадає; редирект у ньому може перехопити чужі хости.
- Канонізація — це не тільки SEO. Вона впливає на cookie (область дії домену), CORS, OAuth redirect_uri і стікінесс сесій.
Один вислів, бо він підходить для операцій: Надія — не стратегія.
— Gordon R. Sullivan
Швидкий план діагностики (перевірте це в першу чергу)
Коли хтось пише «сайт впав, забагато перенаправлень», не починайте з того, що пильно дивитеся на конфіг Nginx, ніби він винний.
Почніть з трьох питань швидко й з доказами.
1) Петля за хостом, схемою чи додатком?
- Використайте
curl -IL, щоб побачити ланцюг редиректів і що змінює кожен хоп. - Якщо чергується між http/https: проблема зі схемою (або заголовками проксі).
- Якщо чергується між www/без-www: конфлікт канонічного хоста (Nginx проти додатку чи CDN).
- Якщо залишається той самий URL, але постійно 301/302: додаток може редиректити сам себе на основі заголовків/куків.
2) Де завершується TLS?
- Якщо TLS завершується в Nginx: використовуйте
$schemeі слухайте 443 з сертифікатами. - Якщо TLS завершується на балансувальнику/CDN: Nginx бачить HTTP, тому редиректи мають базуватися на
X-Forwarded-Protoабо PROXY протоколі. - Якщо не впевнені: перевірте
ss -lntpі конфіг платформи.
3) Хто відповідає за канонізацію?
- Виберіть рівно одного: Nginx на краю, CDN або додаток.
- Якщо і Nginx, і додаток редиректять до «канонічного», ви зрештою створите петлю через дрібну невідповідність (порт, хост, слеш, схема).
Зробіть ці три кроки — і перестанете гадати. Більшість петель зводяться до одного поганого «if ($scheme = http)» за проксі або дуельних правил для www.
Встановіть істину: хто завершує TLS і хто визначає канонічність
На Debian 13 Nginx поводиться так само, як на Debian 12, але ваше оточення, мабуть, ні.
«Debian 13» зазвичай означає «новий вм, нові дефолти, інша поведінка LB, нова автоматизація сертифікатів, нова копіпастна конфігурація».
Визначте політику канонічної URL (запишіть її)
Виберіть один канонічний хост і одну канонічну схему. Приклади:
- https://example.com (без www) — канонічний; весь http і www редиректяться туди.
- https://www.example.com — канонічний; всі non-www редиректяться туди, а весь http редиректиться на https.
Не робіть «обидва працюють» через шари редиректів. Зробіть так, щоб вони прибували в одне місце.
Визначте, де живуть редиректи
Моя тверда думка: виконуйте канонічні редиректи на краю, який ви найнадійніше контролюєте. Для багатьох Debian/Nginx розгортань це сам Nginx.
Для важких CDN-настроювань CDN може це робити — якщо ви дисципліновані і видалите ту ж логіку з Nginx/додатку.
Якщо фреймворк додатку також змушує HTTPS (поширено в Django, Rails, Laravel, Spring), ви або:
- Вимикаєте це в додатку і дозволяєте Nginx робити редиректи, або
- Дозволяєте додатку обробляти це й гарантуєте, що Nginx не конфліктує (без суперечливих rewrite, коректні forwarded заголовки).
Практичні задачі (команди, виводи, рішення)
Ви виправите це швидше, зібравши сигнали. Нижче — практичні завдання, які можна виконати на Debian 13.
Кожне завдання містить: команду, приклад виводу, що це означає та яке рішення прийняти.
Завдання 1: Підтвердити, що Nginx запущений і яка конфігурація завантажена
cr0x@server:~$ systemctl status nginx --no-pager
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Mon 2025-12-30 09:10:18 UTC; 2h 3min ago
Docs: man:nginx(8)
Main PID: 1642 (nginx)
Tasks: 3 (limit: 18988)
Memory: 7.4M
CPU: 1.231s
CGroup: /system.slice/nginx.service
├─1642 "nginx: master process /usr/sbin/nginx -g daemon on; master_process on;"
├─1643 "nginx: worker process"
└─1644 "nginx: worker process"
Значення: Nginx активний. Ви не налагоджуєте мертвий сервіс.
Рішення: Перейдіть до перевірки конфігурації; якщо він не активний — виправте помилки запуску сервісу.
Завдання 2: Перевірити синтаксис конфігурації (не пропускайте це)
cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Значення: Синтаксис в порядку. Петлі редиректів — логічні, а не на етапі парсингу.
Рішення: Перейдіть до «ефективної конфігурації» і ланцюга редиректів.
Завдання 3: Злити ефективну конфігурацію та знайти правила редиректу, які фактично застосовуються
cr0x@server:~$ sudo nginx -T 2>/dev/null | sed -n '1,140p'
# configuration file /etc/nginx/nginx.conf:
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events { worker_connections 768; }
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Значення: Nginx включає conf.d і sites-enabled.
Рішення: Знайдіть у виводі return 301, rewrite, $scheme і X-Forwarded-Proto.
Завдання 4: Знайти всі оператори редиректу і серверні блоки, в яких вони знаходяться
cr0x@server:~$ sudo nginx -T 2>/dev/null | egrep -n 'server_name|listen|return 30|rewrite|X-Forwarded-Proto|proxy_redirect'
58: server {
59: listen 80 default_server;
60: server_name _;
61: return 301 https://$host$request_uri;
84: server {
85: listen 443 ssl http2;
86: server_name example.com www.example.com;
101: if ($host = www.example.com) { return 301 https://example.com$request_uri; }
129: proxy_set_header X-Forwarded-Proto $scheme;
Значення: Є дефолтний редирект на порті 80 і правило канонізації хоста на 443.
Рішення: Це може бути коректно — але якщо ви за проксі, що говорить HTTP до Nginx, $scheme буде http і може запускати петлі.
Завдання 5: Подивитися ланцюг редиректів з самого сервера (обійти браузер)
cr0x@server:~$ curl -IL http://example.com/
HTTP/1.1 301 Moved Permanently
Server: nginx
Location: https://example.com/
HTTP/2 301
server: nginx
location: https://example.com/
HTTP/2 301
server: nginx
location: https://example.com/
Значення: Той самий цільовий URL повторюється: https://example.com/ редиректить сам на себе. Це не «HTTP→HTTPS».
Це логічна умова, що спрацьовує на HTTPS-запитах.
Рішення: Перевірте серверний блок 443 на наявність безумовного редиректу (часто через помилковий if, змінну або поведінку upstream додатку).
Завдання 6: Перевірити, чи дійсно 443 завершує TLS на цьому хості
cr0x@server:~$ sudo ss -lntp | egrep ':(80|443)\s'
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1642,fd=6))
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=1642,fd=7))
Значення: Nginx слухає обидва порти локально. Це вказує, що TLS тут завершується (якщо проксі не тунелює інакше).
Рішення: Якщо ви за балансувальником, переконайтеся, чи він підключається до origin на 443 або 80.
Завдання 7: Перевірити, який сертифікат віддає Nginx (підтверджує, що ви потрапляєте на потрібну машину)
cr0x@server:~$ echo | openssl s_client -connect 127.0.0.1:443 -servername example.com 2>/dev/null | openssl x509 -noout -subject -issuer -dates
subject=CN = example.com
issuer=C = US, O = Let's Encrypt, CN = R11
notBefore=Dec 1 00:12:33 2025 GMT
notAfter=Mar 1 00:12:32 2026 GMT
Значення: TLS завершується в Nginx і SNI співпадає.
Рішення: Зосередьтеся на логіці 443 в Nginx і upstream-додатку, а не на зовнішньому TLS.
Завдання 8: Переглянути access log на повторювані патерни 301/302
cr0x@server:~$ sudo tail -n 8 /var/log/nginx/access.log
203.0.113.44 - - [30/Dec/2025:11:18:19 +0000] "GET / HTTP/2.0" 301 169 "-" "curl/8.10.1"
203.0.113.44 - - [30/Dec/2025:11:18:19 +0000] "GET / HTTP/2.0" 301 169 "-" "curl/8.10.1"
203.0.113.44 - - [30/Dec/2025:11:18:20 +0000] "GET / HTTP/2.0" 301 169 "-" "curl/8.10.1"
Значення: Той самий клієнт постійно редиректиться на тому самому шляху.
Рішення: Ймовірно, це безумовний редирект у 443 сервері або додаток редиректить, а Nginx знову редиректить.
Завдання 9: Увімкнути тимчасовий debug-лог редиректів
cr0x@server:~$ sudo install -m 0644 /dev/null /var/log/nginx/redirect-debug.log
cr0x@server:~$ sudo tee /etc/nginx/conf.d/redirect-debug.conf >/dev/null <<'EOF'
log_format redir '$remote_addr "$host" $server_name $scheme '
'xfp="$http_x_forwarded_proto" xfh="$http_x_forwarded_host" '
'"$request" => $status loc="$sent_http_location" '
'ua="$http_user_agent"';
access_log /var/log/nginx/redirect-debug.log redir;
EOF
cr0x@server:~$ sudo nginx -t && sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Значення: Тепер ви логуватимете схему і forwarded заголовки для кожного запиту.
Рішення: Відтворіть проблему один раз, потім прочитайте debug-лог, щоб зрозуміти, який сигнал спричиняє редирект.
Завдання 10: Відтворити і прочитати redirect debug лог
cr0x@server:~$ curl -IL https://example.com/ -H 'Host: example.com'
HTTP/2 301
server: nginx
location: https://example.com/
cr0x@server:~$ sudo tail -n 3 /var/log/nginx/redirect-debug.log
203.0.113.44 "example.com" example.com https xfp="-" xfh="-" "GET / HTTP/2.0" => 301 loc="https://example.com/" ua="curl/8.10.1"
Значення: Nginx вважає схему https, але все ж повернув 301 на той самий URL.
Рішення: Цей редирект не залежить від схеми; це погане правило канонізації або rewrite/return у неправильному сервері/локації.
Завдання 11: Перелічити ввімкнені сайти і переконатися, що немає конфліктних серверних блоків
cr0x@server:~$ ls -l /etc/nginx/sites-enabled/
total 0
lrwxrwxrwx 1 root root 34 Dec 30 09:05 default -> /etc/nginx/sites-available/default
lrwxrwxrwx 1 root root 39 Dec 30 09:07 example -> /etc/nginx/sites-available/example
Значення: Сайт default все ще ввімкнений. На нових інсталяціях він часто містить редирект або catch-all.
Рішення: Якщо ваш «default» сервер робить редиректи, він може перехоплювати непередбачувані Host-и. Вимкніть його, якщо не використовуєте.
Завдання 12: Знайти, який серверний блок відповідає за певний hostname (default_server пастка)
cr0x@server:~$ sudo nginx -T 2>/dev/null | awk '
/server \{/ {in=1; blk=""; next}
in {blk=blk $0 "\n"}
/\}/ && in {print "----\n" blk; in=0}' | egrep -n 'listen 80|listen 443|server_name'
3: listen 80 default_server;
4: server_name _;
11: listen 80;
12: server_name example.com www.example.com;
19: listen 443 ssl http2;
20: server_name example.com www.example.com;
Значення: Є явний default_server на 80, який відповість на все. Добре, якщо він повертає 444/404; небезпечно, якщо він редиректить.
Рішення: Зробіть default server таким, що повертає 444 або мінімальний 404 — не канонічний редирект — якщо ви не контролюєте всі вхідні Host-и.
Завдання 13: Перевірити, чи додаток upstream виконує редирект
cr0x@server:~$ sudo grep -R "proxy_pass" -n /etc/nginx/sites-available/example
42: proxy_pass http://127.0.0.1:8080;
cr0x@server:~$ curl -I http://127.0.0.1:8080/
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
Server: gunicorn
Значення: Додаток редиректить на HTTPS сам. Якщо Nginx також редиректить, легко отримати петлю (особливо за проксі).
Рішення: Або дайте додатку обробляти HTTPS-редиректи і налаштуйте forwarded заголовки правильно, або вимкніть редирект у додатку і дозвольте Nginx бути власником.
Завдання 14: Перевірити HSTS, який змушує браузер «боротися» з вашими тестами
cr0x@server:~$ curl -I https://example.com/ | egrep -i 'strict-transport-security|location|http/'
HTTP/2 301
location: https://example.com/
strict-transport-security: max-age=31536000; includeSubDomains
Значення: Якщо HSTS присутній, браузери примушуватимуть HTTPS для цього хоста протягом max-age.
Рішення: Під час інциденту тестуйте за допомогою curl і з чистими хостами; не довіряйте фольклору «працює в інкогніто».
Завдання 15: Слідкувати за помилками Nginx під час перезавантажень і обробки запитів
cr0x@server:~$ sudo journalctl -u nginx -n 50 --no-pager
Dec 30 11:14:02 server nginx[1642]: 2025/12/30 11:14:02 [notice] 1642#1642: signal process started
Dec 30 11:18:20 server nginx[1644]: 2025/12/30 11:18:20 [info] 1644#1644: *912 client 203.0.113.44 closed keepalive connection
Значення: Немає помилок при перезавантаженні. Логи не скажуть прямо «петля редиректів», але покажуть проблеми перезавантаження конфігу та патерни запитів.
Рішення: Якщо перезавантаження не вдається, виправте синтаксис/права спочатку. Інакше працюйте з access логами і trace через curl.
Правильне рішення: один канонічний шлях редиректу
Надійний шаблон нудний. Саме тому він працює.
- Майте один серверний блок для HTTP, який лише редиректить на канонічний HTTPS-хост.
- Майте один серверний блок для HTTPS канонічного хоста, який обслуговує контент (або проксує).
- Опційно, мати один HTTPS серверний блок для неканонічного хоста, який редиректить на канонічний.
- Ніколи не редиректьте HTTPS-запити назад на той самий HTTPS-URL (так, люди випадково так роблять через змінні).
- За зворотним проксі не використовуйте
$scheme, якщо TLS не завершується в Nginx.
Чому виникають петлі: зіткнення канонічного хоста та «допоміжності» HTTPS
Типова форма петлі:
- CDN завершує TLS, надсилає HTTP на origin.
- Origin Nginx каже «якщо схема http, редиректити на https». Він бачить
$scheme=httpі редиректить. - Клієнт переходить на HTTPS до CDN знову. CDN повторює HTTP до origin. Origin знову редиректить. Петля.
Інша форма петлі:
- Nginx на 443 каже «якщо хост www, редиректити на non-www».
- Додаток каже «якщо хост non-www, редиректити на www» (бо хтось неправильно встановив змінну середовища).
- Петля. Кожен «правильний» у своїй картині світу.
Жарт №2: Редиректи Nginx — як офісна політика — якщо дві команди вирішують за одне й те саме, ви будете ходити по колу вічно.
Тверда порада: виберіть власника і видаліть інших власників
Якщо Nginx — ваш власник канонічності:
- Вимкніть у фреймворку «force HTTPS», або налаштуйте його довіряти проксі-заголовкам і не повторно редиректити.
- Нехай Nginx обчислює канонічний хост один раз і застосовує це послідовно.
Якщо додаток — ваш власник:
- Припиніть робити host/scheme редиректи в Nginx. Використовуйте Nginx як простий трубопровід з коректними
X-Forwarded-*заголовками. - Переконайтеся, що додаток довіряє цим заголовкам лише від IP вашого проксі (безпека важлива; open redirect і підробка реальні).
Зразки конфігурацій Nginx, що не створюють петлів
Ці шаблони навмисно прості. Витончені конфіги не приносять нагород за доступність.
Замініть example.com і upstream деталі за потреби.
Випадок A: TLS завершується в Nginx (більшість VPS розгортань)
Канонічний: https://example.com. Редиректіть все інше туди.
cr0x@server:~$ sudo tee /etc/nginx/sites-available/example >/dev/null <<'EOF'
# Canonical: https://example.com
# 1) HTTP: redirect to canonical HTTPS host
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
# 2) HTTPS for non-canonical host: redirect to canonical
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
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;
}
# 3) HTTPS canonical: serve
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Optional: keep this minimal during incidents
access_log /var/log/nginx/example.access.log;
error_log /var/log/nginx/example.error.log;
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 $scheme;
}
}
EOF
cr0x@server:~$ sudo ln -sf /etc/nginx/sites-available/example /etc/nginx/sites-enabled/example
cr0x@server:~$ sudo nginx -t && sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Чому це працює: нема if ($scheme …) всередині 443 канонічного сервера. HTTPS-запити не будуть знову редиректитись за схемою.
Редиректи за хостом відбуваються лише для неканонічного хоста.
Випадок B: TLS завершується нагорі (LB/CDN), origin лише HTTP
Якщо балансувальник підключається до Nginx на порту 80, ваш origin не може покладатися на $scheme. Він завжди буде http.
У такому середовищі зазвичай варто уникати HTTP→HTTPS редиректів на origin і робити їх на краю.
Але іноді треба примусово застосувати канонічність на origin (комплаєнс, кілька кутів входу, заплутаний корпоративний DNS). Тоді:
- Довіряйте
X-Forwarded-Protoлише з IP LB/CDN. - Використайте
map, щоб обчислити «реальну схему» і редиректити лише коли це справді було HTTP з боку клієнта.
cr0x@server:~$ sudo tee /etc/nginx/conf.d/real-scheme.conf >/dev/null <<'EOF'
# Compute client-facing scheme safely-ish (still requires IP restrictions below).
map $http_x_forwarded_proto $client_scheme {
default $scheme;
"~*^https$" https;
"~*^http$" http;
}
EOF
cr0x@server:~$ sudo tee /etc/nginx/sites-available/example >/dev/null <<'EOF'
# Origin assumes it may be behind a trusted proxy providing X-Forwarded-Proto.
# Canonical: https://example.com
# IMPORTANT: Restrict who can send spoofed X-Forwarded-Proto
# Replace these with your LB/CDN addresses.
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 192.168.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# Redirect only if the *client-facing* scheme is http
if ($client_scheme = http) {
return 301 https://example.com$request_uri;
}
# If it's already HTTPS at the edge, serve/proxy normally.
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $client_scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
EOF
cr0x@server:~$ sudo nginx -t && sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Попередження: Це if прийнятне, бо це простий return на рівні server. Проте краще робити редиректи на краю, якщо можливо.
Також: якщо ви не обмежите, хто може ставити forwarded заголовки, клієнт може їх підробити і це створить вразливості.
Випадок C: Default server не має редиректити (зменшити радіус ураження)
Ваш default server існує, щоб безпечно обслуговувати некоректні Host заголовки. Він не повинен «допомагати» канонізувати у ваш справжній домен.
Саме так ви опиняєтеся на шляху до обслуговування чужого домену через ваш сайт і створення дивних редирект-штормів.
cr0x@server:~$ sudo tee /etc/nginx/sites-available/default >/dev/null <<'EOF'
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 444;
}
EOF
cr0x@server:~$ sudo nginx -t && sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Значення: Nginx закриває з’єднання без відповіді. Це підходить для невідомих хостів.
Рішення: Тримайте так. Це запобігає випадковим редиректам і робить ваші канонічні правила застосованими лише там, де треба.
За балансувальником/CDN: припиніть довіряти невірному сигналу
Більшість петель редиректів у 2025 році не «Nginx не вміє редиректити». Це «Nginx розповідають неправдиву історію про запит».
Якщо проксі завершує TLS, ваш origin бачить HTTP. Якщо origin наполягає на HTTP→HTTPS, отримуєте бігове коло.
Впізнайте класичну сигнатуру проксі-петлі
У curl -IL ви бачите:
Location: https://example.com/…повторюється- Статус 301/302 знову і знову
- І ваш origin access логи показують тільки HTTP-запити, ніколи 443
Дерево рішень: що робити
- Якщо можете: реалізуйте HTTP→HTTPS редирект на краю (LB/CDN). Вимкніть його на origin. Це найчистіше.
- Якщо мусите робити це на origin: довіряйте заголовку forward-у схеми від проксі, і лише від проксі.
- Якщо не можна довіряти заголовкам: використайте PROXY протокол end-to-end (LB → Nginx) і налаштуйте Nginx відповідно.
Перевірте forwarded заголовки, що приходять в Nginx
Ваш redirect debug лог з Завдання 9 вже виводить:
xfp="$http_x_forwarded_proto" і xfh="$http_x_forwarded_host".
Якщо вони пусті — ваш край їх не надсилає, або Nginx не отримує той запит, який ви думаєте.
Чому «просто поставити proxy_set_header X-Forwarded-Proto $scheme» може бути неправильним
Цей рядок часто копіюють у конфіг origin proxy. Він встановлює X-Forwarded-Proto рівним схемі origin, а не клієнта.
Якщо TLS завершується зверху, $scheme — http. Вітаємо, ви щойно повідомили додатку «це HTTP» назавжди.
Якщо ваш додаток змушує HTTPS на основі X-Forwarded-Proto, він постійно редиректитиме. Якщо Nginx змушує HTTPS на основі $scheme,
він теж постійно редиректитиме. Петля не містить містики. Вона сумлінна.
Примітка з безпеки: forwarded заголовки — це вхідні дані від користувача
Клієнт може надіслати X-Forwarded-Proto: https прямо до вашого origin, якщо може його досягти.
Якщо ви довіряєте цьому заголовку з відкритого інтернету, ви можете зламати припущення безпеки і створити дивну поведінку редиректів і cookie.
Обмежуйте за мережею або переконайтесь, що origin недоступний напряму.
Три корпоративні міні-історії (реалістично, болісно, корисно)
Міні-історія 1: Інцидент через хибне припущення
Середня компанія перенесла портал клієнтів з on-prem пар Nginx до керованого балансувальника перед новим флотом Debian.
План міграції мав одне речення, що їх прирекло: «TLS залишається тим самим.» Усі кивнули, бо це звучало заспокійливо.
Раніше TLS завершувався в Nginx. У новій архітектурі балансувальник завершував TLS і спілкувався з origin через HTTP, бо «так простіше»
і команда не хотіла розповсюджувати сертифікати на інстанси. Origin Nginx лишив старе правило: return 301 https://$host$request_uri;
в сервері на порту 80.
Першим симптомом не було падіння моніторів — це був вибух заявок у супорт: «Логін постійно перезавантажується.»
Декому вдавалося завантажити головну сторінку (кешована). Інші не могли автентифікуватись, бо петля редиректів блокувала встановлення сесійного cookie.
SRE бачили 301 в логах, але думали «редиректи нормальні; браузери їх витримають».
Виправлення було просте і трохи принизливе: прибрати HTTP→HTTPS редирект на origin, реалізувати його на балансувальнику
і додати редирект канонічного хоста тільки на одному шарі. Найкраща цитата з постмортему була нудною:
«Ми припустили, що $scheme відображає схему клієнта.» Ні — він відображав хоп у Nginx.
Міні-історія 2: Оптимізація, що повернулася бумерангом
В іншому місці команда вирішила «оптимізувати», консолідувавши серверні блоки.
Один мегаблок обробляв HTTP і HTTPS, www і non-www, кілька середовищ,
і обчислював ціль редиректа через змінні, щоб конфіг був «DRY».
Почалося невинно: map для канонічного хоста, змінна для схеми, кілька if-ів.
Потім з’явився feature-flag для «maintenance mode» і rewrite для legacy-путей.
Хтось додав return 301 $canonical_scheme://$canonical_host$request_uri; всередині location, що збігався надто широко.
У staging це «працювало», бо staging мав один хост і без CDN. У проді за проксі
канонічна схема змінної обчислювалася як https на основі X-Forwarded-Proto — але один шлях не мав цього заголовка,
бо WAF його обрізав для певних user agent-ів.
Результат: петля редиректів, що вражала тільки частину клієнтів і тільки на шляху оформлення замовлення. Дашборд був зеленим.
Доходи — ні. Оптимізація зекономила 40 рядків конфігу і коштувала днів реагування.
Вони відкочували зміни до окремих серверних блоків і прийняли дублювання як фічу надійності.
Міні-історія 3: Нудна, але правильна практика, що врятувала ситуацію
Команда фінансових послуг мала правило: кожна зміна Nginx, що торкається редиректів, повинна містити записаний curl -IL ланцюг
для чотирьох URL: http/non-www, http/www, https/non-www, https/www. Вивід додавався до change request.
Люди бурчали. Здавалося бюрократією.
Під час рутинного оновлення сертифікатів новий інженер випадково знову увімкнув default site.
Default server на порту 80 редиректив на канонічний хост. Тим часом додаток наполягав на іншому канонічному хості залежно від конфігу.
Така комбінація створила петлю лише для запитів, що потрапляли на default server (тобто будь-який невпізнаний Host).
Рев’ювер change request помітив це, бо потрібні curl-ланцюги раптом показали редирект від несподіваного hostname у проді.
Рев’ювер поставив просте питання: «Чому default_server редиректить кудись?» Виправили це, зробивши default server return 444.
Ніякого інциденту. Ніякого нічного відкату. Просто нудний процес зробив свою роботу. Таку нудь ви маєте полюбити.
Типові помилки: симптом → корінна причина → виправлення
1) Симптом: HTTP→HTTPS петля тільки за CDN/LB
Корінна причина: TLS завершується на CDN/LB, origin бачить HTTP, origin редиректить на HTTPS, CDN повторює.
Виправлення: Перенесіть редирект на край або довіряйте X-Forwarded-Proto від проксі і базуйте редирект на ньому.
2) Симптом: https://example.com редиректить сам на себе (той самий URL)
Корінна причина: Безумовний return 301 https://example.com$request_uri; всередині 443 сервера (або помилково обмежена локація).
Виправлення: Приберіть редирект з канонічного HTTPS сервера. Редиректіть лише з HTTP сервера або з неканонічного HTTPS сервера.
3) Симптом: чергування між www і non-www
Корінна причина: Nginx і додаток не погоджуються щодо канонічного хоста (або CDN має власний редирект).
Виправлення: Виберіть одного власника канонізації хоста. Вимкніть інші правила. Перевірте curl -IL з різних точок.
4) Симптом: Працює для одних користувачів, для інших петля
Корінна причина: Розділена поведінка через різні POP-и, A/B маршрутизацію, WAF, що обрізає заголовки, або лише певні шляхи тригерять логіку додатку.
Виправлення: Додайте логування для $scheme, $host, $sent_http_location і forwarded заголовків. Порівняйте невдалі та вдалi запити.
5) Симптом: Лише ваш браузер падає; curl працює
Корінна причина: Закешований 301 або HSTS в браузері, або застарілі cookie, що тригерять логіку додатку.
Виправлення: Використовуйте curl з -IL. Очистіть HSTS для хоста або тестуйте з чистого профілю/пристрою. Розгляньте тимчасове видалення HSTS поки стабілізуєтеся.
6) Симптом: Петля почалася після увімкнення «default» сайту
Корінна причина: default_server перехоплює запити і застосовує редирект, призначений для конкретного хоста.
Виправлення: Зробіть default server return 444/404. Переконайтеся, що справжні vhost-и мають точні server_name і не конкурують.
7) Симптом: OAuth логін ламається через redirect_uri mismatch
Корінна причина: Канонічний хост/схема змінюються в процесі; редиректи переписують схему/хост і провайдер відкидає.
Виправлення: Наведіть канонізацію перед auth endpoint-ами, і забезпечте, щоб додаток бачив коректну схему/хост через forwarded заголовки.
8) Симптом: Websocket ендпойнти падають, а звичайні сторінки працюють
Корінна причина: Редирект або примус схеми застосовано до шляхів /ws; upstream очікує upgrade і отримує 301.
Виправлення: Звільніть websocket локації від редиректів; забезпечте, щоб канонізація відбувалась на рівні server, а не всередині websocket локацій.
Чеклісти / покроковий план
Покроковий план, щоб виправити петлю без гадань
-
Захопіть ланцюг редиректів.
Запустіть curl з чистого середовища:cr0x@server:~$ curl -IL http://example.com/ HTTP/1.1 301 Moved Permanently Location: https://example.com/ HTTP/2 200Рішення: Якщо ланцюг чергує схему/хост, ви вже знаєте, з чим боретесь.
-
Визначте канонічний хост + схему письмово. Приклад: «канонічний — https://example.com».
Рішення: Усе інше редиректиться туди, один раз.
-
Визначте, де завершується TLS.
cr0x@server:~$ sudo ss -lntp | egrep ':(80|443)\s' LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1642,fd=6)) LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=1642,fd=7))Рішення: Якщо Nginx не слухає 443, не пишіть редиректи на основі $scheme на origin.
-
Приберіть дубльованих власників редиректів. Перевірте конфіг додатку на «force SSL»/«canonical host» і вимкніть або узгодьте їх.
Рішення: Один власник. Не двоє.
-
Зробіть default server нешкідливим. Поверніть 444 або мінімальний 404.
Рішення: Зменште радіус ураження для неправильно маршрутизованих Host-ів.
-
Використайте окремі серверні блоки для редиректів. HTTP redirect block; HTTPS canonical serve block; опційний non-canonical HTTPS redirect block.
Рішення: Надавайте перевагу зрозумілості над хитрощами.
-
Додайте тимчасове debug логування редиректів, якщо невпевнені. Захопіть схему/хост/Location у логах.
Рішення: Не сперечайтеся з чужими припущеннями; сперечайтеся з рядками логів.
-
Перезавантажте і повторно протестуйте за допомогою curl. Завжди перевіряйте конфігурацію і перезавантажуйте акуратно.
cr0x@server:~$ sudo nginx -t && sudo systemctl reload nginx nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successfulРішення: Якщо петля лишається, проблема upstream (CDN/додаток), а не в синтаксисі Nginx.
Операційний чекліст (зберігайте для себе в майбутньому)
- Для будь-якої зміни редиректу записуйте curl-ланцюги для 4 точок входу: http/https × www/non-www.
- Підтвердіть, хто завершує TLS і де застосовуються редиректи.
- Переконайтеся, що
default_serverне редиректить на реальний домен. - За проксі: перевірте наявність
X-Forwarded-Protoі межі довіри. - Не одночасно включайте CDN «force HTTPS» та origin «force HTTPS», якщо ви не розумієте поведінку хопів.
- Будьте обережні з HSTS; увімкніть його лише після перевірки стабільності редиректів.
Поширені питання
1) Чому мій браузер каже «Забагато перенаправлень», а curl інколи працює?
Браузери кешують 301 і застосовують HSTS. Curl зазвичай не кешує між запусками.
Якщо раніше ви віддавали HSTS, браузер може примушувати HTTPS і ніколи не потрапляти у вашу HTTP-логіку так, як ви очікуєте.
2) Використовувати 301 чи 302 для канонічних редиректів?
Для стабільної канонізації (www→non-www, http→https) використовуйте 301 або 308. Якщо експериментуєте — 302/307, щоб уникнути постійного кешування.
В продакшені будьте свідомі: 301 може прилипнути довше, ніж ваше терпіння.
3) Чи нормально використовувати «if» в Nginx для редиректів?
Простий if, що одразу returnається на рівні server, зазвичай підходить.
Погана репутація походить від складних rewrites і вкладеної логіки. Надавайте перевагу окремим серверним блокам, коли можете.
4) Який найшвидший спосіб побачити ланцюг редиректів?
Використовуйте curl -IL. Додайте --max-redirs 20, якщо треба. Дивіться, що змінюється на кожному хопі: схема, хост чи шлях.
5) Я за CDN у режимі «Flexible SSL». Чому це викликає петлі?
Flexible SSL часто означає: браузер↔CDN — HTTPS, CDN↔origin — HTTP.
Origin бачить HTTP і редиректить на HTTPS. CDN продовжує використовувати HTTP до origin. Петля.
Виправте, використавши повний TLS до origin або перемістивши редиректи на CDN і вимкнувши їх на origin.
6) Може додаток спричиняти петлю, навіть якщо Nginx конфіг виглядає правильно?
Абсолютно. Багато фреймворків редиректять на основі сприйнятої схеми/хоста.
Якщо додаток бачить X-Forwarded-Proto: http (або ні), він може редиректити на HTTPS. Якщо Nginx теж редиректить або хости різні — петля.
7) Чому ризиковано залишати default site ввімкненим?
Бо default_server перехоплює невідповідні Host-и. Якщо він редиректить на ваш канонічний домен,
ви можете випадково обслуговувати чи редиректити трафік для несподіваних Host заголовків, включно з опечатками та ворожими запитами.
Зробіть default server return 444/404.
8) Як дізнатися, чи CDN/LB надсилає X-Forwarded-Proto?
Логуйте його в Nginx ($http_x_forwarded_proto) і перевірте. Якщо порожній — або не надсилають, або його обрізає щось, або ви не потрапляєте на очікуваний шлях.
Не припускайте; перевірте в логах.
9) Вмикати HSTS під час виправлення редиректів?
Ні. Спочатку виправте редиректи. Потім вмикайте HSTS, коли впевнені, що HTTPS стабільно обслуговується на канонічному хості.
HSTS — це зобов’язання: корисне, коли ви праві; проблемне, коли ні.
10) Я виправив Nginx, але користувачі все ще скаржаться на петлі. Що далі?
Перевірте край (CDN/LB) правила редиректів, налаштування кешу і чи є кілька origin.
Потім перевірте редиректи на рівні додатку. Нарешті — браузерний кеш/HSTS. Петля може бути поза коробкою, яку ви щойно відредагували.
Висновок: наступні кроки, які реально скоротять час інциденту
Петлі редиректів прості в теорії і дратують на практиці, бо кілька шарів вважають себе «відповідальними» за URL.
Ваше завдання — зробити один шар дорослим у кімнаті і звільнити інших від цієї відповідальності.
Практичні наступні кроки:
- Запустіть
curl -ILдля http/https × www/non-www і збережіть ланцюг у нотатках інциденту. - Визначте канонічну URL (хост + схема) і оберіть одного власника редиректів.
- Якщо за проксі/CDN — не використовуйте
$schemeяк «схему клієнта», якщо TLS не завершується в Nginx. - Зробіть
default_serverтаким, що повертає 444/404, а не редирект. - Перезавантажуйте Nginx безпечно (
nginx -tпотімsystemctl reload) і тестуйте ланцюг, поки він не звузиться до 1–2 переходів. - Лише після стабільності: розгляньте вмикання HSTS з планом відкату.
Після цього «Забагато перенаправлень» стане тим, чим має бути: швидким фіксом, а не рисою характеру.