Библиотека — Linux — время и синхронизация: chrony, timesyncd, RTC [X]
Шифр
linux-time-sync
Разделы
linux tooling
Сверено
1 сентября — в этот день утверждения статьи сверялись с первоисточниками, а не правился текст
Объём
18 386 знаков · 16 разделов · чтения около 20 минут

Linux — время и синхронизация: chrony, timesyncd, RTC


Обзор

Время — та зависимость, которую замечают только когда она ломается, и ломается она не там, где её ищут. Сертификат отвергается с формулировкой «ещё не действителен», хотя выдан вчера. Код из приложения-аутентификатора не подходит, хотя вводится верно. Логи с двух машин при разборе инцидента не сходятся, и восстановить порядок событий невозможно. Во всех трёх случаях виноваты часы, но ни одно сообщение об ошибке этого не говорит.

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

Статья разбирает эти механизмы, два демона синхронизации и, отдельно, диагностику — потому что чтение вывода chronyc требует понимания, что означают его поля, а без этого он выглядит набором чисел.

Версии-якоря: chrony 4.9 (август 2026), systemd 261.

Что такое «время» в системе

RTC и системные часы

Аппаратные часы (RTC, real-time clock) — микросхема на плате с собственным питанием от батарейки. Их единственная задача — помнить время, пока машина выключена. Ядро читает их один раз при загрузке, устанавливает по ним системные часы и дальше к ним практически не обращается: точность RTC невелика, разрешение обычно в секундах.

Системные часы ведёт ядро, отсчитывая от прочитанного при старте значения с помощью счётчика процессора. Именно они возвращаются программам, и именно их правит демон синхронизации.

В виртуальных машинах и на облачных VPS картина другая: RTC там эмулируется гипервизором, а точность после миграции или паузы гостя может быть какой угодно. На таких машинах демон синхронизации не «желателен для точности», а обязателен для работоспособности.

Отдельный случай — машины вовсе без батарейки RTC (одноплатники, часть встраиваемых устройств). Для них systemd-timesyncd держит файл /var/lib/systemd/timesync/clock и при каждой успешной синхронизации обновляет его время модификации; при следующей загрузке системные часы подтягиваются как минимум к этой отметке. Точным временем это не является, но обеспечивает главное — время не идёт назад между перезагрузками, а с ним не ломаются проверки сроков действия и сборка по временным меткам.

UTC или локальное время в RTC

В самих часах не записано, в какой шкале они идут — это соглашение, которое операционная система должна знать заранее. В Linux оно хранится третьей строкой файла /etc/adjtime: UTC или LOCAL.

Правильный ответ — UTC, и он же по умолчанию. Локальное время в RTC создаёт проблему, точно описанную в документации hwclock: если система работает в момент перехода на летнее время, записанное в RTC значение будет скорректировано; если система в этот момент была выключена — прочитанное из RTC значение скорректировано не будет. Одна и та же машина получает разное время в зависимости от того, работала ли она в ночь перехода.

Классический источник боли — дуалбут с Windows, которая исторически держит RTC в локальном времени. Симптом узнаваем: после каждой смены системы часы уезжают ровно на величину смещения пояса. Правильное лечение — приучить Windows к UTC (параметр реестра RealTimeIsUniversal), а не переводить Linux на LOCAL: во втором случае вы получаете описанную выше несогласованность в обмен на видимость порядка.

Проверить и переключить:

timedatectl                              # строка «RTC in local TZ»
timedatectl set-local-rtc 0              # перевести RTC в UTC

Какие часы читать: REALTIME против MONOTONIC

Ядро отдаёт несколько разных часов, и выбор между ними — не оптимизация, а вопрос корректности.

Часы Может ли прыгнуть Учитывает NTP-подстройку Идёт во время suspend
CLOCK_REALTIME да, назад и вперёд да да
CLOCK_MONOTONIC нет да, плавно нет
CLOCK_MONOTONIC_RAW нет нет нет
CLOCK_BOOTTIME нет да, плавно да

CLOCK_REALTIME — настоящее календарное время, и его можно переставить: администратором, демоном синхронизации при шаге, гипервизором. Для «который час» это правильный источник и единственно возможный.

