Это седьмая статья серии «Эксплуатация VPS / DevOps-база». К концу прошлого шага (Firewall и SSH-харднинг) у вас закрытая машина: снаружи видно только {22, 80, 443}, боты ловятся fail2ban, лишние слушающие порты проверены через ss -tulpn. Но есть вещь, которую firewall закрыть не может: сам софт, который вы держите на машине, стареет. CVE (Common Vulnerabilities and Exposures — публичный идентификатор «известной дыры») выходят каждую неделю; боты, которые вы отбили на SSH, точно так же тычут в HTTP, в PHP и в ядро. Закрытый периметр без актуального ядра и PHP — это крепость с оторванными петлями на воротах: снаружи не залезут, а через то, что уже внутри и устарело — легко.
Обычные советы на эту тему разбиваются на два крайних полюса. Первый — «ставь автоматические обновления, они всё починят сами», в реальности оборачивающийся «сервер сам поставил новое ядро в 3 ночи и не перезагрузился, а libc перезапустил nginx посреди пиковой нагрузки». Второй — «я сам всё контролирую, ставлю раз в полгода всё разом», в реальности оборачивающийся «однажды после большого апгрейда не поднялся PHP и сайт лежит на две ночи, потому что не проверили на копии». Эта статья — про середину, где и обновления приходят вовремя, и сайт при этом стоит: как вообще живут обновления в apt (кандидат, hold, pin), что такое unattended-upgrades и для чего они реально, когда патч невозможно применить без перезагрузки (/var/run/reboot-required, needrestart, libc и ядро), как обновлять PHP и MariaDB на WordPress почти с нулевым простоем (reload вместо restart, параллельная версия PHP с переключением сокета в nginx), почему тест на копии — не бюрократия, а единственная защита от «я же обновлял, а оно сломалось», и как откатывать через apt-историю, если что-то пошло не так.
Термины, которые введём один раз: apt (Advanced Package Tool) — система пакетов Ubuntu/Debian; ставит, обновляет, удаляет программы из репозиториев. Пакет — «упаковка» программы (
nginx,php8.2-fpm,mariadb-server); у каждого пакета в системе есть версия и состояние. Кандидат (candidate) — версия пакета, которуюaptпоставит при следующем обновлении (не всегда самая свежая, иногда сдержаннаяpin-ом). Hold — флаг «этот пакет не обновляй» (полезно на софте, который вы тестируете отдельно). Pin — «пришпилька» вapt: правило, какое происхождение (репозиторий/версия) предпочитать. unattended-upgrades — демон, сам ставящий (обычно только security-) патчи без вас. libc (glibc) — фундаментальная библиотека C, её использует каждый процесс на машине; её обновление почти всегда требует перезапуска сервисов. reload vs restart — из systemd: reload («перечитать конфиг/мягко пересоздать детей») не рвёт установленные соединения, restart («остановить и заново запустить») — рвёт. CVE (Common Vulnerabilities and Exposures) — публичный номер известной уязвимости; «security-patch» — пакет, который эту CVE закрывает. Все домены/IP/номера версий условные; подставляйте свои.
Часть I. Как живут обновления в apt: кандидат, hold, pin
Чтобы не ставить обновления «вслепую», сначала научимся читать, что вообще apt собирается сделать.
1. Разделение: «что я могу поставить» и «что стоит сейчас»
# 1) какие списки пакетов (индексы) я вижу — скачиваем актуальные с репозиториев
sudo apt update
# 2) что доступно для обновления, без установки
apt list --upgradable
# 3) про один пакет: что стоит, что кандидат, из какого репозитория
apt-cache policy php8.2-fpmapt-cache policy покажет:
Installed: 8.2.10-1ubuntu0.3
Candidate: 8.2.12-1ubuntu0.4 ← вот это поставит apt upgrade
Version list:
8.2.12-1ubuntu0.4 500 800 http://archive.ubuntu.com ... -updates ← приор 800
8.2.10-1ubuntu0.3 500 500 http://archive.ubuntu.com ... ← приор 500
Две ключевые вещи: Installed — версия в системе, Candidate — версия, которую apt upgrade поставит. Приоритеты (Pin priority) решают, кто побеждает между репозиториями. Если Candidate «какая-то странная» — вы ещё не поставили, уже можно понять, почему apt выбрал её.
2. hold: «эту версию не трогай»
Если вы держите PHP/MariaDB на конкретной версии и обновляете её вручную, поставьте флаг удержания:
sudo apt-mark hold php8.2-fpm php8.2-common php8.2-cli
sudo apt-mark showhold # список удерживаемых пакетов
# снять
sudo apt-mark unhold php8.2-fpmhold не «выключает обновления» целиком — он не даёт apt upgrade трогать конкретный пакет. Security-пакеты того же PHP при hold не применяются автоматически, и вы обязаны сами закрыть эту CVE — осознанно.
Практическое правило:
holdставим на то, что обновляем вручную (версия PHP, на которую настроены пулы/описания в nginx), аunattended-upgradesне должен на этих пакетах ничего пытаться. Это не «мы не хотим обновлять» — это «мы хотим обновлять вот так и вот тогда». Не держитеholdдольше, чем плановый цикл проверок, иначе однажды увидите, что на сервере полгода живёт неподдерживаемая версия.
3. pin: приоритет «не брать отсюда» или «брать отсюда»
pin — более тонкий, чем hold: «когда есть несколько источников, бери вот из этого». Например, вы поставили PPA ondrej/php (сторонний репозиторий с PHP-версиями), и хотите, чтобы из него apt брал только PHP, а не всё подряд:
# /etc/apt/preferences.d/ondrej-php.pref
Package: php*
Pin: release o=LP-PPA-ondrej-php
Pin-Priority: 500
Package: *
Pin: release o=LP-PPA-ondrej-php
Pin-Priority: 100
Строки Pin-Priority: 100 — «бери в последнюю очередь», 500 — обычный, 1000 — «форсировать». Про apt update не сломается; но с 100 вы не рискуете получить из этого PPA libssl вместо репозиторного.
4. Обновление и restart — два разных события
Самое частое непонимание: apt upgrade обновляет файлы пакета на диске, но не всегда применяет изменения к живым процессам. Различают три случая:
- Пакет «одноразовый» (утилита, скрипт) — обновился, и всё сразу работает (
curl,git). - Пакет-демон (nginx, php-fpm, mariadb) — на диске новое; живой процесс исполняет старый код, пока его не перезапустить. Часть демонов сама делает
reload/restartпосле обновления (postinst-скрипт); часть — нет. Это надо знать по факту, а не предполагать. - Пакет-библиотека (glibc, openssl) — файлы обновились, но каждый процесс, который уже загрузил старую библиотеку в память, продолжает работать со старой. Это отдельный класс «неприменённых обновлений», см. Часть II.
Поэтому после любого apt upgrade — всегда два вопроса: «что просит перезапуск?» и «что просит перезагрузку?». Не «всё же ок, пошло-поехало».
Чек-лист части
- Держат `hold` на ядре/PHP «навсегда» — забывают, что security-патч теперь не применяется сам и CVE живёт на сервере.
- Пишут pin-файлы в `/etc/apt/preferences.d/` с опечаткой в имени пакета → `apt` тихо игнорирует правило, приоритеты «не те».
- Считают, что `apt upgrade` «применил изменения» — а PHP-FPM/nginx/mariadb живут на старом коде, пока их не перезапустить.
- Обновляют всё разом «по любви к новизне» — на самом деле каждый обновлённый демон = «потенциально рванёт», и это надо тестировать (см. Часть IV).
Часть II. Что ставит unattended-upgrades и когда неизбежна перезагрузка
Главный вопрос на живом VPS: как получать security-патчи своевременно, но без сюрпризов («сервер сам перезагрузился», «PHP-процесс держит старую libc месяцами»). Здесь два отдельных механизма:
1. unattended-upgrades: автоматические security-патчи
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades # ставит базовый конфигНастройки (/etc/apt/apt.conf.d/50unattended-upgrades):
// какие репозитории брать
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security"; // только security — это база
// "${distro_id}:${distro_codename}-updates"; // ← раскомментируют «для удобства» и потом жалеют
};
// что не трогать (наши ручные обновления)
Unattended-Upgrade::Package-Blacklist {
"php8.2-"; // мы держим на hold, но подстрахуемся
"mariadb-";
};
// авто-ребут — по умолчанию выключен, включайте только со временным окном
Unattended-Upgrade::Automatic-Reboot "false";
// Unattended-Upgrade::Automatic-Reboot-Time "03:30"; // только если выше "true"
// почта/лог при проблемах
Unattended-Upgrade::Mail "you@example.test";
Unattended-Upgrade::Verbose "true";
Что реально делать:
- Разрешить автоматическое обновление только security-репозитория. Это патчи к CVE, у них обычно высокая «совместимость внутри минорной версии» и почти никогда не ломается API. Полные
-updates(мажорные/фичевые) держим на ручном тестировании. - Не включать
Automatic-Reboot "true"без явно настроенного окна и без мониторинга. На машине с 1–2 ГБ RAM без бэкап-подтверждения авто-ребут = однажды в 03:37 сайт лежит из-за не закрывшегося nginx. - Видеть лог:
/var/log/unattended-upgrades/unattended-upgrades.log. Раз в день/неделю смотреть, что он реально сделал:grep -i 'error\|fail' /var/log/unattended-upgrades/*.log.
Проверка, что демон работает:
systemctl status unattended-upgrades --no-pager | head -n 6
sudo unattended-upgrade --dry-run --debug 2>&1 | tail -n 40 # что сделал бы без установки2. Когда патч невозможно «применить, не перезагружая»
Есть класс обновлений, которые физически не применяются к живому процессу на машине без рестарта. Это:
- новое ядро (kernel) — старое ядро работает в памяти; новая версия в
/boot, но загрузится только при ребуте. - libc (glibc) — фундаментальная библиотека C, её уже загрузил в память каждый демон; перезагрузить её «на лету» для уже запущенного процесса нельзя.
libssl/libcrypto,systemd, dbus и др. — аналогично: если процесс держит в памяти «старую» копию библиотеки, он не переедет на новую без рестарта.
Как узнать, что такое было и не применено:
# 1) явный флаг «нужна перезагрузка» — ставится postinst-скриптами после обновления ядра/libc
ls -l /var/run/reboot-required
cat /var/run/reboot-required # текстовое «почему»
cat /var/run/reboot-required.pkgs # список пакетов-виновников
# 2) кто реально держит УДАЛЁННЫЕ файлы на диске (старая версия библиотеки)
# ставим needrestart (есть в репозитории)
sudo apt install -y needrestart
sudo needrestart -r l # level: «что требует перезапуска демонов»
sudo needrestart -k # требует ли ядро перезагрузкиneedrestart — это самый честный ответ «применились ли мои патчи». Он сканирует /proc/*/maps, находит процессы, у которых открыты файлы с пометкой deleted (старая версия библиотеки заменена новой на диске, но процесс держит старую), и предлагает аккуратный список «кого перезапустить». Перезапуск sshd/cron/rsyslog часто безопасен (текущая SSH-сессия не рвётся), но php-fpm/nginx/mariadb требуют внимания (см. Часть III — как reload, а не restart).
3. Практика перезагрузки
- Перезагружайтесь только в окно тишины, когда можно себе это позволить. На одном VPS под личным блогом это обычно ночной слот.
- Перед ребутом проверьте: (а) что вы можете зайти в консоль провайдера (KVM/VNC через панель) — это ваш запасной вход, если сетевой вход не поднимется; (б) что после ребута firewall (
ufw) поднимется сам (это у вас есть, см. Часть II); (в) что после ребута сервер вообще загрузился — если панель VPS не даёт «не загружается» — сразу через неё же. - Не откладывайте ребут бесконечно: если флаг
/var/run/reboot-requiredвисит неделями, вы «обновлены только наполовину». Это не «мелочь», это «CVE уже закрыты, но ядро ещё то же, что было». - Краткая формула: авто-патчи безопасности ставим, авто-ребут отключаем,
reboot-requiredсмотрим вручную, ребут планируем, а не ловим «как получится».
Чек-лист части
- Включили `Automatic-Reboot «true»` без `Automatic-Reboot-Time` — сервер ребутится сразу, как патч встал, иногда в пик трафика.
- Открыли `-updates` в `Allowed-Origins` «чтобы свежее» — получаете мажорные версии без теста; однажды PHP-минор с изменённым поведением и «почему-то не работает».
- Флаг `/var/run/reboot-required` висит месяцами → «у меня всё обновлено» не правдиво: libc/ядро старые.
- Не установили `needrestart`/`checkrestart` → не знаете, что реально требует рестарта сервисов после `apt upgrade`.
Часть III. PHP и MariaDB почти без простоя: reload, параллельная версия, переключение сокета
Обещание «почти без простоя» требует честного разговора: нулевой даунтайм на одной машине для одной БД — это миф. Но «простой в секунды, а не в минуты, и без риска сломать сайт» — это реальность, если знать механику.
1. PHP: минорные обновления почти всегда «незаметны»
Минорное обновление внутри одной ветки (php8.2.10 → php8.2.12) — это apt upgrade php8.2-fpm и мягкий reload:
# ставим только php-пакеты, не всё подряд (чтобы держать контроль над списком)
sudo apt install --only-upgrade php8.2-fpm php8.2-cli php8.2-common
# пул сам обычно делает «reload» (systemd-юнит сохранный). Если — нет, делаем сами:
sudo systemctl reload php8.2-fpmМеханику reload-против-restart разбирали в systemd; PHP-FPM- specifics — в Части II: reload перечитывает конфиг пула и мягко пересоздаёт воркеров (идущие запросы доживаются, новые обслуживаются). Для WordPress это «почти незаметно»: воркеры под нагрузкой пересоздаются за секунды.
⛔ Перед любым шагом, который рвёт воркеры — убедитесь, что вы не на середине
wp-cron-импорта или массового экспорта.reloadне рвёт,restart— рвёт; еслиrestartнеизбежен (например, libc требует рестарта), ставьте в тихое время.
2. PHP: мажорная версия (8.2 → 8.3) — параллельная установка
Мажорную версию PHP нельзя «обновить на месте без риска»: плагины/тема могут не любить новый движок, поведение функций меняется, php.ini с другой версии не всегда мигрирует. Правильный путь на одном VPS — поставить рядом и переключить:
# (обычно) нужен PPA на Ubuntu: sudo add-apt-repository ppa:ondrej/php
sudo apt update
sudo apt install -y php8.3-fpm php8.3-cli php8.3-mysql php8.3-curl php8.3-mbstring ...
# новый пул уже в /etc/php/8.3/fpm/pool.d/; сокет будет свой:
sudo systemctl status php8.3-fpm --no-pager | head -n 6
ls -l /run/php/php8.3-fpm.sockТеперь у вас живут параллельно два PHP-FPM (8.2 и 8.3), каждый со своим сокетом. WordPress на них не подействовал — переключение делаем по доменам, в nginx:
# /etc/nginx/snippets/php-version.conf (сделайте include, чтобы правка в одном месте)
set $phppool /run/php/php8.2-fpm.sock; # ← переключаем одной строкой
# /etc/nginx/snippets/fastcgi-php.conf (в include, не дублируя в каждом vhost)
fastcgi_pass unix:$phppool;
fastcgi_param PHP_ADMIN_VALUE "php_version=on";
И для сайта, который хотите протестировать на 8.3, — в server-блоке:
server {
listen 443 ssl http2;
server_name example.test;
...
# локальный override для одного vhost:
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
include snippets/fastcgi-php.conf;
...
}
}
sudo nginx -t && sudo systemctl reload nginx # reload — текущие соединения доживутДальше — drill на копии (Часть IV) или, если вы уже переключили, беглая проверка. Если что-то пошло, откат = вернуть include/override на 8.2-сокет и reload nginx — секунды. Когда всё хорошо, правим snippets/php-version.conf на 8.3 глобально (для всех доменов), и потом можно снять 8.2-пакет (apt remove php8.2-fpm), а перед снятием — hold снимаем.
3. MariaDB: честный ответ «restart нужен»
Мажорная ветка MariaDB (10.5 → 10.11) — это всегда миграция системных таблиц, рестарт сервиса, иногда «после апгрейда нельзя понизить». На одном VPS без репликации честный путь: (а) тест на копии; (б) окно обслуживания; (в) дамп базы перед стартом (мы умеем из Части IV); (г) systemctl restart mariadb и проверка.
Минорные (10.11.4 → 10.11.6) часто рестарта не требуют или требуют «осторожный». Проверка:
apt list --upgradable | grep mariadb
apt-cache policy mariadb-server
# посмотрим, что пакет просит после апгрейда:
sudo needrestart -r l | grep -i mariadbreload у MariaDB нет (сервер базы данных — не PHP-FPM с воркерами), но restart в минорном обновлении обычно = секунды на холодном старте с InnoDB-пулом (пул прогреется; WordPress «на первой сотне хитов» будет медленнее, см. warm-up в Части IV — та же логика для кэшей).
Если нужно «совсем без простоя» для базы — это репликация (master–master/master–slave на второй машине), тема следующей серии, здесь её не раскрываем, а фиксируем: на одной машине «нулевой даунтайм БД» — это миф, «секунды» — достижимо.
4. Нginx: самый аккуратный, почти всегда reload
Обычное обновление nginx (apt upgrade nginx) пост-скрипт делает systemctl reload (не restart). Ваш WordPress этого не заметит. Исключение — когда nginx требует рестарта (редко; например, после обновления модулей или glibc, если nginx держит старую libc в памяти — тогда needrestart подскажет). Порядок безопасный:
sudo apt install --only-upgrade nginx
sudo nginx -t # синтаксис конфига — перед reload
sudo systemctl reload nginx # текущие соединения не рвутся5. Связка: что реально рвёт трафик и что нет
| Действие | Что происходит | Простой для пользователя |
|---|---|---|
reload nginx | master перечитывает конфиг, старые воркеры доживают свои запросы, новые на новом конфиге | нет (или ~мс) |
restart nginx | все воркеры убиты, сервер поднимается заново | 1–5 секунд |
reload php-fpm | master «мягко» пересоздаёт детей | нет (или ~мс) |
restart php-fpm | дети убиты; запросы на них — 502 до подъёма | 1–5 секунд |
restart mariadb | сервер базы останавливается и поднимается (прогрев пула) | от секунд до десятков секунд (в зависимости от размера пула и «прогретости» ОС-кэша) |
Именно из этой таблицы вытекает, что «обновления без простоя» — это умение выбрать reload, а не restart, а не магия. Если же вам объективно нужен restart (libc, ядро, БД-мажор) — это окно обслуживания, и в окне нужно уметь откатиться (Часть V) и быстро восстановиться после (Часть III + warm-up из PHP-FPM).
Чек-лист части
- Ставят мажорную PHP «обновились пакетом», не проверяя, что плагины на ней живут → однажды не поднимается админка. Правильно — параллельная установка + переключение сокета.
- Держат только один PHP-сокет и «меняют его», а не ставят рядом → откат = `restart` с риском.
- На всякий случай сделали `restart php8.3-fpm` вместо `reload` → рванули живые запросы «на ровном месте».
- Не проверили `nginx -t` перед reload/после переключения сокета → nginx reload провалился, и на 8.2-сокет запросы не идут, а на 8.3 nginx не слушает (белый экран/`502`).
- Не сняли `hold` с PHP после того, как перешли на 8.3 глобально →security-патчи к 8.2 продолжают не ставиться, а 8.3 на него не распространяется.
Часть IV. Тест на копии: staging на одной машине без второй машины
Единственная надёжная защита от «я обновил — а сайт не поднимается» — это прежде, чем на боевой, поставить кандидатную версию в песочницу и проверить. Отдельная staging-машина идеальна, но на самописном VPS её часто нет. Поэтому учимся делать песочницу на той же машине, не трогая боевой сайт.
1. Два способа «копии рядом»
- Вариант А (проще): другая БД + другой
wp-content+ другой домен/порт на том же стеке. Разворачиваем копию боевого сайта как «второй сайт», обновляем пакет, проверяем, боевой не тронут. - Вариант Б (чище): временный контейнер или
chrootс той же версией PHP. Но это уже тема Контейнеры и оркестрация; для одной машины без Docker вариант А реальнее.
Дальше — вариант А.
2. Клон базы в песочную (механика из Части V)
# снимаем дамп боевой базы «на лету», без блокировки
mysqldump --single-transaction --routines --triggers -u wp_example wp_example \
| gzip > /tmp/staging_dump_$(date +%F).sql.gz
# создаём песочную БД и пользователя (не трогаем боевую!)
sudo mariadb -e "CREATE DATABASE wp_staging CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\
CREATE USER 'wp_staging'@'localhost' IDENTIFIED BY 'песочный_пароль';\
GRANT ALL ON wp_staging.* TO 'wp_staging'@'localhost';"
gunzip -c /tmp/staging_dump_$(date +%F).sql.gz | sudo mariadb wp_staging3. Клон файлов и wp-config песочницы
sudo -u site_example cp -a /var/www/example.test /var/www/staging.test
# правим wp-config песочницы на staging-БД и staging-пользователяВ nginx подвешиваем staging.test на другой порт (listen 8081;) или на поддомен staging.example.test, и держим его не открытым наружу в firewall: из Части II — либо bind на 127.0.0.1, либо ufw открывает 8081 только под ваш IP (allow from 203.0.113.7 to any port 8081). Иначе вы только что выложили копию боевого сайта в интернет — включая тестовые данные.
⛔ Песочница обязана быть закрыта наружу. Это та же конвенция, что и везде в ветке: тестовый стенд с боевым дампом (в нём — пользователи, хэши паролей, настройки плагинов) снаружи недопустим. Проверьте
ss -tulpnиufw statusпосле настройки staging.
4. Ставим кандидатную версию на песочницу и проверяем
# на staging-пуле ставим новую PHP (или ту, что тестируем), НЕ на боевом
# после параллельной установки (см. Часть III) переключаем STAGING-vhost на новый сокет
# боевой vhost остаётся на php8.2-fpm.sock
Минимальный набор проверок (не «открыть и посмотреть», а пройтись руками по критичному):
- Главная открывается (200, контент на месте, кириллица не «кракозябрами»).
- Админка заходит («/wp-admin»), видны посты, настройки, пользователи.
- Один сабмит формы работает (комментарий/форма плагином) — ловит поломку `wp-cron`/nonce.
- Загрузка медиа (upload) проходит — ловит поломку `upload_max_filesize`/прав на `wp-content/uploads`.
- `php -m` на песочнице содержит нужные расширения, что стояли на боевом (`mysqli`, `curl`, `mbstring`, `gd/imagick`, `intl`) — мажорная PHP часто «теряет» расширение, которое надо ставить отдельно (`php8.3-gd`).
- Логи не сыпят фаталами: `tail /var/log/php8.3-fpm.log`, `journalctl -u php8.3-fpm -p err —since -5m`.
5. Что не заменяет тест на копии (и почему)
Тест на копии ловит «не поднимется/сломается», но не ловит нагрузочное поведение новой версии (другой опкод, другой план запроса БД). Для «обновил и поедет ли под пиком» это Нагрузочное тестирование из серии про архитектуру, плюс php-fpm slowlog (см. Часть VI). В рамках одной машины и одной статьи фиксируем главное: никакой релиз версии PHP/БД/nginx не идёт на боевой без предварительной проверки на копии — даже «минорный security», если он меняет major-библиотеку.
Чек-лист части
- Песочница (`staging.test`, порт 8081) открыта наружу в firewall с боевым дампом внутри → утечка пользователей/хэшей.
- Тестируют новую PHP «переключив весь сервер» → если не зашло, лежит весь боевой сайт. Правильно: боевой на старом сокете, staging на новом.
- Проверяют «главная открылась» → а админка/загрузка медиа/формы — это другие коды, и они чаще всего и ломаются.
- Не проверили `php -m` → `gd`/`imagick`/`intl` новой мажорной версии не установлены, и «почему-то не генерятся миниатюры».
Часть V. Откат: apt-история, понижение версии и ядро через GRUB
Если после обновления что-то сломалось и копия «не предупредила» (или вы обновили без копии — не надо так, но случается), нужен быстрый путь назад. Разберём откат пакета и отдельно — ядро.
1. Что вообще ставилось и когда: apt-история
# последние транзакции: дата, какие пакеты, кто инициировал
less /var/log/apt/history.log
# быстрый взгляд на последние обновления
grep -A3 'Install\|Upgrade' /var/log/apt/history.log | tail -n 40
# полная история (ротация history.log держит прошлые месяцы в .gz)
zcat /var/log/apt/history.log.*.gz | less
Здесь вы видите, например, что «в 03:11 unattended-upgrades поставил php8.2-fpm 8.2.12», и понимаете, что сравнивать/откатывать.
2. Понизить версию пакета назад
Если нужная версия ещё лежит в кэше/репозитории:
# посмотреть доступные версии пакета
apt-cache madison php8.2-fpm | head
# поставить конкретную (понижение) — --allow-downgrades обязателен при downgrade
sudo apt-get install php8.2-fpm=8.2.10-1ubuntu0.3 --allow-downgrades
sudo systemctl reload php8.2-fpmНюансы честного понижения:
- не всякая версия «откатывается»: у PHP/БД дамп базы после миграции на новую версию может быть несовместим со старой (MariaDB «понизить» нельзя после миграции системных таблиц). Поэтому дамп перед мажорным апгрейдом (Часть III) — не «на всякий», а реальный путь отката базы.
- зависимости: понижение одного пакета может требовать понижения связанных (
aptпокажет список «будут понижены»). Читайте вывод передY. - hold снова: после успешного отката «заморозьте» (
apt-mark hold) пакет, чтобыunattended-upgradesзавтра не поставил ту же битую версию назад.
3. Ядро — только через GRUB и с планировкой
Пакеты ядра не «понижаются» той же командой безопасно: ядро грузится с /boot через загрузчик GRUB. Откат ядра = загрузить старую версию:
# посмотреть, какие ядра стоят
dpkg -l | grep -i 'linux-image' | awk '$2 ~ /linux-image-[0-9]/'
ls -l /boot/vmlinuz-*
В GRUB-меню при загрузке выбирается старое работающее ядро. Держать старое ядро установленным (просто не удаляйте его пакет) — страховка. После успешной загрузки на новом ядре — можно снять страховку.
Для панели VPS без консоли «выбрать ядро при загрузке» (а многие облачные так и дают) — единственный путь, если новое ядро грузится в kernel panic. Поэтому, прежде чем обновлять ядро: узнайте, что есть консоль провайдера (KVM/VNC), и что вы можете через неё выбрать ядро в GRUB-меню. Нет консоли — не обновляйте ядро, не застраховав через snapshot/бэкап диска.
4. autoremove, «осиротевшие» пакеты и мусор
После откатов/переездов между версиями PHP остаются старые .conf-пулы и старые ядра в /boot (закончившееся место на /boot — отдельная частая причина «apt не может поставить обновление»: см. финальную статью про диск). Аккуратно чистим:
sudo apt-get -s autoremove --purge # СНАЧАЛА симуляция: что он собирается удалить
sudo apt autoremove --purge # потом уже реально
sudo apt autoclean # старый кэш пакетов, чтобы /var/cache/apt не раздувалсяОсторожно с autoremove: он может предложить удалить старое ядро, которое ещё нужно как страховка. Симуляция (-s) перед реальным удалением — обязательный шаг.
5. Порядок «сломалось после обновления» — быстрый
# 1) что стоит сейчас и что ставилось (apt history)
grep -i 'Upgrade' /var/log/apt/history.log | tail
# 2) что из живых процессов держит удалённую библиотеку (неприменённое)
sudo needrestart -r l
# 3) какой юнит упал / перезапускается в цикле
systemctl --failed --no-pager ; sudo journalctl -u php8.3-fpm -p err --since -15m
# 4) откатить пакет, который подозревается (apt-get install pkg=ver --allow-downgrades)
# 5) reload затронутого демона, проверить сайт
# 6) apt-mark hold на откатанную версию, чтобы авто не вернуло битую
Чек-лист части
- Понижают версию MariaDB «apt-downgrade» после мажорного апгрейда → БД может не подняться на старой версии из-за нового формата таблиц. Откат базы = восстановление дампа, не apt.
- Удаляют старое ядро `autoremove` «для места» → нечем откатиться, если новое ядро не грузится, а консоли провайдера нет.
- Не читают вывод `apt` перед `Y` при downgrade → понижают зависимости, которые тянут за собой «свал» полстека.
- Забыли `hold` после отката → `unattended-upgrades` ночью снова ставит ту же битую версию.
Инструмент под задачу
После автопатча главный вопрос — «что именно произошло ночью»: разбор apt-истории и логов обновлений по временной метке. Попробуйте инструмент прямо здесь, на своих данных:
Результат анализа:
Чек-лист граблей всей статьи
- Считают `apt upgrade` = «изменения применены» — живые процессы на старом коде, пока не перезапустишь (libc/демоны).
- `unattended-upgrades` на `-updates` «чтобы всё свежее» → мажорные версии без теста.
- `Automatic-Reboot «true»` без окна и мониторинга → «сервер сам перезагрузился в пик».
- Флаг `/var/run/reboot-required` висит неделями → «обновлён только наполовину», старое ядро живёт.
- `hold` на ядре/PHP навсегда, без плана вручную закрыть CVE.
- Мажорную PHP ставят «вместо», а не рядом → нет пути отката за секунды (нет второго сокета).
- `restart` там, где достаточно `reload` (PHP-FPM, nginx) → рвут запросы «на ровном месте».
- Тестируют новую PHP на боевом, переключая весь сервер → не зашло, лежит весь сайт.
- Песочница (`staging.test`) открыта наружу с боевым дампом → утечка.
- `autoremove` без симуляции (`apt-get -s autoremove`) → удалили нужное старое ядро/зависимость.
- Не сделали дамп базы перед мажорным апгрейдом БД → «откатить» базу нечем.
Материал написан простым языком; любой англоязычный термин (apt, пакет, candidate, hold, pin, unattended-upgrades, libc/glibc, reload/restart, CVE, needrestart,
reboot-required, blue-green, staging, GRUB, autoremove, logrotate) объясняется при первом употреблении. Конфиги — для Ubuntu/Debian; имена пакетов и юнитов (php8.2-fpm/php8.3-fpm,ssh/sshd,mariadb/mysql), поведение пост-скриптовapt(«сам делает reload» или нет) и версия PPAondrej/phpсознательно условны — проверяйте фактические черезapt-cache policy,systemctl status,/var/log/apt/history.log. Домены/IP/пароли/версии условные (example.test,staging.test,203.0.113.x— TEST-NET как в №1); подставляйте свои. Правило «не остаться без входа» и «боевой не трогать, пока не проверил на копии» действует во всех частях: песочница не открыта наружу, боевой сокет не переключён, дамп базы снят перед мажорным шагом. Тонкие механизмы (live-patch ядра типаkpatch/livepatch, полностью zero-downtime через репликацию БД, конфигурационное управление через Ansible на флот машин) упомянуты лишь где нужно — это следующий уровень или отдельная разовая тема, когда базовый цикл «патч вовремя + reload где можно + тест на копии + откат» уже выстроен.
👤 Зарегистрируйтесь, чтобы добавить материал в закладки.
