Диагностика: когда сервер «не дышит»

Это девятая, финальная статья серии «Эксплуатация VPS / DevOps-база». Восемь шагов назад это был голый Ubuntu (Анатомия VPS); теперь на машине живёт полный WordPress-стек: сервисы (systemd), фронтенд (Nginx), движок (PHP-FPM), база (MariaDB), закрытый периметр (Firewall/SSH), актуальные патчи (Обновления) и рука на пульсе (Мониторинг ОС).

Но любую, даже правильно собранную машину однажды «накрывает»: сайт не открывается, страницы вешаются, приходит алерт, или — что страшнее — просыпаетесь от того, что «всё лежит». Эта статья не про «поставить что-то ещё», а про навык расследования: как за пять минут, когда всё горит и вы не спите, понять, на каком этаже сломалось и снять закупорку, а не гадать «кто виноват: тема, плагин, база или ядро». Мы соберём метод сверху вниз (сайт → nginx → PHP-FPM → база → ОС), четыре самых частых аварии как готовые развилки («кончился диск/иноды», «OOM-killer кого-то сожрал», «слишком много соединений», «битый конфиг после обновления/правки»), и сведём всё в единый аварийный чек-лист первых пяти минут.

Термины, которые введём один раз: авария (incident) — событие «сервис не работает как надо»; здесь именно техническая недоступность/деградация (а если «не дышит» = взломали — это Реагирование на инциденты, там другой сценарий, в конце свяжемся). Этаж / слой — один уровень стека (фронт nginx, движок PHP-FPM, база, ОС). Метод «сверху вниз» — идём от симптома пользователя вниз к корню, проверяя каждый слой по очереди, а не лезем сразу «в ядро». dmesg — буфер сообщений ядра (его читает и journalctl -k); там видно, что творило ядро при OOM. OOM-killer — «убийца из-за нехватки памяти»: ядро само прирывает процессы, когда RAM/swap кончились и есть жертва-виновник; кого — видно в dmesg. TIME_WAIT / conntrack — «соединения, которые ещё держат след в ядре» после закрытия (для TCP) и таблица отслеживания соединений в ядре (для firewall/NAT); их переполнение тоже «вешает» сеть. etckeeper — инструмент, ведущий Git-историю папки /etc (прошлый шаг конфига легко достать). Все домены/IP/цифры условные (example.test, 203.0.113.x — TEST-NET как в №1); подставляйте свои.


Часть I. Метод сверху вниз: как не промахнуться этажом

Главная ошибка в аварии — «переткнуть» весь сервер командами «в никуда», потому что «всё же горит». Зрелое расследование на одном VPS — это короткий опросник по этажам, каждый этаж — одной-двумя командами, пока не найдёте слой, где ответ «не так».

1. Цепочка, по которой идёт живой запрос

Нарисуйте в голове путь одного гостя до поломки (это же лестница из Части VI и Метрик, только теперь в обратную сторону — для разбора):

браузер → DNS → nginx (443) → [сокет пула] → PHP-FPM (воркер) → MariaDB (коннект) → диск/память/CPU
   0          1        2              3              4                 5                    6 (ОС)

Идём с 0-го/2-го этажа вниз, но спрашиваем от симптома. Симптом всегда «сверху» (пользователь видит «не открывается»), корень — где-то этажом ниже. Задача — найти первый снизу вверх этаж, где уже «не ок».

2. «Жив ли я вообще?» — нулевой шаг до всего остального

Прежде разбирать слои, ответьте на три вопроса — они определяют всю дальнейшую стратегию:

Bash
# а) зашёл ли я по SSH на машину? (если нет — см. консоль провайдера, а не возитесь с nginx)
# б) отвечает ли вообще 443 (даже ошибкой — значит фронт живой)?
curl -sSI --max-time 8 https://example.test/ | head -n 3
# в) что вообще слушает и открыто наружу? (штатный аудит из Firewall)
sudo ss -tulpn | grep -E 'LISTEN|UNCONN'
sudo ufw status numbered 2>/dev/null | head

Обратите внимание: почти каждая команда здесь — это ещё и grep с regex-паттерном (LISTEN|UNCONN — альтернатива через |, флаг -E). Как собрать такой фильтр под нужную вам сигнатуру ошибки — в Регулярные выражения на практике.

Варианты развития:

  • SSH не заходит → проблема на уровне ОС/сети/перезагрузки (не nginx и не PHP). Идите в консоль провайдера; проверяйте, не кончился ли диск (Часть II — полный диск ломает вход в sshd) и не упал ли сам сервер загрузки (reboot-required из Обновлений завис и совпал с рестартом).
  • SSH заходит, curl 443 даёт 502 → nginx жив, умер PHP-FPM или база (этажи 3–5). Идите в Часть III/IV.
  • curl 443 даёт 500 → PHP-FPM жив, но WordPress падает внутри (плагин/тема/база-запрос). Смотрите error-лог PHP и slowlog (Часть V).
  • curl по таймауту (тишина), SSH заходит → nginx/сокет не отвечает, либо диск/память «полошили» всё (Часть II/III).

3. Опросник по этажам (одна команда на слой)

Держите перед глазами и идите сверху вниз. На каждом слое — «здоровый» ответ в скобках.

ЭтажКоманда-опросникЧто значит «не ок»
2 · nginxsudo systemctl is-active nginx; sudo journalctl -u nginx -p err --since -15m --no-pager | tailюнит failed/рестартится; ошибки bind/config → откат конфига (Часть V)
3 · сокет пулаls -l /run/php/сокета нет → php-fpm мёртв; права не те → nginx не может читать (Часть III PHP-FPM)
3 · PHP-FPMsudo systemctl status php8.2-fpm --no-pager | head -n 6; sudo tail -n 30 /var/log/php8.2-fpm.logActive: failed/«too many children»/out of memory → оом/пул (Часть III)
4 · базаmysqladmin ping; sudo systemctl is-active mariadbне отвечает → база легла (Часть II диск/Часть IV коннекты)
4 · медленные запросыsudo mysql -e "SHOW GLOBAL STATUS LIKE 'Threads_connected'; SELECT ID,TIME,STATE,LEFT(INFO,40) FROM information_schema.PROCESSLIST WHERE COMMAND='Query' ORDER BY TIME DESC LIMIT 5;"TIME по minutes, STATE висит → KILL (Часть IV)
5 · медленный PHPsudo tail -n 20 /var/log/php-fpm/example-slow.logповторяющийся плагин/get_posts() сверху → тормозит код или база (slowlog из PHP-FPM)
6 · ОСчек из Мониторинга: uptime; free -h; df -h /; df -i /; systemctl --failedload↑/available↓/диск или иноды↑/юнит failed → части II–IV

Правило метода: как только один слой «не ок» — остановитесь на нём, это ваш подозреваемый корень; глубже пока не лезьте. Найдя этаж, идите в нужную часть статьи.

4. «Авария или атака?» — ранний развилочный вопрос

По ходу опроса держите подозрение: если на этаже 2–4 вы видите незнакомых пользователей, чужие процессы, подменённые файлы, аномальный исходящий трафик — это не «сбой», а взлом, и тактика меняется: не «чиним и поднимаем», а «фиксируем, изолируем, не затираем улики». Мост к сценарию безопасности — Реагирование на инциденты: что делать, если сайт взломали; там же — как отличить «OOM из-за кода» от «OOM из-за майнера». В этой статье держим рамку «техническая недоступность», в Части VI дадим точку ветвления.

Чек-лист части

  • Первым делом лезут «в ядро/в PHP» вместо нулевого вопроса «SSH есть? 443 отвечает? что слушает?» — и чинят не тот этаж.
  • Трогают сразу несколько слоёв «на авось» (переписали nginx + php.ini + перезагрузили машину) → непонятно, что помогло и что сломало. **Один слой за раз.**
  • При признаках взлома продолжают «перезапускать и чинить», затирая улики и упускаяPersistence — надо уходить в сценарий реагирования.

Часть II. «Кончился диск / иноды» — тихая смерть, а не обвал

Самый недооценённый отказ. Диск не «падает» — он забивается, и живые процессы начинают молча терять запись: база не пишет транзакции, PHP не пишет сессии, wp-admin не сохраняет, apt не ставит, иногда ломается даже вход по SSH. Выглядит как «странный 500/502», а корень — «диск 100%».

