Хаос из 23 поддоменов: как NPM, LDAP и CrowdSec навели порядок за один квартал
Март 2026. Пятница, полшестого вечера. Звонит новый разработчик: «А как мне зайти на staging? У меня три пароля не подходят, а на четвёртом сертификат просрочен, браузер ругается красным экраном». Я открываю список поддоменов компании — их 23. У каждого свой конфиг nginx, свой сертификат, свой способ авторизации. У кого-то basic auth в .htpasswd, у кого-то вообще ничего.
В этот момент стало понятно, что мы три года копили технический долг, который называется красиво — «зоопарк веб-сервисов», а по факту это просто бардак, который однажды укусит.
Контекст: как дошли до жизни такой
Компания — 60 человек, продуктовая разработка плюс внутренние сервисы. IT-отдел — я и ещё один инженер. Стек рос органически: сначала был один сайт на одном VPS, потом появился staging, потом CI/CD с превью-окружениями для каждой ветки, потом Grafana, потом внутренний Wiki, потом панель для отдела продаж, потом ещё один VPS, потому что на первом кончилось место.
К марту 2026 у нас было:
- 4 физических/облачных сервера
- 23 поддомена с разными сервисами
- 3 разных способа получения SSL-сертификатов (ручной certbot, самоподписанные, и один вообще без HTTPS)
- 2 системы авторизации — своя для внутренних админок, отдельная для VPN
- 0 централизованного логирования попыток входа
Никто не проектировал эту систему специально. Она просто выросла, как сорняк — каждый новый сервис добавлялся по принципу «сделать быстро», а не «сделать правильно». И это работало. До определённого момента.
Проблема, которую наконец сформулировали
Собственник задал простой вопрос на планёрке: «Сколько времени уходит на добавление нового сотрудника в системы?» Мы посчитали — в среднем 40 минут ручной работы на человека: завести доступ на 4-5 внутренних сервисов, каждый со своим логином и паролем, некоторые вообще без учёта — просто shared credentials в мессенджере.
Второй звоночек — сертификат на CRM протух в понедельник утром, потому что certbot cron job тихо сломался ещё в январе, а никто не проверял. Три часа простоя, звонок от отдела продаж «а почему сайт не открывается».
Третий — сканирование портов. Логи одного из серверов показывали по 400-600 попыток брутфорса в сутки на SSH и на пару открытых веб-панелей. Fail2ban был настроен, но только на одном сервере из четырёх — руки не дошли до остальных.
Стало ясно: нужна не заплатка, а единая точка входа для всего HTTP-трафика. С нормальной авторизацией, автоматическими сертификатами и защитой от сканирования из коробки.
Что пробовали — и что не взлетело
Вариант 1: docker-compose + nginx + certbot вручную. Первая идея была «просто причесать существующее» — написать единые конфиги nginx, добавить cron для certbot на все сервера, унифицировать структуру. Потратили на это две недели. Получилось рабочее, но такое же хрупкое решение — просто более красиво оформленный тот же зоопарк. Каждое изменение домена всё ещё требовало правки конфига руками и перезапуска nginx. Отказались.
Вариант 2: Traefik. Смотрели в сторону Traefik как более современной альтернативы с автообнаружением контейнеров. Плюсы реальные — динамическая конфигурация без перезапуска, нативная интеграция с Docker. Но порог входа выше: YAML-лейблы на каждый контейнер, менее очевидный UI (де-факто его почти нет), и наша команда — два инженера, а не devops-отдел — не готова была тратить время на изучение с нуля ради теоретических преимуществ. Разбирали разницу отдельно, если кому интересны детали — короче, Traefik выигрывает в динамических Kubernetes-средах, а у нас обычные VPS с относительно статичным набором сервисов.
Вариант 3: оставить как есть и просто исправить сертификаты. Самый ленивый вариант — рассматривали серьёзно, потому что «работает — не трогай». Отказались после третьего инцидента за месяц. Аргумент «раньше работало» перестаёт работать в тот момент, когда простой стоит компании реальных денег и нервов отдела продаж.
Что сработало
Остановились на Nginx Proxy Manager (NPM) — надстройке над nginx с веб-панелью, встроенной интеграцией Let's Encrypt и поддержкой access-листов. Развернули на выделенном небольшом VPS, который стал единой точкой входа для всего HTTP/HTTPS трафика компании. Базовую установку и логику работы разбирали в отдельном гайде — здесь только то, что важно для решения бизнес-задачи.
Шаг 1. Консолидация доменов. Все 23 поддомена завели в NPM как proxy hosts, указывающие на внутренние адреса реальных сервисов (сервисы остались на своих серверах — просто трафик теперь идёт через единую точку). Заняло два дня, включая перепроверку каждого сервиса на предмет «а он вообще ещё нужен» — выяснилось, что 4 поддомена не используются больше года. Удалили.
Шаг 2. Сертификаты. NPM сам запрашивает и продлевает Let's Encrypt сертификаты через встроенный ACME-клиент — без cron-скриптов, без ручных команд. Настроили один раз на каждый домен, и с марта не было ни одной просрочки. Для доменов, где нужен wildcard-сертификат через DNS-провайдера (у нас два домена на разных регистраторах с разным API), пришлось повозиться отдельно — тут пригодился опыт автоматизации через acme.sh и DNS API, потому что встроенный клиент NPM не покрывает все кейсы с DNS-01 challenge для менее популярных регистраторов.
Шаг 3. Авторизация, включая LDAP. Это было главной болью — единый вход. У нас уже был Active Directory для внутренних нужд (файловые шары, почта). NPM поддерживает access lists с basic auth, но нативной LDAP-интеграции в самой панели нет — это частое заблуждение. Решили иначе: для сервисов, которым нужна SSO-авторизация через AD, поставили перед ними отдельный authentication proxy (oauth2-proxy с LDAP-бэкендом), а NPM уже проксирует трафик после него. Для менее критичных внутренних панелей хватило обычных access lists NPM с list паролей, синхронизированных вручную раз в квартал.
Не идеально — два уровня авторизации вместо одного, но у 12 сервисов теперь единый логин через корпоративный AD-аккаунт, что закрыло 80% случаев «зачем мне пять паролей». Заодно везде, где это было технически оправдано, добавили двухфакторку — тема отдельная, но если кто настраивает 2FA с нуля, вот разбор.
Шаг 4. Защита от сканирования — CrowdSec. Fail2ban у нас и раньше стоял местами, но это реактивная защита — банит по факту атаки. Перешли на CrowdSec перед NPM: он работает по принципу community-based блоклистов — если IP уже засветился как источник атак у других участников сети CrowdSec, его банят превентивно, ещё до того как он начал ломиться к нам. Сравнивали переход с Fail2Ban отдельно — если коротко, для одного сервера разница не критична, но когда точка входа единая для всей инфраструктуры, community-блоклисты реально снижают число попыток входа.
Провал, о котором не стыдно рассказать
Первую неделю после переноса на CrowdSec мы словили ложное срабатывание, заблокировавшее IP нашего же партнёрского агентства, которое интегрировано с одной из внутренних систем через API. Их сервер делал много быстрых запросов за короткое время — паттерн, похожий на брутфорс, — и CrowdSec забанил их автоматически на 4 часа. Партнёр написал разгневанное письмо, мы полдня разбирались, почему интеграция «внезапно перестала работать».
Решение — добавили whitelist для известных партнёрских IP до того, как включать агрессивные сценарии блокировки. Урок простой: любая система автоматической защиты требует whitelist для доверенных источников трафика ещё на этапе внедрения, а не после первого инцидента. Мы сделали это постфактум, и это стоило нам одного неприятного разговора.
Второй неожиданный момент — миграция домена CRM заняла на полдня больше, чем планировали, потому что старый DNS TTL был выставлен на 24 часа, а мы не проверили это заранее. Пришлось ждать, пока обновление разойдётся по резолверам, и часть команды весь день ловила старый сертификат вместо нового.
Что НЕ стали менять
Специально не трогали внутреннюю структуру Active Directory — она работала стабильно, и рефакторинг ради рефакторинга не входил в задачу. NPM и oauth2-proxy встроили поверх существующего AD, а не переделывали его.
Также не стали переносить всё на Kubernetes или мигрировать на Traefik, хотя соблазн был — «раз уж наводим порядок, давайте сразу по-взрослому». Отказались осознанно: у команды из двух человек нет ресурса поддерживать более сложную инфраструктуру, а текущая задача — снизить хаос, а не нарастить компетенцию в новой платформе ради красивой архитектурной схемы.
Legacy-сервис на PHP 5.6 (да, такой у нас есть, отдельная больная тема) оставили вне единой точки входа — вынесли на отдельный IP с минимальным доступом только по VPN, потому что тащить его под общую систему авторизации оказалось дороже, чем изолировать.
Итоговые цифры
МетрикаДо NPMПосле NPMИзменениеВремя выдачи доступа новому сотруднику40 мин8 мин-32 минПросрочки сертификатов за квартал30-3Попыток брутфорса в сутки (в среднем на все сервисы)~550~90-84%Точек ручного управления сертификатами4 сервера отдельно1 панель-3Часов простоя из-за IT-инцидентов за квартал91-8 чСтоимость решения (VPS + время внедрения)—~4 500 руб/мес + 3 недели работы—
Три недели работы — это не полный рабочий день, а вечера и куски рабочего времени между текущими задачами. Реальных денежных вложений — аренда небольшого VPS под NPM, около 4 500 рублей в месяц. Экономия не в деньгах напрямую, а в часах простоя и в нервах — которые тоже стоят денег, просто их сложнее посчитать в Excel.
Вывод
Главный вывод один: единая точка входа — это не про красоту архитектуры, а про то, сколько времени команда тратит на рутину, которая не должна требовать мысли. NPM закрыл 80% задачи почти без боли — сертификаты, проксирование, базовая защита. Оставшиеся 20% (нормальный SSO через LDAP, тонкая настройка CrowdSec) потребовали ручной работы и одного публичного факапа с партнёром.
Если бы делал заново — начал бы с whitelist для доверенных IP до включения защиты, а не после инцидента. И проверял бы TTL на DNS-записях перед любой миграцией домена, а не в процессе.
Открытый вопрос тем, кто уже прошёл этот путь: используете ли отдельный auth-proxy перед NPM для LDAP, или нашли более элегантное решение? Интересно сравнить подходы в комментариях.
Больше про инфраструктуру без воды — в блоге IT-Аптека и в Telegram-канале.