Для измерения длительности он не годится совсем. Классическая ошибка — засечь CLOCK_REALTIME до операции и после, вычесть. Если в промежуток попал шаг часов назад, разность получится отрицательной; если вперёд — операция «займёт» лишние секунды. Код, где таймаут вычисляется таким способом, при переводе часов либо срабатывает мгновенно, либо не срабатывает никогда, и воспроизвести это в тестах почти невозможно. Для длительностей нужен CLOCK_MONOTONIC: он не переставляется никогда.

Две тонкости, которые обычно упускают. Во-первых, CLOCK_MONOTONIC всё же не равномерен: он подвержен плавным подстройкам частоты от adjtime() и NTP — просто никогда не идёт назад. Если нужна именно аппаратная шкала без вмешательства демона синхронизации, есть CLOCK_MONOTONIC_RAW. Во-вторых, CLOCK_MONOTONIC не считает время, проведённое системой в suspend: на ноутбуке, закрытом на ночь, он «простоит» эту ночь. Если требуется монотонность с учётом сна — CLOCK_BOOTTIME.

Демоны синхронизации

В Linux сегодня два реальных кандидата, и разница между ними принципиальная, а не в удобстве.

systemd-timesyncd реализует только SNTP — упрощённый клиентский протокол. Он опрашивает сервер, получает время, правит часы. Ни серверной части, ни сложной дисциплины часов, ни работы с несколькими источниками одновременно у него нет, и документация systemd это оговаривает прямо: сценарии, требующие полноценного NTP, им не покрываются.

chrony — полная реализация NTP: несколько источников с оценкой качества каждого, отсев источников с неверным временем, статистическая фильтрация, собственная модель дрейфа часов, режим сервера для других машин, аппаратные штампы времени и NTS.

systemd-timesyncd chrony 4.9
Протокол SNTP (клиент) NTP полностью
Несколько источников одновременно нет да, с комбинированием
Отсев источника с неверным временем нет да (falseticker)
Модель дрейфа часов нет да, компенсирует уход между опросами
Работа сервером для парка машин нет да
Плохая сеть, редкая связь, ноутбук посредственно целевой сценарий
Диагностика timedatectl show-timesync chronyc tracking, sources, sourcestats

Когда чего хватает. Для десктопа или ноутбука, которому нужно «примерно верное время», timesyncd достаточен, и он уже установлен. Для сервера — chrony, и причина не в точности как таковой, а в отсеве плохих источников: SNTP-клиент верит единственному серверу, к которому обратился, и если тот отдаёт неверное время, поедет вслед за ним без единого возражения. Для машины, выдающей TOTP-коды, подписывающей что-либо или участвующей в Kerberos, это неприемлемый режим.

Одновременно запускать оба нельзя — они будут драться за системные часы. Установка chrony в большинстве дистрибутивов маскирует systemd-timesyncd автоматически, но проверить стоит: systemctl status systemd-timesyncd должен показывать masked или inactive.

Дисциплина часов: подтягивать или переставлять

Исправить отставшие часы можно двумя способами, и они не равноценны.

Slew (подтягивание) — изменить скорость хода: часы идут чуть быстрее, пока не догонят. Время при этом остаётся монотонным, ни одна отметка не повторяется и не пропадает. Плата — медленно: скорость подтягивания ограничена, и на Linux chronyd может править частоту в пределах до 100000 ppm (10%), то есть даже теоретический максимум даёт лишь несколько секунд в минуту, а реальный предел задаётся директивой maxslewrate и обычно много ниже.

Step (шаг) — переставить часы одномоментно. Быстро, но время скачет: при шаге назад отметки повторяются, при шаге вперёд возникает разрыв. Всё, что опирается на монотонность CLOCK_REALTIME, в этот момент может сломаться — сработать разом или зависнуть.

Отсюда правило: подтягивать всегда, переставлять только на старте, пока прикладной софт ещё не запущен. Именно это и делает директива chrony:

makestep 1.0 3

Первый аргумент — порог в секундах, второй — предел по числу обновлений. Читается так: переставить часы шагом, если расхождение больше секунды, но только в первых трёх обновлениях после запуска chronyd. Дальше — только плавное подтягивание. Документация chrony формулирует смысл прямо: на большинстве систем часы желательно переставлять только при загрузке, до старта программ, которые полагаются на монотонное движение времени вперёд.

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