1. Два разных «кончился» — df -h и df -i

df -h /          # байты: Use% 100% → реально нет места
df -i /          # иноды: IUse% 100% → нет места под новые файлы, при видимых свободных гигабайтах!

Оба обязательны: «No space left on device» без полного df -h = почти всегда иноды (свалка из миллионов мелких файлов). Типичные свалки: сессии PHP в /var/lib/php/sessions (или в своём session.save_path из Части III), мелкие фрагменты кэша, тысячи ротированных логов (logrotate из Мониторинга лечит).

2. Где именно жрёт — идём по дереву

Bash
# где крупные директории (флаг -x — не перескакивать на другие ФС)
sudo du -xh --max-depth=1 / 2>/dev/null | sort -h | tail -n 12
# затем спускаемся в «толстую» из них, например /var:
sudo du -xh --max-depth=1 /var 2>/dev/null | sort -h | tail -n 10
sudo du -xh --max-depth=1 /var/log 2>/dev/null | sort -h | tail -n 10

Частые виновники и что делать (безопасно, не трогая боевые данные):

НашлиОткудаБезопасная чистка
толстый /var/log/nginx/*.logнет logrotate/rotate слишком большойнастроить logrotate; удалить старые .gz (rm /var/log/nginx/*.gz), не текущий .log
/var/log большой, а *.log мелкиеjournaljournalctl --vacuum-size=200M (+ лимит SystemMaxUse из Мониторинга)
/var/log/php-fpm/*-slow.log гигабайтslowlog без ротациитот же logrotate для slowlog; можно временно притереть (: > file) — не удалять открытый дескриптор!
толстый /var/backups/mysql/ или дампы на том же дискебэкапы хранятся в одном местеротация (tail -n +N | xargs rm) + уносить копии на другой диск/хост (из MariaDB)
/var/lib/php/sessions сотни тысяч файловсессии не чистятсяпричесрить старые: find /var/lib/php/sessions -type f -mtime +1 -delete; корень — tmpfs/очистка
/boot 100%накопившиеся старые ядраapt-get -s autoremove --purge симуляцией, затем аккуратно (linux-image-<старое>-extra держать страховкой, Часть V)
/tmp раздутскрипт пишет мусорsystemd-tmpfiles --clean / перезагрузка, найти что пишет

⛔ Никогда не удаляйте файл, который процесс держит открытым (rm error.log у живого nginx не освободит место до перезапуска дескриптора — inode stay занят). Правильно «притирать на месте»: : > /var/log/.../file.log (обнулить, не удалить) — и/или дать демону reload/USR1 (механика postrotate из Мониторинга). Иначе освободите не место, а только «имя файла».

3. Когда диск кончился «в моменте» и нужно быстро дышать

Если df 100% и сайт лежит:

Bash
# 1) найти самый большой легкоудаляемый виновник
sudo du -xh --max-depth=1 /var/log | sort -h | tail
# 2) быстро освободить — притереть/сжать самые старые логи, снести ротированные .gz, почистить journal
sudo journalctl --vacuum-size=100M
sudo rm -f /var/log/*.gz /var/log/*/*.[0-9]   # только ротированные номера/архивы, НЕ текущие .log
# 3) проверить, что отпустило, и что база снова пишет
df -h /
sudo mariadb -e "SELECT 1;"   # база принимает? если InnoDB не давал записи — может потребовать восстановления

4. «Кончился /boot» — отдельная песня (переход от Обновлений)

Симптом: apt install падает на этапе создания initramfs, ошибки «not enough space» в /boot. Это накопленные старые ядра. Лечится удалением самых старых (оставив текущее + одно запасное): apt-get -s autoremove --purge (симуляция!) → dpkg -l | grep linux-image (что стоит) → удалить конкретные старые версии явного пакетом, не «всем скопом».

