Наследие ручных правок: как найти всё, что разошлось с пакетными конфигами
Сервер достался вам с работающими сервисами и конфигами, в которые кто-то явно лазил руками — но какие строки правлены, а какие остались дефолтными из пакета, непонятно. Проблема всплывает в худший момент: apt upgrade или dnf update либо тихо затирает чужую правку дефолтом, либо оставляет рядом файл с непонятным расширением, а сервис работает на старой версии конфига. Разберём, как за один проход найти все места, где конфиг разошёлся с тем, что принёс пакет, и решить, что из расхождений сохранить, а что выкинуть как мусор.
Содержание
- Debian и Ubuntu: debsums и .dpkg-dist/.dpkg-old
- RHEL-семейство: rpm -Vf и .rpmnew/.rpmsave
- Когда автоматической метки нет: сравниваем руками с эталоном пакета
- Решаем, что из правок сохранить, а что было костылём
- Сливаем правки: vimdiff, meld и защита от --force-confnew
- Процесс на будущее: не разово, а при каждом обновлении
Debian и Ubuntu: debsums и .dpkg-dist/.dpkg-old
Основной инструмент — debsums, он сверяет md5-суммы установленных файлов с теми, что были прописаны в пакете на момент установки:
apt install debsums
debsums -ce
Флаг -c — показывать только изменённые файлы, -e — ограничиться конфигурационными (conffiles). Вывод — прямой список путей, где содержимое разошлось с эталоном, без единого лишнего файла.
Ограничение: debsums работает только для пакетов, у которых сохранился файл контрольных сумм /var/lib/dpkg/info/<пакет>.md5sums. Некоторые пакеты (собранные без dpkg-dev, либо совсем старые) его не имеют — узнать список таких пакетов:
debsums -l
Для файлов из этого списка debsums бесполезен, сравнивать придётся вручную — способ описан в следующем разделе.
Если ставить отдельный пакет не хочется (минимальный контейнер), с dpkg версии 1.17.10 и новее есть встроенная проверка без установки чего-либо:
dpkg -V
# только конфиги конкретного пакета
dpkg -V nginx
По возможностям это чуть беднее debsums, но для быстрой проверки «есть ли вообще расхождения» хватает.
Отдельно от расхождений в содержимом стоит поискать файлы, которые сам dpkg уже оставил как метки конфликта при прошлых обновлениях:
find /etc -name '*.dpkg-dist' -o -name '*.dpkg-old' -o -name '*.dpkg-new'
Три расширения означают разное, и путать их — частая ошибка:
.dpkg-dist— новая версия конфига из пакета, которую dpkg не смог влить в изменённый локальный файл и оставил рядом, не трогая рабочий конфиг. Обновление пакета уже произошло, но вы ещё не смотрели, что изменилось в дефолте..dpkg-old— наоборот, ваша старая (изменённая) версия, которую dpkg заменил новым дефолтом и сохранил на всякий случай рядом. Локальная правка уже потеряна из рабочего файла — если она была нужна, её придётся восстанавливать из этой копии..dpkg-new— временный файл, остающийся только если обновление пакета прервалось (упалdpkgпосреди распаковки); в норме его быть не должно, встретили — доустановите пакет.
Какой из первых двух вариантов получится при конфликте, зависит от политики apt при неинтерактивном обновлении (--force-confold оставляет ваш файл и создаёт .dpkg-dist, --force-confnew — наоборот) — подробнее в разделе про слияние правок ниже.
RHEL-семейство: rpm -Vf и .rpmnew/.rpmsave
Аналог debsums во всех дистрибутивах на rpm — команда rpm -V (verify), которая сравнивает файлы пакета с записанными в базе rpm контрольными суммами, размером, правами и владельцем:
rpm -Vf /etc/nginx/nginx.conf
Флаг -f — определить пакет по имени файла и верифицировать именно его, не весь пакет целиком. Строка вида S.5....T. c /etc/nginx/nginx.conf расшифровывается по колонкам: S — изменился размер, 5 — изменилась md5-сумма содержимого, T — не совпадает время модификации, c в конце — пометка «конфигурационный файл». Точка на месте буквы значит «здесь расхождения нет»: если после c идут одни точки, с точки зрения rpm файл не менялся, даже если mtime «свежий» — например, после touch без реального редактирования.
Проверить пакет целиком, а не один файл:
rpm -V nginx
Проверить вообще всё, что установлено, и отфильтровать только конфиги:
rpm -Va | grep ' c '
Это ощутимо медленнее (rpm -Va перепроверяет каждый файл каждого пакета в системе), поэтому на боевом сервере лучше сначала прогнать по конкретным пакетам, которые интересуют, а -Va оставить на разовую полную инвентаризацию.
Метки конфликта после обновления пакета — как и в Debian, две разные по смыслу:
find /etc -name '*.rpmnew' -o -name '*.rpmsave'
.rpmnew— новый дефолт из обновлённого пакета, положенный рядом, потому что ваш файл был изменён; рабочий конфиг остался прежним. Это происходит для файлов с флагом%config(noreplace)в спеке пакета — а таким флагом помечено большинство значимых конфигов (sshd_config,nginx.conf,httpd.conf), поэтому.rpmnew— самый частый случай..rpmsave— старая (изменённая) версия сохранена, а новый дефолт стал рабочим файлом. Так бывает для конфигов без%config(noreplace), таких меньше, но правка там теряется из рабочего файла молча, если не заметить вовремя.
В отличие от Debian, здесь поведение при конфликте прошито в самом пакете (флаг ставит мейнтейнер при сборке), а не настраивается локальной политикой — гибкости меньше, зато предсказуемее из версии в версию.
| Задача | Debian/Ubuntu | RHEL-семейство | |
|---|---|---|---|
| Найти все изменённые конфиги пакетов | debsums -ce | `rpm -Va \ | grep ' c '` |
| Быстрая проверка без установки утилиты | dpkg -V | rpm -V (уже часть базовой системы) | |
| Кто владеет файлом | dpkg -S /etc/nginx/nginx.conf | rpm -qf /etc/nginx/nginx.conf | |
| Новый дефолт, не влитый в текущий файл | *.dpkg-dist | *.rpmnew | |
| Бэкап старого файла, перезаписанного дефолтом | *.dpkg-old | *.rpmsave | |
| Кто решает, что создаётся при конфликте | политика apt (--force-confold/--force-confnew) | флаг %config(noreplace) в самом пакете |
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверКогда автоматической метки нет: сравниваем руками с эталоном пакета
Метки .dpkg-dist/.rpmnew появляются только в момент обновления пакета. Если пакет не обновлялся с момента правки конфига, никакого файла-подсказки рядом не будет — а узнать, что именно отличается от дефолта, всё равно нужно. Здесь помогает прямое извлечение эталонного файла из пакета, без его установки.
Debian/Ubuntu. Скачиваем .deb (не устанавливая) и достаём из него оригинальный файл:
apt-get download nginx-common
mkdir -p /tmp/pkg-extract && dpkg-deb -x nginx-common_*.deb /tmp/pkg-extract
diff -u /tmp/pkg-extract/etc/nginx/nginx.conf /etc/nginx/nginx.conf
apt-get download требует доступного в /etc/apt/sources.list репозитория с нужной версией пакета — обычно это тот же репозиторий, откуда его ставили.
RHEL-семейство. Аналогично, через dnf download и распаковку rpm без установки:
dnf install -y dnf-plugins-core # если 'dnf download' ещё не доступен
dnf download nginx --destdir /tmp
cd /tmp
rpm2cpio nginx-*.rpm | cpio -idmv ./etc/nginx/nginx.conf
diff -u /tmp/etc/nginx/nginx.conf /etc/nginx/nginx.conf
Оба способа дают честный diff против ровно той версии пакета, что установлена сейчас — сравнение со случайно найденным в интернете «типовым конфигом nginx» ничего не докажет, версии дефолтов отличаются от релиза к релизу.
Решаем, что из правок сохранить, а что было костылём
Список расхождений на руках — дальше самое важное: не восстановить, а рассортировать. Слепо переносить всё в новую версию конфига при обновлении — такая же ошибка, как слепо всё затереть дефолтом.
| Тип правки | Пример | Что делать при апдейте пакета |
|---|---|---|
| Хардненинг безопасности | ssl_protocols TLSv1.2 TLSv1.3; вместо дефолтного набора | Сохранить, но сверить с новым дефолтом — версия пакета могла подтянуть тот же уровень защиты сама |
| Локальные пути и адресация | root /var/www/custom;, свои upstream-блоки | Сохранить всегда — без этого сервис не запустится в этом окружении |
| Обход старого бага конкретной версии | proxy_buffering off;, добавленный под известный баг старой сборки | Проверить changelog пакета: если баг закрыт в новой версии, попробовать вернуть дефолт и снять костыль |
| Тюнинг под конкретное железо | worker_processes 8;, лимиты воркеров под старый CPU | Пересчитать заново под текущее железо, а не копировать значение — параметры удобно посчитать по методике из статьи про расчёт конфигурации сервера под нагрузку |
| Закомментированные эксперименты и мусор | 20 строк закомментированного блока «пробовал так, не взлетело» | Удалить — не несёт функции, только раздувает diff и путает следующего инженера |
Для каждой найденной правки, которую решаете сохранить, стоит зафиксировать не только «оставляем», но и почему — иначе через год это снова станет неопознанной строчкой. Проще всего писать причину прямо рядом с директивой:
# оставлено вручную: без этого таймаута падают долгие выгрузки отчётов
# из 1С, см. тикет INFRA-114 (правлено 2026-08)
proxy_read_timeout 300s;
Это не заменяет полноценную систему версионирования, но резко снижает шанс, что через два обновления пакета кто-то удалит правку просто потому, что не понял, зачем она здесь.
Сливаем правки: vimdiff, meld и защита от --force-confnew
Когда .dpkg-dist/.rpmnew уже лежит рядом с рабочим файлом, задача — не выбрать один из двух файлов целиком, а слить нужные куски. Для интерактивного сравнения удобны построчные дифф-редакторы:
vimdiff /etc/nginx/nginx.conf /etc/nginx/nginx.conf.dpkg-dist
# или, если есть графическое окружение / X-forwarding
meld /etc/nginx/nginx.conf /etc/nginx/nginx.conf.dpkg-dist
В vimdiff расхождения подсвечиваются построчно, do/dp переносят блок между окнами (do — взять из другого окна, dp — отдать в другое окно). После слияния файл-подсказку нужно убрать явно — dpkg и rpm сами их не удаляют, они лежат вечно, если не разобрать руками:
rm /etc/nginx/nginx.conf.dpkg-dist # после того как нужное перенесено
Отдельная ловушка Debian/Ubuntu — какое поведение выбрано при неинтерактивном обновлении (unattended-upgrades, cloud-init). Если явно не настроено, apt может применить --force-confnew и заменить изменённый конфиг дефолтом, сохранив вашу правку лишь в .dpkg-old — а живым станет новый дефолт. Если правка была критичной (например, отключала уязвимый модуль), сервис на время окажется в незащищённом состоянии до следующего ручного вмешательства. Явно закрепить безопасное поведение — оставлять локальный файл и складывать новый дефолт рядом, а не наоборот:
cat >/etc/apt/apt.conf.d/local-confold <<'EOF'
Dpkg::Options {
"--force-confdef";
"--force-confold";
}
EOF
--force-confdef — использовать дефолтное поведение dpkg, если оно однозначно; --force-confold — в спорных случаях сохранять локальную версию и создавать .dpkg-dist. Это не решает слияние автоматически, но гарантирует, что апдейт никогда не сотрёт правку без следа.
Процесс на будущее: не разово, а при каждом обновлении
Разовая инвентаризация закрывает вопрос «что сейчас разошлось», но следующее обновление снова создаст новые .dpkg-dist/.rpmnew. Смысл в том, чтобы проверка расхождений стала обязательным шагом каждого апдейта, а не отдельным расследованием раз в год.
Практический порядок для планового обновления пакета с конфигом:
- До обновления —
debsums -ce(илиrpm -Vf) по интересующему пакету, чтобы знать, что вообще может быть задето. - Обновление пакета как обычно (
apt upgrade/dnf update). - Сразу после — поиск свежих
.dpkg-dist/.rpmnewэтого апдейта (find /etc -newer /var/log/apt/history.log -name '*.dpkg-dist'или аналогично по времени последней транзакции). - Слияние по таблице категорий из раздела выше, с комментарием о причине для каждой сохранённой правки.
- Фиксация итога — если на сервере уже настроен
etckeeper(методика в статье про восстановление истории правок, ссылка выше), достаточно закоммитить результат внятным сообщением; еслиetckeeperещё нет, самое время его завести, пока диффы свежие в голове.
Если серверов с одной и той же болью становится много, локальные правки стоит вынести из ручного режима — держать в репозитории и применять пайплайном, а не точечно по SSH. Подход на примере VPN-сервера разобран в статье про GitOps-подход к конфигам: там конфликт с обновлением решается на уровне процесса, а не постфактум диффом.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли просто удалить все .dpkg-dist/.rpmnew, не разбираясь, раз рабочий конфиг и так работает?
Технически да, сервис не заметит — это просто подсказки на диске, их никто не читает. Но вместе с ними теряется информация о том, что изменилось в новом дефолте пакета, включая возможные security-фиксы. Если нет времени разбирать сейчас — лучше переместить их в отдельную папку с датой, чем стереть.
Debsums показывает расхождение в файле, который я точно не трогал — как так?
Возможные причины: пакет переустанавливался с другой ревизией той же версии, файл менял другой процесс автоматически (служба дописывает в свой конфиг при старте), либо конфиг генерируется шаблонизатором при установке (debconf на этапе postinst) и потому официально отличается от «сырого» файла в пакете. Сверьтесь с dpkg -L <пакет> — не создаётся ли файл динамически.
Что делать, если %config(noreplace) не спас и .rpmsave уже перезаписал мою правку?
Правка не потеряна — она в .rpmsave рядом. Сравните его с новым рабочим файлом через diff и перенесите нужные куски по той же логике категоризации, что и для .dpkg-dist/.rpmnew.
Стоит ли гнаться за нулём расхождений с пакетным дефолтом?
Нет, это не самоцель. Ноль расхождений означает либо сервер без специфики, либо что все правки перенесли в другое место (systemd override, отдельный include-файл, конфиг-менеджмент) — хорошо, если сделано осознанно, но не единственно верный путь.
Инструменты вроде debsums/rpm -V ловят права доступа и владельца или только содержимое?
И то, и другое, но раздельно по колонкам вывода (в rpm -V — буквы U/G/M для владельца/группы/прав, debsums права не проверяет вовсе, только содержимое). Если конфиг с виду не менялся, но сервис не может его прочитать — это отдельная проверка stat на права, а не про содержимое.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →