Ви інсталюєте «вебсервер» на Ubuntu 24.04 і раптом їх два. Один займає порт 80, інший клянеться, що був першим, а ваш браузер потрапляє в цикл редиректів, відчуття ніби бігова доріжка з паперовою тяганиною.
Класичний випадок №34: Apache і Nginx обидва ввімкнені, обидва намагаються володіти 80/443, і конфігурація зворотного проксі випадково проксить назад до себе. Добра новина: це можна виправити без драми. Краще: зробити це нудно і повторювано, як люблять продакшн-системи.
Як це проявляється в реальному житті (симптоми, які ви реально побачите)
Ця проблема не оголошується ввічливо. Вона проявляється як:
- Nginx не стартує:
bind() to 0.0.0.0:80 failed (98: Address already in use) - Apache не стартує:
(98)Address already in use: AH00072: make_sock: could not bind to address - Браузер у циклі: нескінченний
ERR_TOO_MANY_REDIRECTS - Nginx 502/504: бекенд недосяжний або доступний, але з неправильним протоколом
- Ви робите curl localhost і отримуєте «не той» сайт: сторінка за замовчуванням від іншого сервера
- Працює локально, не працює зовні: бо прив’язка відрізняється між 127.0.0.1 і 0.0.0.0 або IPv6
Кореневі причини майже завжди вписуються в одну з цих категорій:
- Конфлікт власності порту: Apache і Nginx обидва намагаються слухати 80 і/або 443.
- Випадкова рекурсія проксі: Nginx проксить сам до себе, або Apache проксить сам до себе, часто через
localhost:80. - Плутанина HTTP↔HTTPS: бекенд думає, що це HTTP, фронтенд — що HTTPS, редиректи відскакують вічно.
- Невідповідність віртуальних хостів за іменем: заголовок Host не зберігається або блок серверу за замовчуванням ловить трафік.
Один короткий жарт, бо ми зараз серйозні: коли два демони б’ються за порт 80, Linux не обирає переможця; він просто дає вам лог і дивиться.
Швидкий план діагностики (перший/другий/третій)
Коли ви на чергуванні, вам не потрібна лекція. Потрібна послідовність, що швидко виявляє вузьке місце.
Ось послідовність, яку я виконую, коли Ubuntu 24.04 хостить заплутаний веб-стек.
Перший: хто зараз володіє 80/443?
- Перевірте сокети, які слухають
:80і:443(IPv4 і IPv6). - Вирішіть, який процес має володіти цими портами (фронтенд) та змусьте інший прив’язатися до іншого порту або не слухати зовсім.
Другий: чи є проксі-цикл?
- Шукайте
proxy_pass http://localhost(Nginx) абоProxyPass http://localhost(Apache), що вказує назад на порт, яким сам проксі володіє. - Слідкуйте за редиректами через curl. Якщо бачите повторюваний шаблон Location — ви в циклі.
Третій: чи правильно передаються заголовки та схема?
- Підтвердіть, що
X-Forwarded-ProtoіHostдоходять до бекенду. - Підтвердіть, що бекенд довіряє проксі і не «підвищує» з HTTP на HTTPS помилково.
Четвертий (лише за потреби): поведінка віртуального хоста за замовчуванням і SNI
- Перевірте, який vhost є дефолтним і чи збігається ваш server_name/ServerName.
- По TLS переконайтеся, що SNI вибирає правильний сертифікат і блок серверу.
Цікавинки та контекст (чому так відбувається)
Кілька конкретних фактів допоможуть розуміти хаос, замість сліпо колупатися в ньому.
Ось дев’ять, які мають значення на практиці:
- Apache з’явився раніше за Nginx: Apache HTTP Server почався в середині 1990-х; Nginx з’явився на початку 2000-х для ефективної роботи з великими навантаженнями.
- Проблема «C10k» вплинула на дизайн Nginx: event-driven архітектури стали важливими, коли обробка десяти тисяч одночасних з’єднань перестала бути теорією.
- Обидва сервери тепер можуть виконувати обидві ролі: Apache може бути зворотним проксі і термінувати TLS; Nginx може віддавати статику і проксити аплікації. Вибір інструменту — переважно операційна уподобання.
- Linux дозволяє лише одного слухача на IP:port: хіба що ви використовуєте складні трюки типу
SO_REUSEPORT(і більшість веб-стеків цього не має робити). - IPv6 може бути прихованим власником: ви можете «звільнити» IPv4 :80, але Apache все ще прив’язався до
[::]:80, і Nginx все одно впаде. - systemd змінює видимість помилок: ви не «запускаєте демон»; ви запускаєте unit, і залежності юніта можуть перезапускати або тримати процес напівживим.
- Сайти за замовчуванням зроблені, щоб вас зловити: Debian/Ubuntu постачають дефолтні включені сайти для Apache і Nginx. Вони корисні доти, доки ні.
- Цикли редиректів часто — це несумісність схем: бекенд бачить HTTP і редиректить на HTTPS, але проксі вже термінував TLS і послав внутрішньо HTTP.
- «localhost» — це підводний мінне поле в проксі: на тій же машині «localhost:80» часто вказує назад на сам проксі, а не на намічений бекенд.
Одна цитата, щоб тримати вас в курсі: «Надія — не стратегія.» — генерал Gordon R. Sullivan.
Виберіть адекватну архітектуру (і дотримуйтеся її)
Головне рішення: який демон — «фронт-доор» для портів 80/443?
Якщо ви залишаєте обидва, один — крайовий проксі, а інший — бекенд, прив’язаний до loopback-адреси або альтернативного порту. Все інше — клуб бійців.
Рекомендовані шаблони
- Шаблон A (поширений): Nginx на 80/443 → Apache на 127.0.0.1:8080
Використовуйте Nginx для термінації TLS, HTTP/2/3 (якщо потрібно), буферизації та кешування статичних файлів. Apache обробляє legacy-додатки, .htaccess або додатки, які залежать від модулів Apache. - Шаблон B: Apache на 80/443 → аплікаційні бекенди (PHP-FPM, upstream сервіси)
Якщо вам не потрібен Nginx, не запускайте його. Apache може робити зворотне проксування і TLS цілком нормально. - Шаблон C: тільки Nginx
Найчистіший варіант для багатьох розгортань: Nginx + PHP-FPM (або upstream аплікація) і жодного Apache.
Чого уникати
- Обидва Apache і Nginx слухають 80/443. Якщо «працює», це тільки тому, що один не стартував або фактично не прив’язався.
- Проксування до localhost:80 з крайового проксі. Це спосіб побудувати машину безкінечної рекурсії.
- Змішування відповідальності за термінацію TLS. TLS або завершується на проксі, або на бекенді. Не імпровізуйте в польоті.
Практичні завдання: команди, очікуваний вивід, і рішення (12+)
Це реальні завдання, які ви можете виконати на Ubuntu 24.04, щоб діагностувати і виправити проблему.
Кожне завдання містить: команду, приклад виводу, що це означає і яке рішення прийняти.
Завдання 1: Визначити, хто слухає порти 80 і 443 (IPv4/IPv6)
cr0x@server:~$ sudo ss -ltnp '( sport = :80 or sport = :443 )'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("apache2",pid=1421,fd=4))
LISTEN 0 511 [::]:80 [::]:* users:(("apache2",pid=1421,fd=5))
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=1550,fd=6))
Значення: Apache володіє портом 80 (v4 і v6). Nginx володіє 443. Це вже неконсистентно.
Рішення: Оберіть одного фронтенда для обох портів; зазвичай Nginx на 80/443 і перемістіть Apache на 127.0.0.1:8080.
Завдання 2: Підтвердити стан сервісів і недавні помилки
cr0x@server:~$ systemctl status nginx --no-pager
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Mon 2025-12-30 09:12:01 UTC; 5min ago
Docs: man:nginx(8)
Main PID: 1550 (nginx)
Tasks: 3 (limit: 18906)
Memory: 8.4M
CPU: 120ms
cr0x@server:~$ systemctl status apache2 --no-pager
● apache2.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/apache2.service; enabled; preset: enabled)
Active: active (running) since Mon 2025-12-30 09:10:44 UTC; 6min ago
Docs: https://httpd.apache.org/docs/2.4/
Main PID: 1421 (apache2)
Значення: Обидва ввімкнені і працюють. Це OK лише якщо вони не змагаються за одні й ті самі порти.
Рішення: Якщо ви хочете проксі-архітектуру, збережіть обидва, але перемістіть Apache. Якщо ні — зупиніть і вимкніть один із них.
Завдання 3: Швидко пошукати «address already in use» в логах
cr0x@server:~$ sudo journalctl -u nginx -u apache2 --since "1 hour ago" | tail -n 40
Dec 30 09:08:11 server nginx[1490]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
Dec 30 09:08:11 server nginx[1490]: nginx: [emerg] still could not bind()
Dec 30 09:08:12 server systemd[1]: nginx.service: Failed with result 'exit-code'.
Dec 30 09:11:59 server systemd[1]: Started nginx.service - A high performance web server and a reverse proxy server.
Значення: Nginx намагався прив’язати 80 і програв принаймні одного разу. Потім щось змінилося (можливо, Apache тимчасово зупинився або змінилася конфігурація Nginx).
Рішення: Виправте власність портів, щоб перезапуски були детермінованими.
Завдання 4: Перевірити, на що налаштований Apache
cr0x@server:~$ sudo grep -R "^[[:space:]]*Listen" -n /etc/apache2/ports.conf /etc/apache2/sites-enabled/*.conf
/etc/apache2/ports.conf:5:Listen 80
/etc/apache2/ports.conf:8:Listen 443
Значення: Apache явно слухає і на 80, і на 443.
Рішення: Якщо Nginx — край, приберіть ці настройки і перемістіть Apache на loopback:8080 (та, можливо, 127.0.0.1:8443, якщо вам потрібен внутрішній TLS).
Завдання 5: Перевірити, на що налаштований Nginx
cr0x@server:~$ sudo nginx -T 2>/dev/null | grep -R --line-number -E "listen\s+(80|443)" /etc/nginx/sites-enabled /etc/nginx/conf.d
/etc/nginx/sites-enabled/default:22: listen 80 default_server;
/etc/nginx/sites-enabled/default:23: listen [::]:80 default_server;
/etc/nginx/sites-enabled/default:30: listen 443 ssl default_server;
/etc/nginx/sites-enabled/default:31: listen [::]:443 ssl default_server;
Значення: Nginx теж налаштований на обидва порти як сервер за замовчуванням. Класична колізія.
Рішення: Залиште Nginx на 80/443; перемістіть Apache з цих портів.
Завдання 6: Виявити випадковий проксі-цикл у Nginx
cr0x@server:~$ sudo grep -R --line-number -E "proxy_pass\s+http://(localhost|127\.0\.0\.1)(:80)?/?;" /etc/nginx/sites-enabled
/etc/nginx/sites-enabled/app.conf:41: proxy_pass http://localhost;
Значення: Якщо Nginx слухає 80 і проксує до http://localhost (що за замовчуванням порт 80), він проксує сам до себе. Це генератор циклів.
Рішення: Проксити на бекенд-порт типу 127.0.0.1:8080, на Unix-сокет або на інший upstream сервіс.
Завдання 7: Виявити випадковий проксі-цикл в Apache
cr0x@server:~$ sudo grep -R --line-number -E "ProxyPass\s+/" /etc/apache2/sites-enabled
/etc/apache2/sites-enabled/000-default.conf:18:ProxyPass / http://127.0.0.1:80/
/etc/apache2/sites-enabled/000-default.conf:19:ProxyPassReverse / http://127.0.0.1:80/
Значення: Apache проксує «/» на 127.0.0.1:80. Якщо Apache сам слухає 80, це рекурсія.
Рішення: Або приберіть проксування в Apache, або забезпечте, щоб ціль була іншим портом/сервісом.
Завдання 8: Слідувати за редиректами і побачити цикл через curl
cr0x@server:~$ curl -I -L --max-redirs 10 http://example.internal/
HTTP/1.1 301 Moved Permanently
Server: nginx
Location: https://example.internal/
HTTP/2 301
server: nginx
location: http://example.internal/
curl: (47) Maximum (10) redirects followed
Значення: HTTP редиректить на HTTPS, потім HTTPS редиректить назад на HTTP. Майже завжди це проблема з виявленням схеми через X-Forwarded-Proto або налаштування бекенду.
Рішення: Переконайтеся, що один компонент відповідає за редиректи, і що бекенд розуміє початкову схему.
Завдання 9: Підтвердити заголовок Host і заголовки пересилання від фронтенду
cr0x@server:~$ sudo nginx -T 2>/dev/null | sed -n '1,120p'
# configuration file /etc/nginx/nginx.conf:
user www-data;
worker_processes auto;
...
cr0x@server:~$ sudo grep -R --line-number -E "proxy_set_header\s+(Host|X-Forwarded-Proto|X-Forwarded-For)" /etc/nginx/sites-enabled
/etc/nginx/sites-enabled/app.conf:35: proxy_set_header Host $host;
/etc/nginx/sites-enabled/app.conf:36: proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
/etc/nginx/sites-enabled/app.conf:37: proxy_set_header X-Forwarded-Proto $scheme;
Значення: Ці заголовки присутні. Добре. Якщо їх нема, бекенди часто неправильно визначають схему/хост і редиректять.
Рішення: Якщо цикли редиректів залишаються, переконайтеся, що бекенд довіряє цим заголовкам і не переписує їх.
Завдання 10: Перевірити прив’язки vhost Apache після змін (саніті-чек)
cr0x@server:~$ sudo apachectl -S
VirtualHost configuration:
*:8080 is a NameVirtualHost
default server app.internal (/etc/apache2/sites-enabled/001-app.conf:1)
port 8080 namevhost app.internal (/etc/apache2/sites-enabled/001-app.conf:1)
ServerRoot: "/etc/apache2"
Main DocumentRoot: "/var/www/html"
Main ErrorLog: "/var/log/apache2/error.log"
Значення: Apache тепер прив’язаний до 8080 для vhost-ів, не до 80/443. Це саме те, що потрібно коли Nginx попереду.
Рішення: Продовжуйте і перезапустіть сервіси та перевірте наскрізну роботу.
Завдання 11: Перевірити конфіг Nginx і перезавантажити без здогадок
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
Значення: Синтаксис OK. Це не доводить, що ваш проксі-таргет правильний, але запобігає помилкам «reload зламав прод».
Рішення: Перезавантажуйте Nginx лише після успішного тесту.
Завдання 12: Підтвердити досяжність бекенду напряму (обхід проксі)
cr0x@server:~$ curl -sS -D- http://127.0.0.1:8080/ -o /dev/null | head
HTTP/1.1 200 OK
Date: Tue, 30 Dec 2025 09:20:17 GMT
Server: Apache/2.4.58 (Ubuntu)
Content-Type: text/html; charset=UTF-8
Значення: Apache бекенд доступний на 8080 і повертає 200. Гарна базова лінія.
Рішення: Якщо це не працює — виправте бекенд спочатку. Не дебажте проксі, поки бекенд не працює.
Завдання 13: Підтвердити поведінку краю (Nginx на 80/443)
cr0x@server:~$ curl -sS -D- http://127.0.0.1/ -o /dev/null | head
HTTP/1.1 301 Moved Permanently
Server: nginx
Location: https://app.internal/
cr0x@server:~$ curl -k -sS -D- https://127.0.0.1/ -o /dev/null | head
HTTP/2 200
server: nginx
content-type: text/html; charset=UTF-8
Значення: Nginx — край. HTTP редиректить на HTTPS; HTTPS повертає 200.
Рішення: Якщо HTTPS повертає 301 назад на HTTP — виправте forwarding proto / логіку редиректів бекенду.
Завдання 14: Перевірити, що жоден інший процес не займає порти
cr0x@server:~$ sudo lsof -nP -iTCP:80 -sTCP:LISTEN
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
nginx 1550 root 6u IPv4 29826 0t0 TCP *:80 (LISTEN)
nginx 1550 root 7u IPv6 29827 0t0 TCP *:80 (LISTEN)
Значення: Тільки Nginx слухає 80. Це «нудний» стан, який вам потрібен.
Рішення: Якщо ви бачите Apache або щось інше тут — зупиніться і перевірте конфіги і ввімкнені юніти ще раз.
Як чисто виправити прив’язку портів (власник — Apache або Nginx)
Виправлення — це не «перезапускайте, поки не запрацює». Виправлення: один сервіс володіє публічними портами, інший — ні.
Нижче два чисті рішення. Оберіть одне. Дотримуйтеся його.
Рішення 1 (рекомендовано для змішаних стеків): Nginx володіє 80/443, Apache переміщується на 127.0.0.1:8080
Це найпоширеніший продакшн-шаблон, коли вам потрібен Apache з причин аплікації (legacy rewrites, модулі, .htaccess),
але ви віддаєте перевагу Nginx як крайовому проксі.
Крок A: Зняти Apache з 80/443
Відредагуйте /etc/apache2/ports.conf, щоб прив’язати Apache лише до loopback:
cr0x@server:~$ sudo sed -n '1,120p' /etc/apache2/ports.conf
# If you just change the port or add more ports here, you will likely also
# have to change the VirtualHost statement in
# /etc/apache2/sites-enabled/000-default.conf
Listen 80
Listen 443
Замініть на:
cr0x@server:~$ sudo bash -lc 'cat > /etc/apache2/ports.conf <<EOF
Listen 127.0.0.1:8080
<IfModule ssl_module>
Listen 127.0.0.1:8443
</IfModule>
<IfModule mod_gnutls.c>
Listen 127.0.0.1:8443
</IfModule>
EOF'
Чому loopback? Бо це перешкоджає прямому доступу до Apache з мережі.
Це усуває категорію багів «обійти проксі» і несподіваних проблем безпеки.
Крок B: Оновити Apache vhost-и під новий порт
У ваших включених сайтах може бути <VirtualHost *:80>. Замініть їх на 127.0.0.1:8080 або *:8080 за вподобанням.
Мені подобається явний loopback, щоб запобігти помилкам.
cr0x@server:~$ sudo grep -R --line-number "<VirtualHost" /etc/apache2/sites-enabled
/etc/apache2/sites-enabled/000-default.conf:1:<VirtualHost *:80>
cr0x@server:~$ sudo sed -i 's/<VirtualHost \*:80>/<VirtualHost 127.0.0.1:8080>/' /etc/apache2/sites-enabled/000-default.conf
Якщо ви використовували TLS vhost-и Apache (*:443), вирішіть, чи потрібні вони вам далі.
У типовому дизайні «Nginx термінує TLS» вимкніть TLS-vhost-и Apache і тримайте Apache внутрішнім HTTP.
Крок C: Вимкнути Apache SSL і слухачів порту 443, якщо Nginx термінує TLS
cr0x@server:~$ sudo a2dismod ssl
Module ssl disabled.
To activate the new configuration, you need to run:
systemctl restart apache2
Значення: Apache не завантажуватиме mod_ssl. Якщо вам потрібний внутрішній TLS — залиште і використовуйте 8443 loopback, але будьте послідовні.
Рішення: Більшість команд: термінація TLS на Nginx, звичайний HTTP між Nginx і Apache на 127.0.0.1:8080.
Крок D: Перезапустити Apache, потім переконатися, що він більше не прив’язує 80/443
cr0x@server:~$ sudo systemctl restart apache2
cr0x@server:~$ sudo ss -ltnp '( sport = :80 or sport = :443 or sport = :8080 )'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 127.0.0.1:8080 0.0.0.0:* users:(("apache2",pid=2012,fd=3))
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1550,fd=6))
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=1550,fd=7))
Значення: Ідеальний розподіл. Nginx — край. Apache — внутрішній.
Рішення: Тепер налаштуйте Nginx upstream на 127.0.0.1:8080.
Крок E: Правильно налаштувати зворотне проксі Nginx
У вашому Nginx-сайті блок проксі має виглядати так (важливі частини — порт бекенду і заголовки):
cr0x@server:~$ sudo bash -lc 'cat > /etc/nginx/sites-available/app.conf <<EOF
server {
listen 80;
listen [::]:80;
server_name app.internal;
return 301 https://\$host\$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name app.internal;
ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host \$host;
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/app.conf /etc/nginx/sites-enabled/app.conf
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
cr0x@server:~$ sudo systemctl reload nginx
Рішення 2: Apache володіє 80/443, прибрати Nginx з рівня
Це правильний крок, коли Nginx був встановлений «бо хтось сказав, що він швидший»
і потім ніхто ним насправді не користувався навмисно.
Крок A: Зупинити й відключити Nginx
cr0x@server:~$ sudo systemctl stop nginx
cr0x@server:~$ sudo systemctl disable --now nginx
Removed "/etc/systemd/system/multi-user.target.wants/nginx.service".
Значення: Nginx не буде запускатися при завантаженні або за замовчуванням.
Рішення: Якщо вам Nginx насправді не потрібен — тримайте його вимкненим. Менше компонентів — менше проблем о 3-й ночі.
Крок B: Підтвердити, що Apache прив’язується до публічних портів
cr0x@server:~$ sudo ss -ltnp '( sport = :80 or sport = :443 )'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("apache2",pid=1421,fd=4))
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("apache2",pid=1421,fd=5))
Значення: Apache тепер край.
Рішення: Приберіть будь-які зворотні проксі-конфіги, які були потрібні лише через Nginx, і перевірте редиректи/серти в Apache.
Як вбити проксі-цикли та цикли редиректів назавжди
Конфлікти портів голосні. Проксі-цикли підступні.
Система може бути «вверх», поки користувачі кружляють по колу або трафік зжирає CPU безглуздим рекурсивним обміном.
Проксі-цикли: механіка
Проксі-цикл виникає, коли проксі пересилає запит на адресу, що маршрутизується назад до самого проксі.
На одному хості це зазвичай localhost або 127.0.0.1 на тому самому порту, на якому слухає проксі.
Поганий приклад конфігурації Nginx:
listen 80; і proxy_pass http://localhost;.
Це каже: «Прийняти запит на 80, відправити запит на 80.» Єдине, чого бракує — листа з вибаченням.
Цикли редиректів: звичайний винуватець
Цикли редиректів найчастіше — це «плутанина схем». Ваш бекенд бачить HTTP (бо проксі спілкується з ним по HTTP),
але користувачі на HTTPS (бо проксі термінує TLS). Якщо бекенд примушує HTTPS редиректом,
він шле редирект на HTTPS. Проксі приймає цей запит і знову шле HTTP бекенду. Повторюється, поки браузер не здамося.
Виправте плутанину схем: робіть три речі, не одну
- Надсилайте
X-Forwarded-Protoвід проксі. Nginx:proxy_set_header X-Forwarded-Proto $scheme; - Нехай бекенд довіряє заголовкам проксі. Багато фреймворків вимагають явного налаштування «trusted proxies».
- Переконайтеся, що редиректи відбуваються в одному місці. Зазвичай на крайовому проксі, бо він знає, якою схемою користувався клієнт.
Тестуйте цикли навмисно через curl
Не тестуйте через оновлення браузера і прищурюючись. Використовуйте curl -I -L і обмежуйте редиректи.
Ви хочете побачити малий, адекватний ланцюжок: можливо один 301 з HTTP→HTTPS, потім 200.
cr0x@server:~$ curl -I -L --max-redirs 5 http://app.internal/ | sed -n '1,20p'
HTTP/1.1 301 Moved Permanently
Server: nginx
Location: https://app.internal/
HTTP/2 200
server: nginx
Значення: Один редирект, потім успіх. Це чисто.
Рішення: Якщо бачите чергування http/https у Location — виправте forwarding proto і логіку редиректів бекенду.
Другий короткий жарт (і на цьому з жартами все): цикл редиректів — як корпоративна нарада — всі «вирівнюються», і нічого не відправляється.
Три корпоративні історії (бо продакшн пам’ятає)
1) Інцидент через хибне припущення: «localhost означає бекенд»
Середня компанія тримала один Ubuntu-хост для внутрішнього інструмента. Вони додали Nginx «тільки для TLS» перед Apache.
Інженер скопіював фрагмент проксі зі старої вікі: proxy_pass http://localhost;. Здавалося безпечним.
Припущення: localhost рівний Apache.
Але Apache все ще слухав порт 80, як і Nginx. Після послідовності перезапусків Nginx опинився слухачем на 80.
Тепер localhost:80 означав сам Nginx. Цикл не був одразу очевидним, бо health check-и були наївні:
вони опитували / і приймали 301.
Під навантаженням цикл проявився як піки CPU і накопичення з’єднань. Воркери Nginx не були «повільні»; вони просто говорили самі з собою.
Логи Apache здавалися спокійними, бо Apache нічого не робив. On-call побачив 502 і почав масштабувати VM,
що виправило лише рахунок за хмару, але не проблему.
Виправлення зайняло п’ять хвилин, коли хтось запустив ss -ltnp і простежив редиректи через curl.
Урок не в «не використовуйте localhost». Він у тому: ніколи не проксити на той же порт, на якому ви слухаєте, і завжди вказуйте порти бекенду явно.
2) Оптимізація, що дала зворотний ефект: «Давайте примусово HTTPS скрізь»
Інша організація тримала Nginx на краю і Apache позаду. Вони вирішили стандартизувати HTTPS і додали правила редиректу в обох шарах.
Nginx редиректував HTTP на HTTPS. Apache також редиректував HTTP на HTTPS «на всякий випадок».
Ніхто не зафіксував джерело правди.
Це працювало в стенді, де все було plain HTTP. У продакшні TLS термінувався на Nginx.
Nginx спілкувався з Apache по HTTP. Apache бачив HTTP-запит і робив свій «корисний» редирект на HTTPS.
Він не знав оригінальної схеми клієнта. Він лише бачив те, що отримав.
URL-редирект, який згенерував Apache, повертав клієнта назад до Nginx, який знову відправляв HTTP в Apache.
Безкінечний цикл. Моніторинг показував зростання 301 відповідей, що виглядало «здоровим», якщо дивитись лише на коди статусу.
Користувачі були заблоковані, і служба підтримки отримала класичні тікети «too many redirects».
Відкат був простим: прибрали HTTPS-примус з боку Apache і поклалися на Nginx для редиректів.
Потім додали X-Forwarded-Proto і налаштували аплікацію довіряти йому,
щоб URL-генерація була коректною. «Оптимізація» виявилася подвійним примусом HTTPS.
3) Нудна, але правильна практика, що врятувала день: прив’язувати бекенд до loopback і документувати порти
Команда поруч із фінансами славилася тим, що рухалась повільно. Вони також мали менше інцидентів — набридливо.
Їхнє правило: edge-сервіси прив’язують публічні порти; бекенди прив’язують до 127.0.0.1 або приватної VLAN. Завжди.
Вони також тримали невеликий /etc/services.d/README, де було описано, який демон на яких портах.
Під час оновлення ОС залежність пакета підтягнула Apache.
Він автоматично включив дефолтний сайт. На багатьох системах це могло б стати аутейджем: новий демон, нові порти, несподівані слухачі.
У них Apache не міг прив’язатися до 80/443, бо Nginx уже їх займав, і Apache був налаштований (за політикою) прив’язуватись лише до loopback.
Результатом було просте попередження в journald і нуль впливу на клієнтів.
On-call розібрався зранку з кавою, а не з адреналіном.
Мораль нудно болюча: нудні політики прив’язки портів запобігають цікавим інцидентам. Цікавими інцидентами аудиторів не вразиш.
Поширені помилки: симптом → корінь → виправлення
1) Nginx не стартує: «Address already in use»
Симптом: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
Корінь: Apache (або інший сервіс) вже слухає 80/443 (можливо лише IPv6).
Виправлення: Визначте власника порту; перемістіть інший демон на 127.0.0.1:8080 або вимкніть його. Перевірте через ss -ltnp.
2) Apache не стартує: помилки AH00072 при bind
Симптом: Логи Apache показують AH00072 і сервіс не стартує.
Корінь: Nginx вже слухає порти, на які налаштований Apache.
Виправлення: Та сама дисципліна: один край. Перебазуйте Apache в /etc/apache2/ports.conf і оновіть vhost-и, або вимкніть Nginx.
3) Браузер: ERR_TOO_MANY_REDIRECTS
Симптом: Браузер каже про цикл редиректів; curl показує чергування http/https у Location.
Корінь: Подвійне примусове HTTPS, або бекенд не враховує X-Forwarded-Proto, або неправильний базовий URL аплікації.
Виправлення: Зробіть редиректи в одному шарі (зазвичай край). Налаштуйте заголовки пересилання. Налаштуйте бекенд/додаток довіряти проксі і генерувати правильну схему в URL.
4) Nginx 502 Bad Gateway
Симптом: Nginx повертає 502; лог помилок згадує connect() failed або upstream prematurely closed connection.
Корінь: Бекенд впав, неправильний порт, невідповідний протокол (HTTP↔HTTPS), або прив’язка лише на IPv6/IPv4 mismatch.
Виправлення: Тестуйте бекенд напряму через curl; перевірте сокети; скоригуйте proxy_pass і переконайтеся, що бекенд прив’язаний на 127.0.0.1, якщо він локальний.
5) «Працює на localhost, але не зовні»
Симптом: Локальний curl працює; віддалений — ні або попадає в дефолтний сайт.
Корінь: Сервіс прив’язаний лише до loopback або лише до IPv6; правила брандмауера; дефолтний vhost ловить невідповідний Host.
Виправлення: Перевірте прив’язки через ss; перевірте маршрутизацію хоста через curl -H "Host: ..."; налаштуйте server_name/ServerName і default_server.
6) Подається не той сайт (сторінка за замовчуванням)
Симптом: Ви отримуєте «Apache2 Ubuntu Default Page» або вітальну сторінку Nginx.
Корінь: Увімкнений дефолтний сайт, який має вищий пріоритет; невідповідний server_name; SNI-мismatch по TLS.
Виправлення: Вимкніть дефолтні сайти, забезпечте правильний порядок vhost-ів, вкажіть явний server_name/ServerName, перевірте SNI через curl і дампи vhost-ів.
Контрольні списки / поетапний план
Контрольний список A: «Хочу просто виправити» (Nginx край, Apache бекенд)
- Запустіть
ss -ltnpдля 80/443 і запишіть власників. - Перемістіть Apache на
127.0.0.1:8080в/etc/apache2/ports.conf. - Оновіть Apache vhost-и з
*:80на127.0.0.1:8080. - Вимкніть TLS-vhost-и Apache, якщо вам не потрібен внутрішній TLS.
- Перезапустіть Apache, підтвердіть, що він слухає лише 8080.
- Налаштуйте Nginx слухати 80/443 і проксити на
http://127.0.0.1:8080. - Переконайтеся, що Nginx встановлює
Host,X-Forwarded-For,X-Forwarded-Proto. - Тестуйте бекенд напряму (curl до 127.0.0.1:8080), потім край (curl до 127.0.0.1:80/443).
- Слідкуйте за редиректами через curl і обмежуйте їх. Максимум один редирект для HTTP→HTTPS.
- Тільки після цього впускайте реальний трафік.
Контрольний список B: «Хочу один вебсервер» (тільки Apache)
- Зупиніть і вимкніть Nginx.
- Переконайтеся, що Apache слухає 80/443 і подає правильний vhost.
- Приберіть редирект-логіку ери Nginx з Apache, якщо вона дублює функціонал.
- Перевірте TLS-серти і вибір vhost-ів.
- Переконайтеся, що немає залишкових правил брандмауера, що очікують поведінки Nginx.
Контрольний список C: Регіграційні тести після будь-якої зміни
- Порти: перевірте слухачі на 80/443/8080 через
ss. - Синтаксис конфігів:
nginx -t,apachectl configtest. - Тест бекенду напряму: curl до порту бекенду.
- Тест краю: curl до краю HTTP і HTTPS, слідуйте за редиректами, підтвердіть фінальний статус.
- Маршрутизація по хосту: curl з заголовком Host, щоб перевірити матчинг vhost.
- Логи: перевірте Nginx error log і Apache error log на предмет негайних регресій.
cr0x@server:~$ sudo apachectl configtest
Syntax OK
Значення: Конфіг Apache парситься. Це не означає, що ваші vhost-и коректні, але зменшує ймовірність «reload roulette».
FAQ (питання, які ви поставите о 2-й ночі)
1) Чи можуть Apache і Nginx працювати на одному сервері?
Можуть. Вони просто не можуть обидва слухати один і той же IP:port. Виберіть край (80/443) і перемістіть інший на loopback або інший порт.
2) Чому Nginx каже, що порт 80 зайнятий, коли я не бачу Apache на IPv4?
Бо щось може слухати на IPv6 ([::]:80). ss -ltnp покаже обидва стеку. Виправте, змінивши слухача або вимкнувши інший сервіс.
3) Куди проксити — до “localhost” чи до “127.0.0.1”?
Використовуйте 127.0.0.1:8080 (явний порт) або Unix-сокет. «localhost» без порту створює неоднозначність і цикли.
4) Чому я бачу дефолтну сторінку Apache/Nginx замість свого додатку?
Ваш запит потрапляє в дефолтний vhost (невідповідний server_name/ServerName, порядок vhost-ів, або невідповідний Host). Вимкніть дефолтні сайти і явно вкажіть хости.
5) Що викликає HTTP↔HTTPS цикли редиректів за зворотного проксі?
Зазвичай несумісність схем. Бекенд бачить HTTP і примушує HTTPS, але проксі вже термінував HTTPS і пересилає HTTP внутрішньо.
Виправте через встановлення X-Forwarded-Proto і налаштування бекенду довіряти йому; тримайте редиректи в одному шарі.
6) Якщо Nginx термінує TLS, чи повинен Apache все ще слухати 443?
Зазвичай ні. Тримайте Apache внутрішнім HTTP лише на 127.0.0.1:8080. Внутрішній TLS — валідний вибір, але він має бути свідомим і послідовним.
7) Як підтвердити, що проксі дійсно надсилає заголовок Host?
Перевірте конфіг Nginx на наявність proxy_set_header Host $host;. Потім перевірте поведінку бекенду, роблячи curl через Nginx з різними заголовками Host.
cr0x@server:~$ curl -sS -H "Host: app.internal" http://127.0.0.1/ -o /dev/null -D- | head
HTTP/1.1 301 Moved Permanently
Server: nginx
Location: https://app.internal/
8) Який найчистіший спосіб гарантувати, що Apache недоступний напряму?
Прив’яжіть Apache лише до 127.0.0.1. Не покладайтеся на правила брандмауера як на перший рівень захисту; робіть сокет недосяжним з мережі.
9) Чи змінює цю історію socket-активація systemd?
Може. Socket activation може тримати порти відкритими незалежно від процесу сервісу.
В Apache/Nginx світі на Ubuntu зазвичай бачите звичайні сервіси, але перевіряйте через ss і systemctl list-sockets.
cr0x@server:~$ systemctl list-sockets --no-pager | head
LISTEN UNIT ACTIVATES
/dev/log systemd-journald.socket systemd-journald.service
...
10) Я виправив порти, але все ще бачу 502. Що робити далі?
Підтвердіть досяжність бекенду і протокол. Зробіть curl бекенду напряму. Потім перевірте error лог Nginx на upstream connect errors.
502 зазвичай означає «неможливо дістатися upstream» або «upstream відповів чимось неочікуваним».
cr0x@server:~$ sudo tail -n 20 /var/log/nginx/error.log
2025/12/30 09:24:08 [error] 1552#1552: *12 connect() failed (111: Connection refused) while connecting to upstream, client: 10.0.2.15, server: app.internal, request: "GET / HTTP/2.0", upstream: "http://127.0.0.1:8080/", host: "app.internal"
Висновок: наступні кроки, щоб не потрапляти сюди знову
Виправлення плутанини Apache vs Nginx на Ubuntu 24.04 — не справа хитрості. Це питання власності.
Визначте, хто володіє 80/443. Прив’яжіть бекенд до loopback. Зробіть цілі проксі-цілі явними. Потім тестуйте через curl серйозно.
Практичні наступні кроки:
- Запишіть вашу бажану архітектуру (Nginx edge → Apache 8080, або тільки Apache, або тільки Nginx).
- Застосуйте власність портів у конфігурації, а не в племінній пам’яті команди.
- Додайте простий smoke-тест після змін: перевірка сокетів
ss+ ланцюжок редиректів через curl + прямий curl до бекенду. - Вимкніть дефолтні сайти, які ви не використовуєте. За замовчуванням вони дружні, поки не стануть небажаними.
- Тримайте редиректи в одному шарі і будьте явними щодо forwarded-заголовків.
Якщо ви зробите це, випадок №34 залишиться вирішеним. Назавжди. Найкращий інцидент — той, якого більше не буває.