Алерт: сайт не открывается. Вы заходите на сервер: load average 0,3, памяти свободно больше половины, диск занят на 40%. Графики зелёные, а пользователи пишут, что всё лежит.
Так бывает, потому что обычный мониторинг следит за тремя‑четырьмя ресурсами: процессор, память, место на диске, сеть. У сервера их больше. Inode, файловые дескрипторы, точное время, работающий DNS, живой сертификат тоже могут кончиться, и тогда сервер выглядит здоровым, но не работает.
Ниже пять таких причин. Для каждой: как она выглядит, почему дашборд её не видит, какой командой её найти, как починить и что добавить в мониторинг. Все пять я разбирал, пока делал кейсы для тренажёра дежурств (о нём была первая статья). Самые короткие проверки были в прошлом чек‑листе, здесь — подробно.
1. Кончились inode
Как выглядит. В журнале приложения «No space left on device», загрузки и сессии падают с 500. При этом df -hпоказывает 70% свободного места. Перезапуск не помогает, а иногда делает хуже: сервис не может создать даже свой PID‑файл и не стартует.
Почему дашборд молчит. Место на диске меряют в байтах. Но каждый файл, даже пустой, занимает ещё и inode — запись в таблице файловой системы. На ext4 их число задаётся при создании файловой системы, по умолчанию один inode на 16 КБ, и потом не растёт.
На диске в 50 ГБ это около 3,2 млн inode. Если все они уйдут на файлы по 4 КБ (сессии, кэш, превью, очередь почты), займут они всего 12,5 ГБ. График места покажет занятые 25%, а новый файл уже не создать.
Как найти.
df -i # IUse% 100% — оно sudo du --inodes -x -d 2 /srv | sort -n | tail # где больше всего файлов sudo find /srv -xdev -type f -printf '%h\n' | sort | uniq -c | sort -n | tail
Последняя команда медленнее, зато называет конкретный каталог, а не ветку дерева.
Как починить. Первое желание — rm /var/lib/php/sessions/*. На миллионе файлов оно упадёт с «Argument list too long»: shell разворачивает звёздочку в миллион аргументов. Удаляйте через find, и только старое:
sudo find /srv/app/cache -type f -mmin +60 -delete
Затем выясните, почему файлы копились, иначе через неделю всё повторится. В кейсе тренажёра про inode релиз «ускорил ответы API» и заодно отключил очистку кэша. Если же inode не хватает честной нагрузке, добавить их в ext4 нельзя. Поможет отдельный раздел, созданный с mkfs.ext4 -i 4096, или XFS: там inode выделяются по мере надобности.
Что добавить в мониторинг. Процент свободных inode рядом с процентом свободного места. В Prometheus с node_exporter:
node_filesystem_files_free{fstype!~"tmpfs|overlay"} / node_filesystem_files{fstype!~"tmpfs|overlay"} < 0.10
2. Too many open files
Как выглядит. systemctl status говорит active (running), процессор почти свободен. Но новые клиенты не подключаются, health‑check висит, а в журнале Too many open files (у nginx — accept4() failed (24: Too many open files)). Те, кто подключился раньше, при этом работают, и это сильно путает.
Почему дашборд молчит. Каждое сетевое подключение, открытый файл и pipe — это файловый дескриптор, и у процесса есть лимит на их число. Процесс, упёршийся в лимит, не грузит сервер, а просто отказывает новым клиентам. CPU и память в этот момент даже снижаются.
Есть ещё ловушка. ulimit -n в вашей консоли показывает лимит вашей сессии, а не сервиса. systemd запускает сервисы мимо PAM, поэтому /etc/security/limits.conf на них не действует, и у сервиса остаётся лимит по умолчанию: 1024.
Как найти.
PID=$(systemctl show -p MainPID --value notify-gateway) grep 'open files' /proc/$PID/limits # лимит, с которым реально работает процесс sudo ls /proc/$PID/fd | wc -l # сколько дескрипторов занято sudo ls -l /proc/$PID/fd | awk '{print $NF}' | sed 's/\[.*//' | sort | uniq -c | sort -rn | head systemctl cat notify-gateway # откуда лимит: юнит и все его drop-in
Самая полезная здесь четвёртая строка: она говорит, чем заняты дескрипторы. 1500 строк socket: — честная нагрузка, лимит просто мал. Тысячи одинаковых /var/log/audit.log — утечка: код открывает файл и не закрывает.
Как починить. Если нагрузка честная, поднимите лимит там, где его читает systemd:
sudo systemctl edit notify-gateway # в редакторе: [Service] и LimitNOFILE=65536 sudo systemctl restart notify-gateway grep 'open files' /proc/$(systemctl show -p MainPID --value notify-gateway)/limits
Последняя команда обязательна. Drop‑in применяются по алфавиту имён, и файл политики безопасности с именем вроде zz-hardening.conf перебьёт вашу правку.
Если это утечка, подъём лимита лишь отложит отказ на несколько минут. Нужно найти и отключить то, что течёт, а рестарт оставить как временную меру.
В тренажёре у этого кейса три варианта, и все взяты из жизни. Drop‑in политики безопасности с LimitNOFILE=512. Лимит, который остался в limits.d после переезда с init‑скрипта на systemd. И аудит, который открывает лог на каждое сообщение.
Что добавить в мониторинг. Долю занятых дескрипторов. Клиентские библиотеки Prometheus для Go, Java и Python уже отдают process_open_fds и process_max_fds, достаточно алерта на их отношение выше 0,8. И смотрите на график: число дескрипторов растёт, а нагрузка нет — значит, течёт.
3. Часы ушли
Как выглядит. Всё подряд становится «ещё не действительным» или «уже истёкшим»:
вход не работает, в журнале
token used before issued: свежий JWT для сервера пришёл из будущего;запросы к чужому API падают с
certificate is not yet valid;apt updateпишетRelease file ... is not valid yet;не подходят коды двухфакторной аутентификации, а Kerberos отказывает при расхождении больше 5 минут.
Каждая ошибка толкает чинить своё: авторизацию, сертификаты, репозиторий. А общая причина одна.
Почему дашборд молчит. У часов нет «загрузки». Сервер, чьи часы отстали на двое суток, отвечает на пинг и отдаёт метрики. Их даже не исказит: Prometheus ставит на собранные метрики время своих часов, а не серверных. Графики выглядят идеально.
У меня это случалось на собственной тестовой виртуалке. Исходящий NTP (UDP 123) там закрыт, и после пауз виртуалки часы отставали то на два часа, то на четыре дня. Заметил я это не по мониторингу, а по упавшему docker build: apt отказался ставить пакеты.
Как найти.
timedatectl # System clock synchronized: no — первый звоночек chronyc tracking # System time: насколько часы расходятся с эталоном chronyc sources -v # с кем синхронизируемся; ^? — источник недоступен curl -sI https://ya.ru | grep -i '^date' # время по версии чужого сервера
Если вместо chrony работает systemd‑timesyncd, его состояние покажет timedatectl timesync-status.
Как починить. Зависит от того, почему часы ушли. Три самых частых причины стали вариантами кейса в тренажёре:
Сервер восстановили из снапшота, а там chrony выключен. Лечится
sudo systemctl enable --now chrony.В конфиге остался недоступный NTP‑сервер. chrony работает, но синхронизироваться ему не с чем. Поправьте строки
serverиpoolв/etc/chrony/chrony.confи проверьте, что UDP 123 наружу открыт.Виртуалку приостановили, и chrony догоняет плавно. Стандартная строка
makestep 1 3разрешает прыжок времени только в первые три обновления после старта. Потом часы догоняются не быстрее 83 мс в секунду, и на три часа отставания уйдут полтора суток. Сдвинуть часы сразу можно командойsudo chronyc makestep.
После большого сдвига часов стоит перезапустить сервисы, которые опираются на время: кэши со сроком жизни, планировщики, авторизацию.
Что добавить в мониторинг. Расхождение часов сервера с часами системы мониторинга и статус синхронизации. node_exporter отдаёт и то, и другое:
abs(time() - node_time_seconds) > 30 # часы уже разошлись node_timex_sync_status == 0 # и сами не сойдутся
4. DNS
У админов есть хокку: «Это не DNS. Не может быть, чтобы DNS. Это был DNS». Смешно ровно до первого инцидента.
Как выглядит. Сервис работает, но всё, что ходит к соседям по имени, сломано. В журнале Temporary failure in name resolution, Name or service not known или Connection refused на незнакомый адрес. Команда соседнего сервиса отвечает, что у них всё работает, и они правы.
Бывает и так, что запросы не падают, а тормозят ровно на 5 или 10 секунд. Такие круглые числа почти всегда означают таймаут резолвера: glibc по умолчанию ждёт ответа DNS 5 секунд и пытается дважды.
Почему дашборд молчит. Процесс, который ждёт ответа DNS, спит: CPU ноль, память ровная. Его /healthпроверяет только себя и отвечает 200. Мониторинг соседнего сервиса тоже зелёный: сломан не он, а путь к нему с одного конкретного сервера.
Путь имени до адреса длиннее, чем кажется, и сломаться может любой шаг:
/etc/nsswitch.confрешает, где и в каком порядке искать. После скрипта харденинга там может остатьсяhosts: files, и DNS вообще перестанут спрашивать./etc/hosts: забытая запись выигрывает у DNS./etc/resolv.confговорит, какой DNS спрашивать. Его любят перезаписывать DHCP, cloud‑init и NetworkManager.Локальный кэш (systemd‑resolved, dnsmasq) может пересылать зону на старый сервер после переезда или хранить тестовую запись.
Вышестоящий DNS и сама зона.
Три такие поломки стали вариантами кейса тренажёра про платёжный шлюз: hosts: files после харденинга, забытая тестовая запись в локальном кэше и пересылка зоны на старый DNS‑сервер.
Как найти. Спросите имя на каждом шаге и сравните ответы:
getent hosts gateway.pay.internal # как имя видит приложение: nsswitch, /etc/hosts, DNS dig gateway.pay.internal # что отвечает DNS из resolv.conf, мимо nsswitch dig @10.0.0.2 gateway.pay.internal # что отвечает вышестоящий DNS напрямую grep '^hosts' /etc/nsswitch.conf; cat /etc/resolv.conf time getent hosts gateway.pay.internal # 5 или 10 секунд — таймаут резолвера
Расхождение показывает сломанный шаг. dig прав, а getent нет — смотрите nsswitch и /etc/hosts. Локальный dig ошибается, а dig @upstream нет — виноват локальный кэш.
Есть и случай, когда все три команды отвечают правильно, а сервис всё равно ходит на старый адрес. Например, nginx разрешает имя из proxy_pass один раз при загрузке конфига и держит адрес до reload. После переезда соседнего сервиса он стучится туда, где его уже нет.
Как починить. Чините тот шаг, на котором разошлись ответы, и сбрасывайте кэш: resolvectl flush-caches для systemd‑resolved, рестарт для dnsmasq. Самое быстрое «решение» — прописать адрес в /etc/hosts. Оно же самое вредное: следующий переезд соседа уронит сервис снова, и искать будут уже дольше.
Что добавить в мониторинг. Проверку, которая разрешает критичные имена с самого сервера, а не с сервера мониторинга. И health‑check, который ходит до важных зависимостей, а не только отвечает «я жив». На графике задержек насторожить должны всплески ровно на 5 и 10 секунд.
5. Протух сертификат
Как выглядит. Сервер и nginx в порядке, а браузеры показывают «Ваше подключение не защищено». Мобильное приложение и интеграции падают с certificate has expired. Мониторинг при этом зелёный.
Почему дашборд молчит. Сертификат не деградирует: до последней секунды он исправен, потом сразу нет. Проверки доступности часто ходят по HTTP или с curl -k, который ошибки TLS игнорирует. А ещё Let's Encrypt с 4 июня 2025 года больше не присылает письма о скором истечении. Если эти письма были вашим единственным мониторингом, сейчас его нет.
Автопродление при этом обычно «есть». Почему оно не сработало:
Нет таймера. Таймер продления ставит пакет certbot, а если его устанавливали через pip или выпускали сертификат руками, таймера нет. В кейсе тренажёра
/etc/cron.weekly/пуст, а готовый скрипт продления лежит рядом и никем не запускается.Продление падает. Для проверки HTTP-01 Let's Encrypt должен достучаться до порта 80. Его закрыли в файрволе или сломали редиректом — и таймер весь последний месяц срока молча пишет ошибки в лог.
Сертификат продлился, а nginx отдаёт старый. nginx читает сертификат при старте и reload. Без хука, который перечитывает конфиг, новый файл лежит на диске, а в памяти остаётся старый. Самый коварный вариант:
certbot certificatesпоказывает свежий срок, и все думают, что проблема на стороне клиента.
Как найти. Сравните, что сервер отдаёт сейчас, с тем, что лежит на диске:
echo | openssl s_client -connect example.ru:443 -servername example.ru 2>/dev/null \ | openssl x509 -noout -subject -enddate # что видят клиенты sudo certbot certificates # что лежит на диске и до какого числа systemctl list-timers | grep -i certbot # есть ли таймер продления sudo tail -n 50 /var/log/letsencrypt/letsencrypt.log # почему падало продление
-servername обязателен. Без него сервер с несколькими сайтами отдаст сертификат сайта по умолчанию, и вы будете смотреть не на тот.
Как починить. Продлите сертификат и сразу добавьте хук, чтобы nginx подхватывал новый сам. Скрипты из renewal-hooks/deploy/ certbot запускает после каждого успешного продления:
printf '#!/bin/sh\nsystemctl reload nginx\n' | sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx sudo certbot renew sudo certbot renew --dry-run # продление проходит и в следующий раз
Что добавить в мониторинг. Срок сертификата, измеренный снаружи, тем же путём, что и у пользователей. В Prometheus это blackbox_exporter:
probe_ssl_earliest_cert_expiry - time() < 14 * 86400
Порог в 14 дней выбран не случайно. certbot продлевает сертификат за 30 дней до конца срока, и если осталось меньше двух недель, значит, продление не работает уже две недели. Алерт придёт, когда время спокойно разобраться ещё есть.
Что общего у всех пяти
Во всех пяти случаях кончается ресурс, которого нет на дашборде. Пока метрики нет, сервер для вас здоров, и о проблеме сообщают пользователи. Пять строк из колонки «Что мониторить» добавляются за вечер и закрывают целый класс ночных инцидентов.
Granulex
"No space left on device" при 70% свободного – самая честная ошибка: место действительно кончилось, просто другое. Inode – второй склад, ёмкость которого расписали при форматировании, а табло в мониторинге одно на оба.