Выбор источников

Источник времени задаётся директивами server (конкретный хост) и pool (имя, за которым скрывается набор адресов):

pool 2.pool.ntp.org iburst

iburst ускоряет первую синхронизацию, отправляя серию запросов вместо одного, — на старте это разница между секундами и минутами.

Stratum — расстояние от эталона: stratum 1 берёт время напрямую от источника (GPS, атомные часы), stratum 2 синхронизирован по stratum 1 и так далее. Обращаться к stratum 1 без надобности не нужно: их немного, они перегружены, а разница для обычной машины неразличима.

Для парка машин правильная схема — один-два собственных NTP-сервера, синхронизированных наружу, и все остальные машины по ним. Это даёт две вещи: во-первых, машины парка согласованы между собой, что важнее абсолютной точности, когда вы сравниваете их логи; во-вторых, наружу ходят двое, а не сотня.

Диагностика

Что показывает chronyc

chronyc tracking

Из вывода практически значимы четыре величины.

Last offset — расхождение на последнем обновлении. Мгновенное значение, скачет; само по себе мало о чём говорит.

RMS offset — усреднённое расхождение за длительный срок. Вот это и есть ответ на вопрос «насколько точны часы»: значения в единицы миллисекунд для машины в интернете нормальны, устойчивые сотни миллисекунд означают проблему.

Frequency — на сколько ppm часы уходили бы без коррекции. Это свойство железа, а не сети. Величина в единицы-десятки ppm обычна; сотни означают либо плохой генератор, либо, чаще, виртуализацию.

Root dispersion вместе с Root delay дают верхнюю оценку погрешности: сумма накопленной по цепочке неопределённости и половины сетевой задержки. Если требуется утверждение «наше время верно с точностью до X» — оно строится отсюда, а не из Last offset.

chronyc sources

