Короткий ответ

Nginx Proxy Manager (NPM) — nginx в Docker с UI. Если https://site.example даёт 502, NPM принял TLS и не достучался до Forward Hostname/IP:Port. На Ubuntu с origin 10.0.20.40 типично: сервис в другой docker-сети, localhost из контейнера NPM — это сам NPM, не хост, SSL «на origin» при http-бэкенде, порт не тот. Не ставьте второй системный nginx на 80 рядом без плана: конфликт порта.

Проверьте: контейнер приложения LISTEN, docker exec + wget с контейнера NPM на upstream, затем сертификат Proxy Host.

Симптомы и как отличить

  • UI NPM зелёный, сайт 502.
  • Прямой заход на http://10.0.20.40:8080 работает, через домен нет.
  • Ошибка выпуска сертификата в UI, сайт при этом HTTP открывается.
Где 200Вывод
только внутри docker networkNPM не в той сети
на хосте :8080, не из NPMForward = localhost ошибка
нигдесамо приложение
долго 504таймаут

Возможные причины

  1. NPM и приложение в разных user-defined сетях Compose.
  2. Forward 127.0.0.1:3000 из NPM.
  3. Scheme HTTPS к бэкенду, который слушает HTTP (или наоборот).
  4. Websockets Support выключен для WS-приложения.
  5. Let's Encrypt в NPM не видит 80 (порт занят хостовым nginx).
  6. Custom location ломает /.
  7. Неверный server_name / не тот Proxy Host.
  8. Firewall хоста режет forwarded порт, хотя Docker опубликовал его.

Диагностика

1. Порты хоста

sudo ss -tlnp | grep -E ':80|:443|:81|:8080|:3000'
docker ps --format 'table {{.Names}}\t{{.Ports}}\t{{.Networks}}'

NPM обычно публикует 80, 443, UI 81. Если хостовый nginx тоже :80 — кто реально принял пакет, смотрите PID в ss.

2. Сеть

docker network ls
docker inspect npm_app --format '{{json .NetworkSettings.Networks}}'
docker inspect site_app --format '{{json .NetworkSettings.Networks}}'

Имена контейнеров подставьте свои (npm, site.example стек). Общая сеть должна существовать.

3. С контейнера NPM к апстриму

docker exec npm wget -S -O /dev/null http://site_app:80/ 2>&1 | head
docker exec npm wget -S -O /dev/null http://172.18.0.4:80/ 2>&1 | head

Адрес возьмите из inspect, не из статьи. Connection refused — неверный порт/имя. No route — сети разные.

4. Логи NPM

docker logs npm --tail 80

Ищите connect() failed, SSL wrong version number (https на http-порт).

5. Сертификат

В UI: Certificate, expiry, chain. С хоста:

echo | openssl s_client -connect 10.0.20.40:443 -servername site.example 2>/dev/null | openssl x509 -noout -dates -issuer

Цепочка: fullchain. Выпуск LE: порт 80 должен приходить в NPM для HTTP-01.

Решение

Сценарий A. Общая docker-сеть

В Compose оба сервиса:

networks:
  proxy:
    external: true

Создайте docker network create proxy, подключите NPM и бэкенд. Forward Hostname = имя сервиса в этой сети (site_app), порт внутренний (80 в контейнере, не 8080 на хосте).

Не публикуйте бэкенд на 0.0.0.0 без нужды, если достаточно overlay/bridge.

Сценарий B. Бэкенд на хосте, NPM в Docker

Forward: IP хоста в docker bridge, часто 172.17.0.1:3000 (проверьте ip -4 addr show docker0), либо network_mode/extra_hosts. Не 127.0.0.1.

Приложение должно слушать 0.0.0.0:3000, не только 127.0.0.1, либо DNAT осознанный. Слушать 0.0.0.0 на хосте — риск; лучше firewall только с docker0.

Сценарий C. SSL к origin

Если бэкенд HTTP — в Proxy Host «Scheme: http», «SSL» только с клиента к NPM. «Force SSL» — редирект клиентов. «Block Common Exploits» не чинит 502.

Если бэкенд HTTPS с самоподписанным — включите «Skip certificate verification» только как временный костыль к своему origin, не в интернет.

Сценарий D. Конфликт с системным nginx

Остановите хостовый nginx или не публикуйте NPM на 80/443. Два фронта на одном порту не работают.

Сценарий E. Let's Encrypt в NPM

Про Brokering HTTP-01: A-запись на 10.0.20.40, 80/tcp в контейнер NPM. Если используете DNS-01 в NPM — токен API в UI, не в git.

Не параллельный certbot на хосте на те же имена без синхронизации файлов — два клиента ACME путают.

Сценарий F. WebSocket / таймауты

Включите Websockets в Proxy Host. Для длинных запросов увеличьте timeouts в Advanced точечно, понимая 504.

Сценарий G. Custom Nginx Config в UI и proxy_set_header Host

Если в Advanced написали proxy_set_header Host $host дважды или сломали proxy_pass без ;, NPM пишет ошибку в docker logs, хост может не примениться. Сначала уберите Custom Config, получите 200, потом добавляйте по строке. proxy_pass http://site_app:80 в custom перетирает UI Forward — не смешивайте.

Для PHP за NPM на хосте: NPM → 10.0.20.40:8080 (nginx/Apache хоста) → FPM. Тогда 502 может быть уже на втором хопе: смотрите access.log хостового nginx, не только NPM. Не открывайте FPM-сокет в Docker напрямую без нужды.

IPv6: Proxy Host с Forward IPv6, бэкенд только v4 — 502. Оставьте IPv4 имя сервиса.

После смены сети контейнера IP в UI устаревает: используйте DNS Docker, не зашитый 172.18.0.x.

Как проверить, что проблема устранена

curl -sI --resolve site.example:443:10.0.20.40 https://site.example/ | head
docker exec npm wget -q -O- http://site_app:80/ | head

С клиента — 200/302 приложения, не HTML 502 NPM. TLS валиден без -k. UI Health зелёный.

Если не помогло

  • Custom nginx snippet в Advanced с синтаксической ошибкой — смотрите logs, NPM мог не применить host.
  • HTTP/2 к старому бэкенду.
  • IPv6 в Proxy Host при бэкенде только v4.
  • Access list в NPM режет ваш IP — 403, не 502.

Профилактика

  • Одна входная точка 80/443.
  • Именованные сети, не IP контейнеров в UI (IP плывут).
  • Бэкап SQLite/Postgres NPM и certs.
  • Мониторинг 502 на домене.

FAQ

Можно ли NPM и системный nginx вместе?

Да, на разных IP или NPM только на 443 внутренним, но это усложняет ACME. Обычно выбирают одно.

Forward Port 80 или 8080?

Тот, внутри сети Docker, что EXPOSE/listen приложения. Хостовый mapped порт — если идёте через IP хоста.

UI на :81 в интернет

Закройте. SSH-туннель или VPN. 81 — админка.

cache assets ломает приложение

Выключите кэш в Proxy Host для динамического PHP.

Версия NPM и nginx внутри

Смотрите образ. Директивы как у nginx 1.24+ обычно совместимы; не копируйте модули хоста внутрь контейнера.