Чек-лист части

  • Смотрят `df -h`, не смотрят `df -i` → сутки чинят «500» при «30% занято», а кончились иноды.
  • Удаляют текущий открытый лог (`rm error.log`) → место не освобождается (дескриптор живой), и демон «осиротел» без нового файла. Нужно `: >` или `postrotate`-сигнал.
  • Хранят бэкапы баз на том же диске, что база → кончился диск = потеряли и данные, и копии (из [MariaDB](mariadb-baza-dannyh-na-servere.md)).
  • `/boot` переполнен, чистят «всем скопом autoremove» без `-s` → удалили нужное ядро/зависимость.

Часть III. OOM-killer: кого сожрало ядро и как не допустить рецидива

На VPS с 1–2 ГБ памяти самая частая «внезапная смерть одного сервиса» — OOM-killer. Память (Available из Мониторинга) кончилась, swap тоже/нет, и ядро само выбирает «пожирнее/поменьше ценности» процесс и убивает его. Чаще всего жертва — MariaDB (большой RSS) или php-fpm-воркер, реже — вы сами (SSH-сессия).

1. Как увидеть, что это был именно OOM

Bash
# журнал ядра: кто был прирезан (флаг -T — читаемые даты)
sudo dmesg -T | grep -iE 'out of memory|oom-kill|killed process' | tail -n 20
# то же через systemd-журнал ядра
sudo journalctl -k --since "-2 hours" | grep -iE 'oom|out of memory|killed process' | tail

Типичная строка:

[...  Fri Oct 18 03:11:02 2026] Out of memory: Killed process 8123 (mariadbd) total-vm:1.9GB, anon-rss:1.2GB ... oom_score_adj:0

Видите Killed process ... (mariadbd) или (php-fpm8.2) — вот и ответ, почему в 03:11 «база упала» и WordPress отдал Error connecting to database. Это не «база сама сломалась», её прирезало ядро, потому что память на машине кончилась.

2. Найти причину перебора памяти (связка с расчётами предыдущих статей)

OOM — это следствие; причина почти всегда «сумма хотелок больше физической памяти». Проверяем бюджет:

# суммарно поедает PHP-FPM детей (RSS в Сумме) + кто сколько
ps -o rss=,comm= -C php-fpm8.2 | awk '{s+=$1} END{printf "php-fpm суммарно: %.0f МБ\n", s/1024}'
ps -o rss= -C mariadbd --no-headers | awk '{printf "mariadbd RSS: %.0f МБ\n", $1/1024}'
free -h

Потенциальные переборы (сверяем с тем, что считали раньше):

  • PHP-FPM: pm.max_children (суммарно по всем пулам из Части V) × типовой memory_limit/RSS. Формула из PHP-FPM нарушена (детей больше, чем (доступно − запас − opcache) / RSS). Лечится: снизить pm.max_children/перейти на ondemand для редких пулов, поднять pm.max_requests, включить Opcache (уменьшает на воркера).
  • MariaDB: innodb_buffer_pool_size из MariaDB выставлен «по учебнику 70%», но рядом живут ещё и 12 детей PHP → не хватило запаса системе. Лечится: пересчитать связку «пул + база + ОЗУ» на один общий бюджет.
  • Кто-то новый: только что включённый плагин/cron с раздутой памятью (memory_limit на запрос у WordPress), или процесс-«внезапный жилец» (внимание → если незнакомый — см. Часть VI про атаку/майнер).
  • Нет swap: на 1 ГБ VPS без swap первое же отклонение = OOM вместо «притормозил на disk». Swap-файл — подстраховка из Анатомии VPS (и vm.swappiness не «0» на 2 Гб, если есть база).

3. Поднять упавший сервис после OOM и притормозить рецидив

Bash
# 1) понять порядок: база или php умер? что сейчас failed
systemctl --failed --no-pager
# 2) поднять базу, потом php (если база была жертва, php без базы вернёт 502 пока база не встанет)
sudo systemctl start mariadb && sleep 2 && sudo mysqladmin ping
sudo systemctl reload php8.2-fpm
# 3) временно уменьшить аппетит (быстро, без точного пересчёта), пока разбираетесь:
sudo nano /etc/php/8.2/fpm/pool.d/www.conf     # pm.max_children ↓
sudo systemctl reload php8.2-fpm

Долгосрочная защита — пересчитать бюджет по формулам PHP-FPM и MariaDB, добавить swap, поставить на available-падение warn-алерт (из Мониторинга), чтобы не узнавать о рецидиве из dmesg постфактум.

4. Ограничить «кто именно жертва» (продвинутый)

Ядро выбирает жертву по «oom score» (доля памяти). Можно защитить критичный процесс (база/SSH), подняв ему «shield» — OOMScoreAdjust в systemd-юните:

# override для mariadb: systemd-даёт -1000 = «никогда не убивать» (крайность, используйте осознанно)
# /etc/systemd/system/mariadb.service.d/oom.conf
[Service]
OOMScoreAdjust=-500

Это не «чинит причину», а «решает, кого убить», когда причину вы уже знаете и чините. (Механика systemd-override — Часть II.)

Чек-лист части

  • «База сама упала» — а в `dmesg` чёрным по белому `Killed process (mariadbd)`. Не читали `dmesg`/`journalctl -k` → чините не то.
  • `innodb_buffer_pool_size` + `pm.max_children` × `memory_limit` вместе > физическая память (каждый по-отдельности «нормальный», сумма — OOM). Считать одним бюджетом.
  • Нет swap на малом VPS → любое отклонение = убийство процесса, а не «тормоз на disk».
  • Подняли php до базы → пока база не встала, php просто возвращает `Error connecting to database`. Порядок: сначала база.
  • `OOMScoreAdjust` поставили «всем в -1000» → ядру некого убить при реальном нехватке, и оно может убить… нет (лучше остаться без возможности), либо уйдёт в панику. Ограничивать точечно.

Часть IV. «Слишком много соединений» и висящие запросы

«Сайт тормозит, а потом 502/timeout», но памяти достаточно и диск не забит — почти всегда это конечный ресурс соединений/запросов, а не железо. Разбираем по слоям.

1. База: «Too many connections» ≠ «база упала»

Bash
sudo mysql -e "SHOW GLOBAL STATUS LIKE 'Threads_connected'; SHOW GLOBAL STATUS LIKE 'Max_used_connections'; SHOW VARIABLES LIKE 'max_connections';"
sudo mysql -e "SELECT COUNT(*) FROM information_schema.PROCESSLIST WHERE COMMAND<>'Sleep';"

Ошибки WordPress вида Error establishing a database connection + в PHP error-логе ERROR 1040: Too many connections = исчерпан max_connections, не «сбой базы». Лечится связкой из Части VI: max_connections ↔ суммарные дети всех PHP-FPM-пулов ↔ wait_timeout (закрывать «уснувшие» коннекты). Быстрая помощь — поднять потолок и снизить wait_timeout (пересчёт по формулам), честная — не плодить детей/соединения.

2. Висящий запрос: найти и KILL, не рестартуя сервер

Механика из Части VI:

Bash
# найти самый долгий живой запрос
sudo mysql -e "SELECT ID,USER,TIME,STATE,LEFT(INFO,50) q FROM information_schema.PROCESSLIST WHERE COMMAND='Query' ORDER BY TIME DESC LIMIT 5;"
# снять конкретный зависший коннект (НЕ рестарт сервиса!)
sudo mysql -e "KILL 8123;"

Смотрите STATE и Info перед KILL (не обрывайте чужой незавершённый импорт вслепую). Если «запрос к базе» повторяется из раза в раз — это не «разовый висняк», а тормоз-код: идите в slowlog PHP-FPM (этаж 5) и оптимизацию запроса (Оптимизация БД).

3. Сеть/ОС: TIME_WAIT, conntrack, «висящие» TCP

Бывает «база не при чём», а коннекты копятся на уровне ядра.

# сводка по соединениям
ss -s
# состояния TCP: много TIME_WAIT / SYN-RECV?
ss -tan | awk '{print $1}' | sort | uniq -c
# conntrack (таблица отслеживания соединений) забита? (особенно с firewall/Docker из Firewall/nftables)
cat /proc/sys/net/netfilter/nf_conntrack_count /proc/sys/net/netfilter/nf_conntrack_max 2>/dev/null
  • много TIME_WAIT — часто норма (короткие соединения, keep-alive выключен); лечится на nginx (upstream keepalive) или tcp_tw_reuse (аккуратно).
  • много SYN-RECV + рост — возможно SYN-flood; это периметр: firewall-rate-limit/ufw limit/fail2ban из Firewall и WAF (ModSecurity).
  • nf_conntrack_count близко к _max — «new connections» отвергаются молча; поднять net.netfilter.nf_conntrack_max (sysctl) или сократить «шум» (fail2ban до ядра).

