16 KiB
arch-08. Контракт корневого nginx
Канонический контракт nginx для всех application VM.
Compose-топология, сети и published ports —arch-03-docker-compose-blueprint.md.
Реализация на конкретной VM —module-03-nginx-vm1.mdиmodule-03-nginx-vm2.md.
Telemetry access log —arch-07-observability.md. Env —arch-04-settings-and-content.md. Host security —arch-06-service-hosting-security.md.
Назначение
Документ фиксирует то, что должно совпасть между ВМ1 и ВМ2: независимый nginx на каждой машине, TLS/ACME, request id, forwarded headers, запрет internal paths, JSON access log, hardening Compose и failure/reload policy.
Routing matrix, upstreams, SPA, WebSocket, CSP и allow-list конкретной машины здесь не детализируются и не кастомизируются так, чтобы сломать этот контракт.
1. Обязательная топология
В production-like контуре каждая VM имеет собственный nginx в своём root Compose и собственный deployment lifecycle.
- публичный трафик одной VM не проходит через nginx другой;
- отказ или deploy одной VM не обязан прерывать ingress другой;
- контейнеры приложений не публикуют host ports; на каждой VM наружу смотрит только её nginx;
- если перед конкретной VM есть WAF/LB, trusted proxy CIDR задаются отдельно (N1).
Между public route ВМ1 и ВМ2 нет reverse-proxy chain и нет SPA/API fallback на другую VM.
2. Общие правила routing
Порядок location критичен. Exact locations объявляются до prefix.
На обоих public hosts:
/internal/,/_internal/, Redis/OTLP/admin/status/config files возвращают404;- fallback на другую VM или чужой SPA запрещён;
- query не участвует в exact location matching;
- адрес из недоверенного
X-Forwarded-Forне используется для allow-list; - при внешнем LB сначала настраиваются его trusted CIDR и нормализация real IP.
Upstream failures не перенаправляются на другой сервис и не попадают в SPA. API-подобные locations возвращают 502/504 с безопасным nginx body и X-Request-ID; custom JSON error допустим только там, где профильная спецификация это разрешает, и не имитирует backend domain code.
Проверка prompt injection / malware не выполняется в nginx: это message-safety через api-backend (arch-02).
3. HTTP/HTTPS и TLS
- public host каждой VM на
:80обслуживает только ACME challenge и308 https://$host$request_uri, кроме явно зафиксированных исключений профильной спецификации; - выделенный API host, если появится, не имеет listener
:80; :443 ssl http2, TLS 1.2/1.3, современные cipher suites, session tickets по ops policy;- сертификат доверенного CA, private key read-only и недоступен приложению;
- OCSP stapling при поддержке CA/DNS;
- HSTS включается только после успешной проверки HTTPS:
max-ageиз env, затем по решению opsincludeSubDomains; preload не включать автоматически; - OIDC redirects, cookies и external URLs всегда HTTPS;
Servertokens скрыты; upstreamX-Powered-Byудаляется.
ВМ2 дополнительно слушает private 8443 с сертификатом internal CA — детали в спецификации ВМ2.
4. ACME lifecycle
Выбран webroot Certbot/ACME client с общими named volumes:
nginx-certs -> /etc/letsencrypt (rw у certbot, ro у nginx)
nginx-acme-webroot -> /var/www/certbot
Каждая VM выпускает свой сертификат на свой public host. Секреты и volumes двух projects не общие.
Bootstrap:
- DNS указывает на VM; 80/443 разрешены.
- Запустить временный HTTP config с
/.well-known/acme-challenge/. - Выпустить certificate без остановки nginx.
- Проверить
nginx -t, атомарно активировать TLS config, reload.
Renew container/host timer выполняет certbot renew минимум дважды в сутки;
после фактического renewal проверяет рабочую конфигурацию командой
docker compose exec -T nginx nginx -t -c /tmp/nginx.conf и отправляет
master-процессу docker compose kill -s HUP nginx. Bare-команды nginx -t и
nginx -s reload запрещены: контейнер read-only, а рабочие config/PID находятся
в /tmp. При ошибке остаётся старый worker/config/cert и срабатывает alert.
Успешный deploy/renew hook возвращает 0 с пустым stderr: вывод успешного
nginx -t и progress signal command перехватывается или подавляется; при
ошибке сохранённая диагностика полностью печатается в stderr. Любой stderr на
success path считается дефектом интеграции с Certbot.
Контролируются expiry days и последняя успешная попытка. Staging CA используется
в rehearsal, чтобы не исчерпать лимиты.
Non-root nginx не монтирует root-only дерево Let's Encrypt целиком: только необходимые cert/key files по arch-03/arch-06.
5. Request ID и forwarded headers
На edge формируется trusted request id. Базовый nginx не генерирует UUID штатной переменной, поэтому используется njs/Lua либо модуль request-id, включённый в закреплённый image. Входящий X-Request-ID принимается только если соответствует UUID/ULID и длине; иначе генерируется новый.
Upstream получает:
Host: original host
X-Real-IP: trusted real client IP
X-Forwarded-For: normalized proxy chain
X-Forwarded-Proto: https
X-Forwarded-Host: original host
X-Forwarded-Port: 443
X-Request-ID: edge request id
traceparent: входной валидный либо новый согласно OTEL integration
Ответ всегда содержит X-Request-ID. Клиентские X-Forwarded-* от недоверенного адреса перезаписываются. Authorization, Cookie, query string и body не попадают в access log.
Private listener доверяет forwarded headers только от allow-listed private caller; public listener применяет собственную trusted proxy policy.
6. Timeouts и body limits — общие ориентиры
| Группа | connect/send/read | Владелец |
|---|---|---|
| обычный API | 3s / 30s / 30s | ВМ1 |
| auth | 3s / 30s / 60s | ВМ1 |
| Bitrix local callback | 3s / 30s / 60s | ВМ1 |
| Direct SMS callback | 3s / 30s / 60s | ВМ1 |
| WS | 3s / 30s / 75s+ | ВМ1 |
| message POST | 3s / 30s / MESSAGE_SAFETY_TASK_POLL_MAX_SEC + 30s минимум |
ВМ1 |
| CRM webhook | 3s / 30s / 60s | ВМ2 |
| private Safety | по контракту Safety, не короче caller budget | ВМ2 |
Значение timeout генерируется из env template до startup; nginx не выполняет арифметику env runtime.
client_max_body_size global 8m по arch-04, но JSON locations получают более строгие limits, где возможно. Байты вложения не проходят через nginx/API: клиент PUT напрямую в S3. Buffering request допустим для малого JSON; для callback устанавливается bounded temp storage. Header count/size ограничены.
7. Edge rate limits — общие правила
limit_req_zone использует binary remote address. Ответ превышения — 429, Retry-After (статический/вычисляемый для зоны) и request id. Edge не реализует user-level бизнес-лимит; это делает API/Redis.
Карта зон принадлежит спецификации VM. Новые зоны добавляются только там, затем при необходимости сюда как реестр имён.
8. Health
- внутренний
GET /nginx-health/liveвозвращает static 200 и доступен Docker healthcheck; - внешний health публикуется только если нужен мониторингу, с allow-list (N3);
- nginx health не утверждает готовность upstream;
- Docker healthcheck использует только binary, гарантированно присутствующий
и проверенный внутри exact pinned nginx digest;
wget/curlзапрещены, если их наличие не подтверждено image inventory; - upstream
/health/readyне агрегируется публично без решения ops.
9. Логи и OTEL correlation
JSON access log: timestamp, request_id, trace_id (если извлечён), remote IP/hashed policy, host, method, route class, normalized URI на основе $uri без $request_uri/$args, status, bytes, duration, upstream addr/status/time, cache status, TLS protocol/cipher, user agent при принятой retention.
Не логируются Authorization, cookies, request/response body, OTP, tokens, query token, presigned query, PII. Error log структурирован настолько, насколько позволяет nginx; debug выключен production.
Nginx передаёт W3C trace context; native OTEL module допустим при закреплённой версии (arch-07 O-TBD5). Если edge создаёт span, request id остаётся отдельным correlation key. Логи идут stdout/stderr; Docker/collector отвечает за доставку и rotation.
Parser и service.name=nginx — arch-07 §8.4.
10. Layout конфигурации
Общий каркас репозитория nginx:
nginx/
Dockerfile
docker-compose.yml
nginx.conf
templates/
00-maps.conf.template
10-upstreams.conf.template
20-http-redirect.conf.template
30-https-site.conf.template
snippets/
proxy-common.conf
security-headers.conf
tls.conf
rate-limits.conf
njs/request_id.js
scripts/{render,validate,reload-after-renew}.sh
tests/
Профильная спецификация добавляет только нужные snippets (websocket.conf, private server block, webhook allow-list). Image и modules pin по digest/version. Render использует allow-list env и fail-fast для пустых host/cert/upstream/timeouts. Секреты в rendered config не требуются.
11. Docker Compose
nginx подключён к public и backend (или эквивалентным сетям своей VM). Публикация host ports разрешена только nginx. Filesystem read-only, tmpfs для cache/run/temp//etc/nginx/conf.d по arch-03, non-root где позволяет bind ports/capabilities. Cert volumes read-only для nginx. ACME client имеет только необходимые volumes/network.
Non-root nginx получает writable tmpfs только для /etc/nginx/conf.d, /var/cache/nginx, /var/run и /tmp; tmpfs задаёт явные UID/GID/mode. Основной nginx.conf подключает конкретный rendered include, не неограниченный wildcard, который позволил бы обойти nginx -t.
depends_on health не заменяет retry/readiness. После healthy upstream
обязателен config test с production service DNS names и reload/recreate nginx.
После recreate upstream повторяется reload policy либо используется явно
протестированный dynamic resolver. Nginx может временно отдавать bounded 502,
но не считается ready до этой post-ready проверки.
Published Docker ports сопоставляются original host destination через conntrack/DOCKER-USER по arch-06.
12. Failure behavior
- upstream down: bounded 502/504, без SPA fallback и без переноса на другую VM;
- cert renewal failed: текущий cert продолжает работу, alert до expiry;
- invalid new config: reload отменяется, старые workers остаются;
- disk/cache full: public cache bypass/evict, requests продолжаются где безопасно;
- DNS upstream changed: resolver/restart policy восстанавливает адрес;
- overload: 429/503 на edge, bounded queues; не накапливать неограниченные connections.
13. Общий Definition of Done
- на каждой VM ровно один nginx; независимые public
80/443и собственные сертификаты; - TLS/ACME bootstrap, renewal и safe reload испытаны;
- internal endpoints/ports извне недоступны;
- request id и trusted forwarded headers корректны;
- JSON logs коррелируют request/trace и не содержат секретов;
- health/synthetic checks и failure tests проходят;
- image non-root/read-only насколько возможно, versions pinned;
- runbooks для cert, reload, upstream outage и rollback готовы.
Профильный DoD VM дополняет routing matrix, allow-list и listener этой машины.
14. Решения, допущения и TBD
Решения: независимые public nginx ВМ1/ВМ2; njs/module для UUID; public internal paths → 404; webroot ACME; CRM webhook приходит прямо на ВМ2; private 8443 ВМ2 только server-to-server.
Допущения: ВМ1 и ВМ2 используют разные public hosts и сертификаты; upstream service names стабильны внутри каждого Compose; S3 CORS настраивается отдельно.
TBD:
- N1: доверенные WAF CIDR — по VM.
- N2: production cipher suite/OCSP.
- N3: нужен ли публичный health.
- N6: финальные burst/connection limits — по зонам VM.
- N7: certbot vs другой ACME client после ops review.
N4 (CSP Expo) и N5 (Bitrix frame ancestors) — спецификация ВМ1.
15. Ссылки
- Compose/сети:
arch-03-docker-compose-blueprint.md. - ВМ1:
module-03-nginx-vm1.md. - ВМ2:
module-03-nginx-vm2.md.