NTP (Network Time Protocol) — это протокол, по которому компьютер по сети узнает точное время у серверов и плавно подстраивает свои часы к UTC. Он работает поверх UDP, порт 123, и умеет учитывать задержку сети: клиент не просто спрашивает «который час», а измеряет, сколько шел ответ, и вычитает это из поправки.
Содержание
- Зачем синхронизировать время
- Словарь: offset, delay, stratum
- Как NTP считает поправку: 4 метки времени
- Минимальный SNTP-клиент на Python
- Stratum и как устроена иерархия
- NTPv4, SNTP, NTS и PTP: что выбрать
- Настройка chrony в Linux
- Настройка w32time в Windows
- Безопасность NTP-сервера
- Если не получилось
- Выводы
- Где применяется / связь с практикой
- FAQ
Ниже разберем, как NTP считает поправку по четырем меткам времени (с рабочим примером на Python), что такое stratum, чем NTPv4 отличается от SNTP и NTS, как настроить chrony в Linux и w32time в Windows и как не превратить свой сервер времени в усилитель DDoS.
Зачем синхронизировать время
Часы любого компьютера дрейфуют: кварцевый генератор может уходить на секунды в сутки в зависимости от температуры и экземпляра. Расхождение в несколько секунд или минут ломает вполне конкретные вещи:
- Kerberos и вход в домен Active Directory: по умолчанию допустимое расхождение часов — 5 минут, дальше аутентификация отказывает;
- TLS: сертификат «еще не действителен» или «уже истек», если часы клиента ушли;
- логи и трассировка: события с разных серверов нельзя сопоставить по времени;
- распределенные системы, репликация и планировщики задач, где порядок событий важен.
Словарь: offset, delay, stratum
Эти три термина постоянно путают, поэтому сразу разведем их:
| Термин | Что это | Пример |
|---|---|---|
| offset (смещение) | насколько часы клиента отличаются от часов сервера | +2.5 s: сервер впереди на 2,5 секунды |
| delay (задержка) | время пути запроса и ответа по сети туда и обратно, без обработки на сервере | 40 ms |
| stratum (уровень) | «расстояние» сервера от эталонных часов в иерархии | 1 — сервер с GPS-приемником, 2 — его клиент |
| jitter (разброс) | насколько скачут последовательные измерения offset | 0.3 ms |
Как NTP считает поправку: 4 метки времени
Клиент и сервер обмениваются одним пакетом в каждую сторону, и в этом обмене появляются четыре метки:
- T1 — клиент отправил запрос (по часам клиента);
- T2 — сервер получил запрос (по часам сервера);
- T3 — сервер отправил ответ (по часам сервера);
- T4 — клиент получил ответ (по часам клиента).
Из них клиент вычисляет:
- offset = ((T2 — T1) + (T3 — T4)) / 2
- delay = (T4 — T1) — (T3 — T2)
Прогоним руками. Часы сервера впереди на 2,5 с, путь в каждую сторону — 20 мс, сервер обрабатывает запрос 1 мс. T1 = 10.000, T2 = 12.520, T3 = 12.521, T4 = 10.041. Тогда offset = (2.520 + 2.480) / 2 = 2.5 с, delay = 0.041 — 0.001 = 0.040 с. Поправка найдена точно.
Граница модели: формула предполагает, что путь туда и обратно занимает одинаковое время. Если запрос идет 30 мс, а ответ 10 мс (T2 = 12.530, T3 = 12.531), offset получится 2.51 с — ошибка ровно в половину асимметрии, (30 — 10) / 2 = 10 мс. Измерить асимметрию изнутри одного обмена нельзя, поэтому NTP берет несколько серверов и много замеров и доверяет тем, у кого delay меньше.
Минимальный SNTP-клиент на Python
Скрипт ниже запускает на localhost учебный «сервер» с часами, которые спешат на 2,5 с, отправляет ему 48-байтный пакет NTPv4 и вычисляет offset и delay по тем же формулам. Сеть и права root не нужны: сервер слушает свободный порт, а не 123.
import socket
import struct
import threading
import time
NTP_DELTA = 2208988800 # секунды между 1900-01-01 и 1970-01-01
SERVER_SKEW = 2.5 # часы учебного сервера спешат на 2,5 с
def to_ntp(t):
"""Unix-время (float) -> 64-битная метка NTP: секунды + доля."""
sec = int(t) + NTP_DELTA
frac = int((t - int(t)) * 2**32)
return sec, frac
def from_ntp(sec, frac):
return sec - NTP_DELTA + frac / 2**32
def server(sock):
data, addr = sock.recvfrom(48)
t2 = time.time() + SERVER_SKEW # T2: прием запроса
t1_sec, t1_frac = struct.unpack("!II", data[40:48])
t3 = time.time() + SERVER_SKEW # T3: отправка ответа
reply = struct.pack(
"!BBbb11I",
(0 << 6) | (4 << 3) | 4, # LI=0, VN=4, Mode=4 (server)
2, 6, -20, # stratum, poll, precision
0, 0, 0, # root delay, root dispersion, refid
*to_ntp(t3), # reference timestamp
t1_sec, t1_frac, # origin = T1 клиента
*to_ntp(t2), # receive = T2
*to_ntp(t3), # transmit = T3
)
sock.sendto(reply, addr)
srv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
srv.bind(("127.0.0.1", 0)) # свободный порт вместо 123: не нужен root
threading.Thread(target=server, args=(srv,), daemon=True).start()
cli = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
cli.settimeout(2)
t1 = time.time()
request = struct.pack("!B39x", (0 << 6) | (4 << 3) | 3) # Mode=3 (client)
t1_ntp = to_ntp(t1)
request += struct.pack("!II", *t1_ntp) # transmit = T1
cli.sendto(request, srv.getsockname())
reply, _ = cli.recvfrom(48)
t4 = time.time()
if len(reply) < 48:
raise ValueError("слишком короткий пакет")
fields = struct.unpack("!BBbb11I", reply)
li_vn_mode, stratum = fields[0], fields[1]
if li_vn_mode & 7 != 4 or stratum == 0 or li_vn_mode >> 6 == 3:
# не ответ сервера, Kiss-o'-Death (stratum 0) или LI=3: сервер не синхронизирован
raise ValueError("ответ нельзя использовать для времени")
if tuple(fields[9:11]) != t1_ntp: # origin должен совпасть с нашим T1
raise ValueError("чужой или поддельный ответ")
t2 = from_ntp(fields[11], fields[12])
t3 = from_ntp(fields[13], fields[14])
offset = ((t2 - t1) + (t3 - t4)) / 2
delay = (t4 - t1) - (t3 - t2)
print(f"version={li_vn_mode >> 3 & 7} mode={li_vn_mode & 7} stratum={stratum}")
print(f"offset = {offset:+.3f} s")
print(f"delay = {delay * 1000:.3f} ms")
Вывод на Python 3.14 (delay на вашей машине будет другим, это доли миллисекунды на localhost):
version=4 mode=4 stratum=2
offset = +2.500 s
delay = 0.054 ms
Что важно в коде:
- первый байт пакета упаковывает три поля: LI (предупреждение о секунде координации), VN (версия, 4) и Mode (3 — клиент, 4 — сервер);
- метка времени — 64 бита: 32 бита секунд с 1 января 1900 года и 32 бита доли секунды;
- ответ с неверным режимом, stratum 0 (Kiss-o’-Death — сервер просит снизить частоту запросов или не обращаться к нему) или LI=3 для времени не используют;
- сервер копирует T1 клиента в поле origin, и клиент сверяет его со своим. Это отсекает случайные и «слепые» подложные ответы, но не является аутентификацией: атакующий, который видит трафик, подделает и origin. Защиту дает NTS (ниже).
Это SNTP-логика одного замера. Настоящий демон делает замеры регулярно, фильтрует выбросы и подстраивает частоту часов, а не просто прибавляет offset.
Типичная ошибка: забыть про эпоху 1900 года
Секунды в пакете NTP отсчитываются от 1900 года, а Unix-время — от 1970. Если прочитать их как Unix-время:
from datetime import datetime, timezone
ntp_seconds = 4_000_000_000 # секунды из поля метки NTP
wrong = datetime.fromtimestamp(ntp_seconds, timezone.utc)
right = datetime.fromtimestamp(ntp_seconds - 2208988800, timezone.utc)
print("как Unix-время:", wrong)
print("с поправкой :", right)
как Unix-время: 2096-10-02 07:06:40+00:00
с поправкой : 2026-10-03 07:06:40+00:00
Ошибка — 2208988800 секунд, то есть 70 лет с учетом 17 високосных дней. Исправление — вычитать константу 2208988800. Отсюда же «проблема 2036 года»: 32-битный счетчик секунд NTP переполнится в феврале 2036-го, и реализации должны корректно обрабатывать смену эры.
Stratum и как устроена иерархия
- Stratum 0 — эталонные часы: GPS/ГЛОНАСС-приемник, атомные часы. В сети NTP они не участвуют, подключаются к серверу напрямую.
- Stratum 1 — сервер, к которому подключены эталонные часы.
- Stratum 2, 3… — серверы, синхронизированные с уровнем выше; каждый переход добавляет единицу.
- Максимум — 15. Значение 16 означает «не синхронизирован». Поле в пакете восьмибитное, но значения выше 16 зарезервированы, так что «256 уровней» в старых пересказах — ошибка.
Stratum — это не показатель точности. Сервер stratum 2 в соседней стойке нередко дает время точнее, чем stratum 1 через океан, потому что меньше и стабильнее delay.
Из нескольких источников NTP выбирает «правдивых» алгоритмом пересечений (развитие алгоритма Марзулло): интервалы [offset — погрешность, offset + погрешность] должны перекрываться у большинства. Поэтому рекомендуют от четырех источников: при двух непонятно, кто врет, а при трех один сбойный уже выявляется, но отказ еще одного оставляет систему без запаса.
NTPv4, SNTP, NTS и PTP: что выбрать
| Вариант | Что это | Когда брать |
|---|---|---|
| NTPv4 (RFC 5905) | полный протокол: много источников, фильтрация, дисциплина частоты часов | серверы, рабочие станции, свой сервер времени для сети |
| SNTP | тот же формат пакета, но упрощенный клиент, обычно один источник без сложной фильтрации | встраиваемые устройства, простые клиенты; например, systemd-timesyncd |
| NTS (RFC 8915) | Network Time Security: согласование ключей по TLS (TCP 4460), дальше NTP-пакеты с проверкой подлинности | когда важна защита от подмены времени и сервер NTS поддерживает |
| PTP (IEEE 1588) | другой протокол с аппаратными метками времени в сетевых картах и коммутаторах | нужна точность в микросекунды и лучше: биржи, телеком, промышленность |
Типичная точность NTP через интернет — единицы или десятки миллисекунд, в хорошей локальной сети — доли миллисекунды. Это ориентир, а не гарантия: она зависит от асимметрии маршрута, нагрузки и качества источников.
Настройка chrony в Linux
chrony — основной NTP-демон в RHEL-семействе и во многих других дистрибутивах; классический ntpd из пакета ntp постепенно уходит (где-то его заменяет ntpsec). Пути ниже — для Debian/Ubuntu (/etc/chrony/chrony.conf); в RHEL/Fedora файл лежит в /etc/chrony.conf, служба называется chronyd.
В Ubuntu с версии 25.10 chrony стоит по умолчанию на новых установках (при обновлении с 25.04 может остаться systemd-timesyncd). Штатные источники там описаны в /etc/chrony/sources.d/ubuntu-ntp-pools.sources и работают через NTS, поэтому кроме UDP 123 нужен исходящий TCP 4460. Если сеть его не пропускает, chrony с конфигурацией по умолчанию часы не подстроит.
Сначала сохраните исходный конфиг под уникальным именем и запомните путь — это и есть точка отката:
sudo apt install chrony
CONF=/etc/chrony/chrony.conf
BACKUP="$CONF.bak-$(date +%F-%H%M%S)"
test ! -e "$BACKUP" && sudo cp -p "$CONF" "$BACKUP" && echo "копия: $BACKUP"
Минимальная клиентская конфигурация:
# /etc/chrony/chrony.conf (фрагмент)
pool ru.pool.ntp.org iburst maxsources 4
driftfile /var/lib/chrony/chrony.drift
makestep 1.0 3
rtcsync
poolберет несколько серверов из DNS-пула;iburstна старте посылает серию быстрых запросов, чтобы синхронизироваться за секунды, а не минуты;driftfileхранит измеренную скорость ухода часов, чтобы после перезагрузки не начинать с нуля;makestep 1.0 3разрешает «прыжок» часов, если расхождение больше 1 с, но только в первых трех обновлениях; дальше время подстраивается плавно, без скачков назад;rtcsyncпериодически копирует системное время в аппаратные часы.
Применение и проверка:
sudo systemctl restart chrony
chronyc sources -v
chronyc tracking
В chronyc sources символ ^* отмечает выбранный источник, ^+ — пригодные для усреднения, ^- — отброшенные, ^? — недоступные или непроверенные. В tracking смотрите строку System time (текущее смещение) и Leap status (должно быть Normal). Откат: sudo cp -p "$BACKUP" "$CONF", затем снова restart и chronyc tracking.
Раздавать время в локальную сеть
По умолчанию chrony работает только клиентом. Чтобы машина стала сервером для своей подсети, добавьте allow с конкретной сетью, а не для всех:
# сервер времени для офисной подсети (адрес - пример)
allow 192.168.10.0/24
# в изолированной сети без интернета: раздавать свои часы, пометив их stratum 10
local stratum 10
local stratum 10 — честная замена старому совету server 127.127.1.0 из ntpd: без внешних источников ваш сервер не становится точным, он лишь дает сети общее время. Клиенты при этом будут одинаково неправы, но согласованы между собой.
Настройка w32time в Windows
Компьютеры в домене Active Directory получают время по иерархии домена, и вручную их трогать не нужно: внешние источники настраивают только на контроллере с ролью PDC-эмулятора корневого домена леса. Для отдельной машины или этого контроллера (команды в cmd от администратора):
w32tm /config /manualpeerlist:"0.ru.pool.ntp.org,0x8 1.ru.pool.ntp.org,0x8" /syncfromflags:manual /update
w32tm /resync
w32tm /query /status
w32tm /query /peers
Флаг 0x8 — запросы в клиентском режиме. /query /status показывает источник, stratum и время последней синхронизации. Если /resync пишет, что данные о времени недоступны, проверьте, открыт ли исходящий UDP 123 на межсетевом экране.
Безопасность NTP-сервера
Amplification. Старые версии ntpd отвечали на служебный запрос monlist списком последних клиентов — ответ в сотни раз больше запроса. Атакующий подделывал адрес жертвы, и открытые NTP-серверы заливали ее трафиком (CVE-2013-5211). Что делать:
- не держать публично открытый сервер, если он не нужен; раздавать время только своим подсетям (
allowв chrony); - в ntpd — актуальная версия и ограничения
restrict default kod limited nomodify notrap nopeer noquery, гдеnoqueryзакрывает служебные запросы, аlimitedиkodрежут частые запросы; - на межсетевом экране пропускать UDP 123 внутрь только из доверенных сетей.
Подмена времени. Обычный NTP не аутентифицирует сервер: атакующий на маршруте может сдвинуть часы клиента и, например, «оживить» отозванный сертификат. Защита — NTS там, где сервер его поддерживает (server <имя> iburst nts в chrony 4.x), несколько независимых источников и makestep только на старте, чтобы время нельзя было резко сдвинуть в работе. Это снижает риск, но не делает синхронизацию неуязвимой: доступность UDP-трафика все равно можно нарушить.
Если не получилось
chronyc sourcesпуст или все^?— закрыт исходящий UDP 123 (а для NTS-источников, как в Ubuntu 25.10+, еще и TCP 4460) или не резолвится DNS пула; проверьте межсетевой экран,chronyc -N authdataдля NTS иgetent hosts ru.pool.ntp.org.- Время не прыгает к правильному — смещение больше порога
makestep, а лимит обновлений исчерпан; однократно выполнитеsudo chronyc makestep. - Две службы спорят за часы — одновременно работают systemd-timesyncd и chrony;
timedatectl statusпокажет активную, лишнюю отключите. - Виртуальная машина уходит после паузы — гипервизор тоже синхронизирует время гостя; оставьте один механизм.
Выводы
- NTP вычисляет offset и delay по четырем меткам времени; точность ограничена асимметрией маршрута, поэтому нужны несколько источников, лучше от четырех.
- Stratum — расстояние от эталона (1-15, 16 = не синхронизирован), а не мера точности.
- SNTP — упрощенный клиент того же протокола, NTS добавляет проверку подлинности, PTP нужен для микросекундной точности.
- В Linux практичный выбор — chrony (
pool ... iburst,makestepна старте,allowтолько для своих сетей), в Windows — w32tm, в домене AD — иерархия от PDC-эмулятора. - Публичный NTP-сервер без ограничений — риск amplification-атак; закрывайте служебные запросы и фильтруйте UDP 123.
Где применяется / связь с практикой
Освойте тему на практике
Синхронизация времени — одна из базовых сетевых служб, которую сетевой инженер настраивает на маршрутизаторах, коммутаторах и серверах наравне с DNS и DHCP: без нее не сходятся логи, отваливаются аутентификация и сертификаты. Сетевые протоколы, UDP-сервисы и их защиту системно разбирают на курсе «Сетевой инженер. Базовый уровень». Отдельные темы по сетям и администрированию можно посмотреть на открытых уроках Otus.
FAQ
Какой порт использует NTP?
UDP 123 для самих запросов времени. NTS дополнительно использует TCP 4460 для согласования ключей.
Чем NTP отличается от команды date, которой можно выставить время?
date один раз ставит часы по вашему вводу. NTP-демон постоянно измеряет смещение и подстраивает частоту хода часов, поэтому время остается точным, а не только в момент установки.
Что делать с секундой координации?
Демоны получают о ней предупреждение через поле LI и отрабатывают ее сами. Некоторые крупные провайдеры вместо вставки секунды «размазывают» ее на сутки (у Google — линейно с полудня до полудня UTC) — смешивать такие источники с обычными не рекомендуют.