4. PHP-FPM: очередь детей и «502 из пустого пула»

Если база и сеть в норме, а 502 остаётся — пулу PHP-FPM не хватает свободных воркеров (все заняты висящими запросами; Часть I про «отказ на сокет → 502»).

Bash
# сколько детей и все ли заняты (по всем пулам)
ps -C php-fpm8.2 --no-headers | wc -l
sudo tail -n 20 /var/log/php8.2-fpm.log   # строки «server reached pm.max_children setting» — это вы

max_children достигнут и лог орёт «reached pm.max_children» при нормальной памяти — либо реально много параллельных (трафик), либо висящие запросы держат детей (возвращаемся к п.2 — снять зависшие KILL, и к slowlog п.2 этажа 5).

Чек-лист части

  • `ERROR 1040: Too many connections` лечат рестартом базы → реально поднять `max_connections`/снять висящие/ускорить `wait_timeout`.
  • Висящий «SELECT на минутах» из `PROCESSLIST` убили рестартом nginx/PHP «чтобы полегчало» → коннект к базе пережил рестарт фронта; надо `KILL <Id>`.
  • Не различают «база» (1040/висящий запрос) vs «сеть» (SYN-flood/conntrack) vs «пул» (reached max_children) — чинят не тот слой.
  • `pm.max_children` подняли «чтобы не было 502» → получили OOM (Часть III). Порог 502 и потолок памяти — одна задача, не две отдельные.

Часть V. «Поправил/обновил — не поднялось»: быстрый откат по слоям

Замыкающий навык из Обновлений и systemd. Правило аварии «после изменения»: катим назад ровно тот слой, который поменяли, проверив синтаксис, а не «перезагружаем всю машину в надежде».

1. Проверить синтаксис ДО reload — и после падения

Bash
sudo nginx -t                 # конфиг nginx валиден?
sudo php-fpm8.2 -t            # пулы/конфиг php-fpm валидны?
sudo sshd -t                  # (если правили ssh) конфиг sshd валиден — до рестарта!
sudo systemd-analyze verify /etc/systemd/system/my-unit.service  # юнит валиден?

Упавший юнит читаем через journalctl -u <юнит> -p err --since -10m --no-pager | tail — там почти всегда первая строка ошибки ("unknown directive" в nginx, "pool config error" в fpm).

2. Откатить битый конфиг (слой за слоем)