Первые два символа строки — самое информативное в выводе. Первый обозначает тип источника (^ — сервер, = — peer, # — локальные часы), второй — состояние выбора:

Символ Значение
* выбран основным источником
+ участвует в комбинировании с основным
- пригоден, но не выбран
x falseticker: время расходится с большинством остальных
~ слишком большой разброс измерений
? непригоден: недоступен, не синхронизирован или мало измерений

x напротив источника — не мелочь, а сообщение о том, что кто-то отдаёт неправильное время; именно эту ситуацию SNTP-клиент распознать не способен в принципе.

Колонка Reach читается как восьмеричное число: каждый бит — успех или неуспех одного из последних восьми опросов. 377 — все восемь удались, 0 — источник недоступен, промежуточные значения означают потери. Это самый быстрый способ отличить «сервер отвечает через раз» от «сервер не отвечает вообще».

timedatectl

timedatectl                        # общая картина: пояс, RTC, статус синхронизации
timedatectl show-timesync --all    # детали, если работает timesyncd
timedatectl set-ntp true           # включить синхронизацию

Строка System clock synchronized: yes означает лишь, что демон однажды успешно синхронизировался, а не что часы верны прямо сейчас. Для реального ответа нужен chronyc tracking.

Симптом → причина

Что видно Где искать
TLS: сертификат «ещё не действителен» или «истёк», хотя срок в порядке часы клиента ушли; сверить timedatectl с внешним источником
TOTP-коды не принимаются, хотя вводятся верно расхождение больше окна (обычно ±30 с) на любой из двух сторон
Kerberos: «clock skew too great» расхождение больше допуска (по умолчанию 5 минут)
Логи двух машин не сходятся по порядку событий машины синхронизированы с разными источниками или одна не синхронизирована вовсе
Таймер systemd отработал дважды или пропустил запуск переход на летнее время в локальном поясе
Часы уезжают, несмотря на работающий демон chronyc sources: все источники ? или x, либо блокируется UDP/123
Огромный Frequency, часы плывут после каждой паузы виртуализация без корректной синхронизации с гипервизором

Часовые пояса

Пояс — это слой отображения поверх системных часов: ядро оперирует UTC, а преобразование в локальное время делает библиотека по данным из tzdata. /etc/localtime — символьная ссылка на файл зоны в /usr/share/zoneinfo.

timedatectl list-timezones | grep Moscow
timedatectl set-timezone Europe/Moscow

Практическое следствие: tzdata — это данные, которые устаревают. Правила перехода на летнее время и смещения поясов меняются решениями правительств, иногда за недели до вступления в силу. Машина с неактуальным пакетом tzdata показывает верное UTC и неверное локальное время, причём молча. Для сервера это ещё один довод держать всё в UTC: серверу локальное время нужно ровно в одном месте — при показе человеку.

Переход на летнее время — регулярный источник багов в расписаниях. Осенью час повторяется, весной час не существует; задание, назначенное на этот час, соответственно либо выполняется дважды, либо не выполняется. Для systemd-таймеров лечение простое: OnCalendar= понимает суффикс UTC.

OnCalendar=*-*-* 02:30:00 UTC

Для заданий, которые обязаны идти именно раз в сутки, это правильный выбор; привязка к локальному времени оправдана только там, где смысл задания связан с человеческим распорядком.

Отдельная категория — високосная секунда: вставка лишней секунды в UTC для согласования с вращением Земли. Для системы это разрыв монотонности CLOCK_REALTIME со всеми вытекающими, и крупные инфраструктуры давно обходят его «размазыванием» секунды по суткам (leap smearing). Резолюцией 4 Генеральной конференции по мерам и весам 2022 года принято решение прекратить добавление високосных секунд — не позднее 2035 года; окончательные детали ещё дорабатываются, и до тех пор секунды добавляются по прежним правилам. Практический вывод на сегодня прежний: если инфраструктура зависит от непрерывности времени, источники NTP должны быть согласованы в том, как они этот момент обрабатывают — смешивать «размазывающие» и обычные серверы нельзя.

Сравнение с FreeBSD

В базовой системе FreeBSD лежит ntpd — референсная реализация от ntp.org, та самая, на смену которой chrony пришёл в большинстве Linux-дистрибутивов. Включается он в rc.conf:

ntpd_enable="YES"
ntpd_sync_on_start="YES"

Второй переключатель — прямой аналог решения задачи «на старте часы могут быть далеко»: он запускает ntpd с флагом -g, разрешающим однократную большую коррекцию при запуске. Документация rc.conf(5) называет его предпочтительной заменой утилите ntpdate, которая выводится из состава дистрибутива NTP. Если вы встречаете совет «добавьте ntpdate в загрузку» — он устарел.

Различия по существу:

  • Идеология та же, что и везде во FreeBSD: в базе лежит рабочий и консервативный вариант, а более современный инструмент берётся из портов. chrony есть в портах и работает так же, как в Linux, включая конфигурацию.
  • Предел скорости подтягивания у ядра ниже: 5000 ppm против 100000 ppm в Linux, причём, как отмечает документация chrony, установка maxslewrate во FreeBSD в диапазоне от 500 до 5000 ppm фактически даёт 500 ppm. Практического значения для обычной эксплуатации это не имеет, но объясняет, почему сильно ушедшие часы там подтягиваются дольше.
  • Управление RTC — через sysctl machdep.wall_cmos_clock и machdep.adjkerntz вместо /etc/adjtime; смысл настройки тот же, и правильный ответ тот же — держать RTC в UTC.
  • Слой часовых поясов идентичен: тот же tzdata, тот же /etc/localtime, настраивается через tzsetup.

Источники

  • chrony documentation — makestep, maxslewrate, поля tracking и легенда sources
  • chrony NEWS — состав релизов, версия 4.9
  • systemd-timesyncd.service(8) — «implements SNTP only», файл clock для машин без RTC
  • clock_gettime(2) — поведение CLOCK_REALTIME, CLOCK_MONOTONIC, CLOCK_MONOTONIC_RAW, CLOCK_BOOTTIME
  • hwclock(8) — /etc/adjtime, несогласованность локального времени в RTC при переходе на летнее
  • rc.conf(5), FreeBSD — ntpd_sync_on_start и статус ntpdate
  • Резолюции CGPM 2022 — решение о прекращении добавления високосных секунд

Связанные заметки

Из библиотеки института уносят только знания. Читателей здесь не считают: посещение не регистрируется, отметок в издании не остаётся, счётчиков нет ни у одного издания фонда.

Издание пополняется и пересверяется. Текст доступен на условиях CC BY-SA 4.0 — унесённое можно передавать дальше на тех же условиях.