Chrony : le cœur du système NTP/NTS#
Pourquoi#
Tout ce qui précède dans ce guide, le PPS câblé, le noyau temps réel, les
IRQ isolées sur des cœurs dédiés, n’existe que pour nourrir un seul
programme : chronyd. C’est lui qui reçoit les impulsions PPS et les
trames GNSS, filtre le bruit de mesure, discipline l’horloge système, et
sert le temps aux clients via NTP et NTS. Tout le reste de l’installation
est de la plomberie au service de ce démon.
chrony (par opposition au ntpd historique) est le choix retenu ici pour
plusieurs raisons concrètes : une convergence plus rapide dans les
conditions imparfaites (redémarrage, connexion intermittente), un support
natif de NTS (chiffrement du protocole NTP, RFC 8915), une base de code
nettement plus petite et auditable (~30 000 lignes contre ~120 000 pour
ntpd), et un sandboxing seccomp activé par défaut sur les distributions
récentes.
Comment#
La source PPS : le cœur du cœur#
refclock PPS /dev/pps0 lock GPS poll 2 filter 16 precision 1e-7 \
rate 1 width 0.5 maxlockage 10 preferDécomposons chaque option :
| Option | Signification |
|---|---|
/dev/pps0 | Le device exposé par le module kernel pps-gpio |
lock GPS | N’utilise le PPS que si la source GPS (voir plus bas) confirme un fix valide, évite de discipliner l’horloge sur une impulsion sans repère temporel |
poll 2 | Intervalle entre échantillons : 2² = 4 secondes |
filter 16 | Médiane statistique sur les 16 derniers échantillons : c’est ce filtre qui transforme un jitter brut de plusieurs microsecondes en RMS offset effectif autour de la microseconde |
precision 1e-7 | Précision de cette refclock, en secondes décimales du recepteur GPS, ici arrondi à 100 ns |
maxlockage 10 | Tolère jusqu’à 10 absences de confirmation GPS avant de “délocker” |
prefer | Cette source est favorisée dans l’algorithme de sélection si elle est disponible |
refclock SHM 0 refid GPS offset 0.125 noselect poll 4Cette seconde ligne reçoit les trames GNSS (position, date) via gpsd, en
mémoire partagée (shared memory, canal 0). Elle ne sert qu’à lever
l’ambiguïté de seconde du PPS (lock GPS ci-dessus), la directive
noselect l’exclut explicitement de tout rôle de source de temps propre :
la précision de l’horloge repose uniquement sur le PPS. offset 0.125
compense le délai moyen entre l’instant réel du point GNSS et la réception
de la trame NMEA correspondante sur le port série (une trame texte prend un
temps non nul à être émise puis parsée), une valeur déterminée
empiriquement, sans conséquence sur la précision finale puisque cette
source n’est jamais sélectionnée pour l’horodatage lui-même.
Les sources de secours#
server ntp-p1.obspm.fr minpoll 6 iburst xleave
server ptbtime4.ptb.de minpoll 6 iburst xleave
server ntp1.npl.co.uk minpoll 6 iburst xleave
server ntp1.sp.se minpoll 6 iburst xleave
server ntp2.oma.be minpoll 6 iburst xleave
server time.nist.gov minpoll 6 iburst xleaveMême un Stratum 1 GNSS garde des sources amont classiques, pour deux
raisons : détecter une dérive silencieuse du PPS (un GPS qui continue
d’émettre des impulsions régulières mais légèrement décalées, par exemple à
cause d’un problème d’antenne), et fournir un filet de sécurité en cas de
panne GNSS totale. xleave (mode interleaved, RFC 5905) réduit le biais
lié à l’asymétrie du réseau ; minpoll 6 fixe l’intervalle minimal
d’interrogation à 2⁶ = 64 secondes, l’étiquette à respecter pour toute
source institutionnelle (chapitre Performances), pas plus qu’une requête
par minute. Le choix des six lignes ci-dessus privilégie des laboratoires
nationaux de métrologie plutôt qu’un pool anonyme :
ntp-p1.obspm.fr(France, LNE-SYRTE / Observatoire de Paris) offre un ancrage institutionnel directement rattaché à la réalisation française du temps légal.ptbtime4.ptb.de(Allemagne, Physikalisch-Technische Bundesanstalt).ntp1.npl.co.uk(Royaume-Uni, National Physical Laboratory).ntp1.sp.se(Suède, RISE / SP).ntp2.oma.be(Belgique, Observatoire royal de Belgique).time.nist.gov(États-Unis, National Institute of Standards and Technology) : seul repère hors Europe de cette liste, il diversifie la zone géographique de dépendance.
Chacun de ces six serveurs est un Stratum 1 institutionnel connu, dont l’écart à UTC est publié mensuellement dans la Circular T du BIPM (le chapitre Performances recense l’ensemble des laboratoires nationaux de métrologie exposant un service NTP public, bien au-delà de ces six-là).
Combien de sources ? Pour une machine qui tire son heure du réseau (un client ordinaire, ou ce serveur en secours GNSS), un minimum de 3 serveurs est conseillé : avec 2, un désaccord est impossible à trancher ; à partir de 3, la majorité isole une source aberrante, et 4 permettent de conserver cette majorité même pendant la panne d’une source (algorithme de sélection, chapitre Vue d’ensemble). À l’autre extrémité, 5 à 6 sources constituent un maximum raisonnable : au-delà, le gain statistique devient négligeable tandis que la charge imposée aux serveurs publics augmente inutilement. L’exemple ci-dessus totalise 6 sources, cinq laboratoires européens et un nord-américain, le haut de la fourchette.
local stratum 14 orphan distance 0.999Ce mode “orphelin” ne s’active que si aucune source amont n’est sélectionnable (panne GNSS et réseau coupé) : le serveur continue d’émettre l’heure (au stratum 14, donc clairement dégradé) plutôt que de s’arrêter complètement de répondre.
Servir les clients : allow et ratelimit#
Tout ce qui précède configure ce que ce serveur interroge en amont. Encore faut-il l’autoriser à répondre à ses propres clients, sans quoi tout ce guide ne produirait qu’une horloge parfaitement disciplinée mais muette.
allow
ratelimit interval 2 burst 8
ntsratelimit interval 3 burst 8Sans
allow, chrony refuse par défaut toutes les requêtes clientes. C’est le piège le plus trompeur de ce chapitre : un serveur parfaitement configuré côté sources amont (PPS, GNSS, secours réseau) mais sans directiveallowdisciplinera sa propre horloge avec une précision exemplaire, et ne répondra à aucun client externe. Rien dans les journaux ne signale cette absence comme une erreur : les requêtes entrantes sont simplement ignorées silencieusement.allow(sans argument) autorise tous les clients ; on peut aussi restreindre à une plage (allow 203.0.113.0/24).
ratelimit limite le débit de requêtes NTP au niveau du protocole
lui-même, interval 2 correspond à un intervalle minimal de 2² = 4
secondes entre deux requêtes tolérées d’une même IP, burst 8 autorise
une rafale initiale de 8 requêtes avant que la limite ne s’applique
(compatible avec iburst côté client). ntsratelimit fait de même
spécifiquement pour les négociations NTS-KE, avec ses propres valeurs :
interval 3 porte l’intervalle minimal à 2³ = 8 secondes plutôt que 4,
une poignée de main NTS-KE complète (négociation TLS 1.3) coûtant
nettement plus cher en calcul qu’un simple paquet NTP, la limite est
logiquement plus prudente.
Cette limite est un troisième niveau de protection, distinct des deux autres déjà vus dans ce guide : le rate-limiting réseau via nftables (chapitre nftables, aveugle au protocole, agit avant même que le paquet n’atteigne chrony) et le Kiss-o’-Death (chapitre Anatomie du paquet NTP, une rétorsion protocolaire coopérative).
ratelimitse situe entre les deux : c’est chrony lui-même qui décide, au niveau applicatif, de droper ou non une requête excédentaire, et c’est précisément ce mécanisme qui déclenche l’envoi d’un code KissRATEau client fautif.
NTS : chiffrer le protocole#
ntsport 4460
ntsservercert /etc/chrony/fullchain.pem
ntsserverkey /etc/chrony/privkey.pemNTS (Network Time Security, RFC 8915) ajoute une couche d’authentification cryptographique au protocole NTP historiquement en clair. Le client établit d’abord une session TLS sur le port TCP 4460 (NTS-KE, Key Establishment) pour obtenir des clés à usage unique, puis échange des paquets NTP normaux mais signés avec ces clés. Concrètement, cela réutilise le même certificat TLS que le site web (Let’s Encrypt), rien de plus à gérer côté certificats.
L’alternative plus ancienne : clé symétrique partagée#
NTS n’est pas la seule façon d’authentifier NTP, ni même la première. chrony sait aussi authentifier un serveur particulier via une clé symétrique pré-partagée (mécanisme natif de NTP décrit dans la RFC 5905 elle-même), un ancien mécanisme, mais toujours pris en charge et activement utilisé, y compris en parallèle de NTS.
# /etc/chrony/chrony.keys, clé de démonstration, usage réservé aux tests
# La clef 42 générée le 26/02/2026 sur time.example.org
42 SHA256 HEX:A4E57E0554C9860BD5E2C1384768FBA42E305E3617C83085FB655E480879A29F# /etc/chrony/chrony.conf
keyfile /etc/chrony/chrony.keys
server time.example.org key 42 iburst xleave minpoll 6
pool pool.ntp.org iburstkeyfile indique à chrony où trouver le fichier de clés ci-dessus, sans
cette directive, la référence key 42 d’une ligne server ne
résoudrait vers rien et chrony refuserait de démarrer l’association
authentifiée. Chaque paquet échangé avec ce serveur particulier est
ensuite signé par un HMAC utilisant la clé 42 partagée à l’avance, le
client comme le serveur doivent connaître cette clé. minpoll 6 fixe
l’intervalle minimal d’interrogation à 2⁶ = 64 secondes, une valeur
classique pour un serveur
NTP distant sur une liaison de confiance de ce type, largement suffisante
une fois la convergence initiale atteinte et moins gourmande en requêtes
qu’un minpoll par défaut plus court.
Ne pas confondre trois mécanismes distincts :
- Clé symétrique (ci-dessus, RFC 5905) : simple, toujours utilisée aujourd’hui, mais la distribution de la clé est manuelle : il faut la transmettre hors-bande à chaque client autorisé, ce qui ne passe pas à l’échelle d’un serveur public anonyme.
- Autokey (RFC 5906, conçu par D. Mills) : une tentative d’automatiser cette distribution de clé via de la cryptographie à clé publique. Des faiblesses cryptographiques identifiées par la recherche académique (vulnérabilité à des attaques par déni de service et par usurpation) ont conduit la communauté à l’abandonner : c’est bien Autokey qui a disparu, pas l’authentification par clé symétrique.
- NTS (RFC 8915) : la solution moderne au même problème d’automatisation, en s’appuyant sur TLS 1.3 plutôt que sur le schéma propre à Autokey.
Sur ce projet, la clé symétrique reste utile pour un lien de confiance point à point où les deux parties peuvent échanger une clé hors-bande (test, liaison interne) ; NTS reste la bonne réponse pour un serveur public répondant à des clients anonymes qui ne peuvent pas récupérer une clé au préalable.
Confiner chronyd en temps réel sur le Core 1#
chronyd calcule et applique en continu la correction de fréquence de
l’horloge système à partir du PPS : une préemption tardive dégraderait
directement la précision de cette boucle de discipline. Un override
systemd le place en ordonnancement temps réel FIFO, juste sous la
priorité de l’IRQ PPS (99), sur le même cœur que phc2sys.
mkdir -p /etc/systemd/system/chrony.service.d/
cat > /etc/systemd/system/chrony.service.d/override.conf <<'EOF'
[Unit]
After=network-online.target
Wants=network-online.target
[Service]
# Ordonnancement temps réel FIFO, priorité 98 (juste sous l'IRQ PPS à 99)
CPUSchedulingPolicy=fifo
CPUSchedulingPriority=98
CPUAffinity=1
# Accès en écriture au tmpfs NTS
ReadWritePaths=/var/nts/chrony
# Délai pour laisser le pilote PPS et l'horloge PHC se présenter
ExecStartPre=/bin/sleep 5
Restart=on-failure
RestartSec=10s
EOF
systemctl daemon-reload
systemctl restart chronyNe jamais ajouter
After=gpsd.servicedans ce drop-in. Cela créerait un cycle de dépendances au démarrage entrechronyetgpsd: les deux services attendent alors indéfiniment l’un l’autre.
Persistance de la dérive : driftfile#
driftfile /var/lib/chrony/chrony.driftchrony réestime en continu la dérive de fréquence de l’oscillateur local
et l’écrit périodiquement dans ce fichier. Au redémarrage, il relit
cette valeur et l’applique immédiatement, plutôt que de repartir de
zéro : la convergence vers une précision optimale est nettement plus
rapide qu’à un premier démarrage. C’est le même principe que la table
tempcomp ci-dessous (un état persistant entre reboots), en plus
simple : une seule valeur de dérive de fond, sans dépendance à la
température.
Compensation thermique dynamique : tempcomp#
Un oscillateur à quartz dérive avec sa température : le Pi 5 n’embarque
qu’un quartz nu, non compensé, et les pentes mesurées sur ce serveur vont
de 0,06 à 0,2 ppm/°C selon la zone (chapitre Du quartz nu au
pseudo-TCXO), en plus de la stabilisation passive déjà décrite (chapitre
Stabilisation thermique). chrony propose la directive
tempcomp pour compenser activement cette dérive, à partir d’une sonde de
température lue périodiquement :
tempcomp /sys/class/thermal/thermal_zone0/temp 20 /var/lib/chrony/chrony.tempcomp/sys/class/thermal/thermal_zone0/temp est la même sonde SoC que celle
pilotant déjà la courbe du ventilateur (chapitre Firmware) : aucune sonde
dédiée au quartz n’existe sans modification matérielle. La table de
points (plutôt qu’une simple formule quadratique) permet de coller à une
courbe réelle qui n’est pas une parabole propre.
log tracking measurements tempcompCette directive est ce qui alimente concrètement tracking.log et
tempcomp.log, sans elle, tempcomp-refit (plus bas) n’aurait aucune
donnée à relire, la table resterait figée à ses valeurs initiales.
Un script (tempcomp-refit, lancé par un timer systemd quotidien)
reconstruit cette table à partir de l’historique observé (tempcomp.log +
tracking.log), plutôt que de figer une calibration unique. Pour cela, il
découpe la plage de température en tranches de 0,5 °C (un bin, au sens
statistique du terme : un intervalle regroupant tous les échantillons
tombant dedans) et calcule, pour chaque bin ayant assez d’échantillons, la
compensation moyenne observée à cette température :
systemctl enable --now tempcomp-refit.timerTrois points méritent attention pour qui voudrait reproduire ce mécanisme :
- chrony ne relit le fichier de points qu’au démarrage. Pas de
rechargement à chaud, même via
SIGHUP(vérifié dans le code source de chrony). Une nouvelle table n’a donc aucun effet tant que chrony n’est pas redémarré. Le script ne redémarre jamais chrony lui-même : il écrit la table et journalise qu’elle est prête, le redémarrage reste un geste manuel choisi par l’opérateur. - chrony extrapole sans plafond au-delà de la table. Une température jamais observée (canicule, panne ventilateur) prolongerait la pente du dernier segment mesuré, potentiellement bruitée. Le script ajoute donc systématiquement une marge plate de part et d’autre de la plage observée, pour que la compensation reste figée à la dernière valeur fiable plutôt que de diverger.
- une mesure prise pendant un régime transitoire n’est pas fiable. Un test réel de refroidissement forcé sur ce serveur a montré que la fréquence mesurée juste après un choc thermique brutal peut dépasser largement ce que prédit le comportement en régime stable, avant de revenir progressivement à la normale. D’où des seuils de volume minimum de données avant tout déploiement automatique.
- un bin déjà déployé est rejeté s’il s’écarte trop. Un bin peut continuer à s’affiner d’un passage à l’autre, mais si la nouvelle valeur diffère trop de celle actuellement en place, la mise à jour est rejetée et journalisée plutôt qu’écrite. Objectif : suivre le vieillissement naturel, lent, du quartz, tout en bloquant un saut brutal causé par un échantillon contaminé par un transitoire.
Erreurs à éviter#
La théorie n’est pas toujours confirmée par la mesure : seule la mesure tranche. Un raisonnement a priori solide suggérait qu’avec un PPS à 1 Hz,
poll 2(échantillonnage toutes les 4 s) ne remplit que 4 des 16 emplacements du filtre médian (filter 16), et qu’unpoll 4(16 s) le remplirait réellement, donc, en théorie, une meilleure réjection du bruit. Testé en conditions réelles sur ce projet :poll 2a mesurément donné un meilleur RMS offset quepoll 4. La leçon : sur un système avec autant de facteurs couplés (charge IRQ, comportement du filtre, dérive thermique), une hypothèse théorique doit être validée par une mesure avant/après sur 24 h avant d’être adoptée définitivement. Certains de ces facteurs ne sont pas de simples paramètres qu’on pourrait rejouer à l’identique : ils sont, pour partie, intrinsèquement non déterministes. La gigue d’interruption du noyau en est un exemple concret : elle varie de façon stochastique d’un échantillon PPS à l’autre (état du cache processeur, contention du bus mémoire au moment précis de l’interruption), ce qui modifie la structure de corrélation des échantillons quefilter 16moyenne, un raisonnement purement théorique ne peut pas l’anticiper.
- Commentaires en fin de ligne sur les directives
server. Certaines versions de chrony n’acceptent pas de commentaire#après une directiveserversur la même ligne, le parseur peut se comporter de façon inattendue. Les commentaires explicatifs doivent être placés au-dessus de la ligne. chronycqui échoue silencieusement sous un service systemd durci. Sichronydtourne dans un sandbox systemd restrictif (recommandé, voir le chapitre durcissement), la commandechronycpar défaut (qui utilise un socket UDP local) peut échouer sans message clair.- Confondre
noselectet absence de source.refclock SHM 0 ... noselectreste actif et fournit des données àlock GPS,noselectsignifie seulement “ne pas utiliser directement comme référence de temps”, pas “désactivé”. - Redémarrer
chronydpour recharger un certificat NTS. Unsystemctl restartcasse la boucle de discipline en cours (la PLL perd son état). Pour recharger un certificat renouvelé, envoyer unSIGHUPau processus suffit et préserve la synchronisation.
Vérifier#
$ chronyc tracking
Reference ID : 50505330 (PPS0)
Stratum : 1
RMS offset : 0.000000071 seconds
Skew : 0.007 ppm
Leap status : NormalReference ID=PPS0confirme que c’est bien le PPS qui pilote l’horloge, pas une source réseau de secours.RMS offset: l’écart type de l’erreur mesurée, autour de la microseconde ici, c’est l’objectif du projet.Skew: la dérive de fréquence de l’oscillateur local, en ppm (parties par million). Une valeur stable et faible (< 0,05 ppm) est le signe d’un système bien discipliné et thermiquement stable.
# Confinement RT effectif : classe FF (FIFO), priorité 98, cœur 1
ps -eLo psr,cls,rtprio,comm | grep chronyd
# Attendu : 1 FF 98 chronyd$ chronyc sources -v
.-- Source mode '^' = server, '=' = peer, '#' = local clock.
/ .- Source state '*' = current best, '+' = combined, '-' = not combined,
| / 'x' = may be in error, '~' = too variable, '?' = unusable.
|| .- xxxx [ yyyy ] +/- zzzz
|| Reachability register (octal) -. | xxxx = adjusted offset,
|| Log2(Polling interval) --. | | yyyy = measured offset,
|| \ | | zzzz = estimated error.
|| | | \
MS Name/IP address Stratum Poll Reach LastRx Last sample
===============================================================================
#* PPS0 0 2 377 3 +18ns[ +16ns] +/- 802ns
#? GPS 0 4 377 13 -1082us[-1082us] +/- 277us
^- ntp-p1.obspm.fr 1 9 377 244 -278us[ -250us] +/- 1289us
^- ptbtime4.ptb.de 1 9 377 301 -462us[ -418us] +/- 10ms
^- ntpsvr1.npl.co.uk 1 8 377 170 -552us[ -536us] +/- 5236us
^- 193.11.166.8 1 10 377 185 -1208us[-1190us] +/- 19ms
^- 129.6.15.25 1 8 377 39 -330us[ -330us] +/- 44ms
^- heppen.be 1 10 377 29m +116us[ +462us] +/- 8549usSortie réelle de ce serveur (l’option -v ajoute la légende) :
#*: source actuellement sélectionnée comme référence (le#indique une horloge de référence locale, pas un serveur réseau). Ici le PPS, à ±802 ns d’incertitude estimée.#?: source non sélectionnable actuellement (normal pourGPSqui n’a que le rôle de désambiguïsation, son offset de l’ordre de la milliseconde ne gêne en rien).^-: source réseau, validée mais non sélectionnée (comportement attendu tant que le PPS répond correctement). Les six sources amont de ce serveur sont elles-mêmes des Stratum 1 institutionnels (Observatoire de Paris, PTB allemande, NPL britannique, RISE suédois, NIST américain, plus un serveur communautaire belge), en cohérence avec la fourchette de 5-6 sources de la section Sources de secours.Reach 377(octal) : les 8 dernières interrogations ont toutes répondu ;LastRx 29mmontre qu’une source espacée àPoll 10(2¹⁰ ≈ 17 min) peut afficher un dernier échantillon ancien sans que ce soit un défaut.
$ chronyc -N authdatapermet de vérifier qu’un client s’est bien authentifié via NTS.
📚 Pour aller plus loin
Protocole on-wire (calcul d’offset). Pour chaque échange, quatre timestamps sont collectés (T1 départ requête client, T2 réception serveur, T3 départ réponse serveur, T4 réception client) :
offset = ((T2 - T1) + (T3 - T4)) / 2 délai = (T4 - T1) - (T3 - T2)Hypothèse implicite : symétrie du réseau dans les deux sens. Toute asymétrie introduit un biais égal à la moitié de l’asymétrie (D. Mills, Computer Network Time Synchronization, 2006, ch. 3).
Sélection des sources. Quand plusieurs serveurs sont interrogés, l’algorithme dérive de l’algorithme de Marzullo (K. Marzullo, thèse de doctorat, Stanford, 1984), adapté par Mills pour NTP : chaque source fournit un intervalle de confiance
[offset - délai, offset + délai], et l’algorithme cherche l’intersection maximale cohérente entre intervalles pour exclure les sources aberrantes (falsetickers). C’est ce qui justifie de garder plusieurs sources amont même sur un Stratum 1 : détecter au moins un falseticker nécessite au minimum 4 sources.Références normatives :
- RFC 5905, Network Time Protocol Version 4: Protocol and Algorithms Specification, D. Mills et al., 2010.
- RFC 8915, Network Time Security for the Network Time Protocol, D. Franke, D. Sibold et al., 2020.