СлойБыстрый откат
nginxвернуть прежний конфиг из .d-резерва/sites-available-bak; nginx -t → systemctl reload nginx
PHP-FPMправки в www.conf пула откатать; php-fpm -t → systemctl reload
sshdне трогать основной файл — убрать битый .d-drop-in (rm /etc/ssh/sshd_config.d/99-битый.conf); живой второй сеанс не закрывать!
systemd-юнитsystemctl revert <юнит> (сбрасывает override к пакетному дефолту)
/etc/* вообщеetckeeper (Git-история /etc): cd /etc && sudo git log && sudo git checkout HEAD~1 -- <файл>

Bash
# если ставили etckeeper — конфиг-ошибку видно в git и откатывается за секунды
sudo etckeeper commit "before nginx edit"  # (идеал: коммитить ПЕРЕД правкой)

Если etckeeper не ставили, «последний рабочий конфиг» = ваш .bak.$(date +%F) из правил №7 («бэкап перед правкой» — из PHP-FPM про sshd, и везде). Отсутствие истории конфигов — одна из причин, почему «один шаг отката» превращается в сутки переписывания вручную.

3. Пакет/ядро — если виновником оказалось обновление (из Обновлений)

  • Битый пакет → apt-get install pkg=версия --allow-downgrades + reload сервиса (не restart фронта).
  • Битое ядро (не грузится после ребута) → через GRUB из консоли провайдера выбрать старую запись загрузки; это «аварийное окно», не «на лету». Держать запасное ядро установленным (не autoremove-нуть его) — страховка из Обновлений.
  • Виновник — авто-обновление ночью → читать /var/log/apt/history.log (что ставилось) + /var/log/unattended-upgrades/unattended-upgrades.log; дальше hold/unattended-блэклист на этот пакет.

4. «Откатился — проверяем, что живое»

Bash
sudo systemctl --failed --no-pager         # больше ничего не в failed?
curl -sSI --max-time 8 https://example.test/ | head -n 3   # 200/301 = фронт отвечает
sudo mysqladmin ping                        # база жива
sudo tail -n 10 /var/log/php8.2-fpm.log     # нет новых «reached max_children / OOM»

И главное правило отката в аварии: зафиксировать, что сделали (одной строкой в /root/incident-log или Issue), чтобы «сделали три шага назад и теперь всё работает» не стало «через неделю никто не помнит, почему тут вот такой hold/бэкап-файл».

Чек-лист части

  • Правят конфиг и сразу `systemctl restart` без `-t` → синтаксическая ошибка роняет сервис, который до этого ещё работал.
  • Меняют сразу nginx+php+ядро «чтобы наверняка» → невозможно понять, что помогло/сломало (нарушен принцип «один слой за раз»).
  • Правят sshd и закрывают текущий сеанс до проверки второго → «SSH есть, входа нет» (правило №1/№6).
  • Нет истории `/etc` (`etckeeper`/бэкап-файла) → откат = ручное вспоминание «а что там было до этого».

Часть VI. Единый чек-лист «авария» (первые 5 минут) и развилка «сбой vs взлом»

Собираем всю серию в один рабочий документ «первой минуты». Распечатайте/держите в ~/incident.md. Идём строго по порядку; как только нашли «не ок» — проваливаетесь в нужную часть.

1. Аварийный чек-лист — по порядку

[0] Вход и пульс машины (60 секунд)
    - SSH заходит? (нет → консоль провайдера, не трогать ничего другого)
    - uptime; free -h; df -h /; df -i /; systemctl --failed
    - что слушает наружу: sudo ss -tulpn | grep -E 'LISTEN|UNCONN'; sudo ufw status numbered
    → диск/иноды 100% → Часть II ; available≈0/`--failed` → Часть III ; лишнее слушает → Firewall/аудит
[1] Отвечает ли фронт (30 секунд)
    - curl -sSI https://example.test/ | head -3
    → 502 → Часть III/IV (php-fpm/пул) ; 500 → error-лог PHP/плагин/база ; таймаут → Часть II/IV
[2] Слои по опроснику Части I (nginx→сокет→php→база→ОС)
    - systemctl is-active nginx php8.2-fpm mariadb
    - ls -l /run/php/ ; tail -n 30 /var/log/php8.2-fpm.log
    - mysqladmin ping ; SHOW PROCESSLIST (TIME↓) ; tail slowlog
    → висящий запрос → KILL (Часть IV) ; «reached max_children» → пул/память (Часть III/IV)
[3] Снять закупорку без «свалить всё»
    - KILL <Id> зависшего; reload (не restart) где можно; вернуть конфиг из .bak/git; откат пакет/ядро по необходимости
    - после каждого шага: curl 443 снова (не чиним «вслепую», проверяем симптом ушёл)
[4] Зафиксировать (1 минута, критично!)
    - что нашли (этаж+признак), что сделали, что откатили → ~/incident-log
    - если «после обновления» → apt history; если «в 3 ночи» → unattended log; если OOM → dmesg -T
[5] Не допустить рецидив завтра
    - пороги/heartbeat на [Мониторинг](monitoring-urovnya-os-cpu-ram-disk.md); пересчёт pm/buffer_pool; logrotate; hold на битый пакет; бэкап базы на другой диск

2. Развилка «сбой vs взлом» — когда сменить сценарий

Прямо в моменте аварии отличите «технику» от «атаки» — по признакам:

Признак«Сбой/нагрузка»«Похоже на взлом»
Незнакомый процесс с высокой CPU/RAMваш php/cronнезнакомое имя/путь в /tmp/.hidden, майнер
Логинормальные сообщенияпропавший auth.log, странные authorized_keys, новые юниты
Файлы сайтане менялисьизменённые timestamps в wp-content, «левые» .php в загрузках
Сетьтрафик по профилюисходящий флуд, открытые наружу :9100/база, SYN-flood
Входсвой IPчужой IP в journalctl/auth, сменившийся пароль админа

Если ловите правый столбец → не «чиним и перезапускаем» (затрёте следы и «вернёте» атакующего): изолируем (закрыть наружу доступ, отозвать скомпрометированные ключи), фиксируем (копии логов/подозрительных файлов, снимок), потом чиним. Полный сценарий — Реагирование на инциденты: что делать, если сайт взломали; там же «восстановление из чистого бэкапа» (база из MariaDB, файлы из архива) как единственный надёжный путь «вернуть чистое».

Инструмент под задачу

Когда «сервер не дышит», половина ответов уже лежит в логах — dmesg, OOM-строчки, journalctl, apt-история. Вставьте выдержку, и разбор покажет, на что смотреть первым. Попробуйте инструмент прямо здесь, на своих данных:

AI Log Analyzer
Гость
Токены: 0 / 50 000
Гостевой лимит — 50 000 токенов. Зарегистрируйтесь — для пользователей лимит + 200 000 токенов.
0 / 20 000 символов
ИИ анализирует логи...

Результат анализа:

Чек-лист граблей всей статьи и серии

  • При аварии первым делом «перезагрузить машину» → потеряли состояние/улики и не поняли причину (идите по опроснику этажей).
  • Не читают `dmesg -T`/`journalctl -k` на «база/PHP внезапно умер» → не увидели, что был OOM (Часть III).
  • Не смотрят `df -i` рядом с `df -h` → сутки не поймут «500 при свободном диске» (Часть II).
  • `KILL` зависшего запроса «вслепую», не прочитав `Info` → оборвали чужой незавершённый импорт/транзакцию (Часть IV).
  • Откатывают «всё сразу» вместо одного подозрительного слоя → не знают, что помогло (Часть V).
  • Правят конфиг без `-t` и без второго SSH-сеанса → роняют то, что ещё работало / теряют вход (Часть V, №1/№6).
  • При признаках взлома продолжают «чинить и перезапускать» → затирают следы, атакующий остаётся (Часть VI → Incident response).
  • Забыли зафиксировать «что нашли/сделали» → через неделю машина «странно сконфигурирована», и никто не помнит почему.
  • После починки не поставили порог/heartbeat, не пересчитали пул — рецидив через неделю через тот же механизм (Часть VI, шаг 5).

Материал написан простым языком; любой англоязычный термин (авария/incident, этаж/слой, dmesg, OOM-killer, oom_score, RSS, TIME_WAIT, SYN-RECV, conntrack, KILL, etckeeper, systemctl revert, --allow-downgrades, GRUB, heartbeat) объясняется при первом употреблении. Команды — для Ubuntu/Debian; имена пакетов/юнитов/сокетов/логов (php8.2-fpm/8.3, mariadb/mysql, sshd/ssh, systemd-journald) и версии сознательно условны — проверяйте фактические через systemctl status, php -v, mariadb --version, ls /run/php/. Домены/IP/цифры (203.0.113.x — TEST-NET, 85%/70%, пороги) условные; подставляйте свои под бюджет машины. Главный запрет серии в этой статье — «не перезагружай машину как первый шаг» (теряете состояние и улики): идите по опроснику этажей, снимайте закупорку точечно, меняйте один слой за раз, проверяйте симптом (curl 443) после каждого действия, и всегда держите второй SSH-сеанс открытым, пока не убедились, что новый вход заходит. Тонкие механизмы (eBPF-профилирование, perf/strace глубокого трейсинга одного процесса, ядро-параметры sysctl network-тюнинга, systemd resource-контроль MemoryMax по юниту) упомянуты лишь где нужно — это следующий уровень, когда базовый контур «сверху вниз → снять закупорку → зафиксировать → не допустить завтра» отработан.

👤 Зарегистрируйтесь, чтобы добавить материал в закладки.