Алерт: сайт не открывается. Вы заходите на сервер: 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. Мониторинг соседнего сервиса тоже зелёный: сломан не он, а путь к нему с одного конкретного сервера.

Путь имени до адреса длиннее, чем кажется, и сломаться может любой шаг:

  1. /etc/nsswitch.conf решает, где и в каком порядке искать. После скрипта харденинга там может остаться hosts: files, и DNS вообще перестанут спрашивать.

  2. /etc/hosts: забытая запись выигрывает у DNS.

  3. /etc/resolv.conf говорит, какой DNS спрашивать. Его любят перезаписывать DHCP, cloud‑init и NetworkManager.

  4. Локальный кэш (systemd‑resolved, dnsmasq) может пересылать зону на старый сервер после переезда или хранить тестовую запись.

  5. Вышестоящий 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 дней до конца срока, и если осталось меньше двух недель, значит, продление не работает уже две недели. Алерт придёт, когда время спокойно разобраться ещё есть.

Что общего у всех пяти

Во всех пяти случаях кончается ресурс, которого нет на дашборде. Пока метрики нет, сервер для вас здоров, и о проблеме сообщают пользователи. Пять строк из колонки «Что мониторить» добавляются за вечер и закрывают целый класс ночных инцидентов.

Комментарии (1)


  1. Granulex
    26.10.2026 18:34

    "No space left on device" при 70% свободного – самая честная ошибка: место действительно кончилось, просто другое. Inode – второй склад, ёмкость которого расписали при форматировании, а табло в мониторинге одно на оба.