Your Servers Are Living in Slightly Different Realities
Two servers. Both think they know what time it is. One is 47 seconds behind. This is fine until it’s suddenly your authentication tokens are rejected, your log correlation is useless, and your distributed transaction system is making decisions based on timestamps that contradict each other.
Time synchronization is one of those infrastructure fundamentals that nobody thinks about until it explodes. The good news: it’s easy to set up properly. The bad news: you’ve probably been ignoring it.
Why Time Actually Matters
You might think “who cares if my home server is a few seconds off?” Here’s who cares:
TLS/HTTPS certificates: Certificate validity is time-bounded. If your server clock is wrong enough, valid certs get rejected. “It works on my machine” becomes “why does this work on my laptop but not the server.”
Kerberos authentication: Kerberos has a hard 5-minute clock skew tolerance. Exceed it and authentication fails completely.
Log correlation: Trying to correlate logs across servers with drifted clocks is like reading a novel where each chapter is from a different timeline. The bug happened “between these two events” becomes impossible to determine.
Distributed databases: Systems like CockroachDB, Cassandra, and even Postgres replication use wall clock time for conflict resolution. Wrong clocks = wrong data.
Cron and scheduled jobs: Technically works, but drift means your “run at midnight” job runs at 12:03, then 11:57, then…
ntpd vs Chrony: Why Chrony Wins for Most People
ntpd is the old standard, been around since the 1980s. It works, but it’s designed for servers that are always on and always connected. It takes a long time to converge after startup and is bad at handling intermittent connectivity.
Chrony was written to solve these problems. It:
- Converges much faster at startup
- Handles intermittent network connections well (laptops, VMs, home servers)
- Adjusts for system clock drift more aggressively when needed
- Uses less memory and CPU
- Is the default on RHEL/CentOS/Fedora and most modern distros
Unless you have a specific reason to use ntpd, use Chrony. Your home lab servers are probably not always-on datacenter hardware with rock-stable network connections, and Chrony handles the messy real world better.
Installing Chrony
# Debian/Ubuntusudo apt install chrony
# RHEL/CentOS/Fedora (often pre-installed)sudo dnf install chrony
# Check if it's runningsudo systemctl status chronydIf you also have systemd-timesyncd running (Ubuntu installs this by default), disable it first — they conflict:
sudo systemctl disable --now systemd-timesyncdsudo systemctl enable --now chronydchrony.conf: The Important Parts
The main config is at /etc/chrony.conf or /etc/chrony/chrony.conf depending on your distro.
# Use pool.ntp.org — pick your regionpool 2.pool.ntp.org iburst
# Or use specific servers for more reliabilityserver time.cloudflare.com iburst preferserver time.google.com iburstserver ntp.ubuntu.com iburst
# Record the rate at which the system clock gains/loses timedriftfile /var/lib/chrony/drift
# Enable kernel synchronization of the hardware clockrtcsync
# Allow stepping the clock in the first 3 updates if off by more than 1 secondmakestep 1.0 3
# Log tracking, measurements, and statisticslogdir /var/log/chrony
# Only accept time sync from localhost (default — important for security)# Remove or expand this to allow LAN clients# allow 192.168.1.0/24Key directives explained:
iburst: send 8 packets at startup instead of 1 — converges fasterprefer: use this server preferentially when it’s reachablemakestep 1.0 3: if the clock is off by more than 1 second in the first 3 updates, step it immediately (don’t just drift toward correct)rtcsync: sync the hardware RTC from the software clock periodicallydriftfile: records how much your hardware clock drifts — survives reboots
After editing:
sudo systemctl restart chronydchronyc: Checking What’s Happening
# Show current tracking statuschronyc trackingSample output:
Reference ID : A29FC801 (time.cloudflare.com)Stratum : 3Ref time (UTC) : Fri Apr 03 14:22:31 2026System time : 0.000012345 seconds slow of NTP timeLast offset : -0.000008234 secondsRMS offset : 0.000015432 secondsFrequency : 2.456 ppm slowResidual freq : -0.023 ppmSkew : 0.089 ppmRoot delay : 0.021345678 secondsRoot dispersion : 0.001234567 secondsUpdate interval : 64.3 secondsLeap status : NormalWhat matters here:
- System time: how far off you are right now. Sub-millisecond is excellent.
- Stratum: how many hops from a reference clock (more on this below)
- Frequency: rate of drift in parts-per-million. Chrony compensates for this automatically.
- Leap status: “Normal” is good. “Insert” or “Delete” means a leap second is coming.
# Show all configured NTP sources and their statuschronyc sources -v
# Show source statisticschronyc sourcestats
# Force an immediate sync checkchronyc makestep
# Check if time is synchronized (returns 0 or 1)chronyc waitsyncUnderstanding Stratum Levels
Stratum is the NTP hierarchy’s way of indicating time accuracy:
- Stratum 0: Atomic clocks, GPS receivers, radio clocks — the physical reference
- Stratum 1: Servers directly connected to stratum 0 hardware (e.g., time.cloudflare.com)
- Stratum 2: Servers synced to stratum 1 (most public NTP pool servers)
- Stratum 3: Your home lab NTP server syncing to stratum 2
- Stratum 4+: Other machines syncing to your home lab server
Each hop adds potential error. Stratum 3 or 4 in a home lab is completely fine — you’re talking microseconds of error, not seconds.
Setting Up Your Own LAN NTP Server
Here’s the useful home lab config: one server syncs to the internet, all your other machines sync to it. This reduces external NTP traffic and keeps all your local clocks synchronized to the same reference.
On your dedicated NTP server (could be your router, a Pi, whatever’s always on):
# /etc/chrony.conf — NTP SERVER config
pool 2.pool.ntp.org iburstserver time.cloudflare.com iburst preferserver time.google.com iburst
driftfile /var/lib/chrony/driftrtcsyncmakestep 1.0 3
# Allow clients on your LAN to sync from this serverallow 192.168.1.0/24
# Serve time even if not synced to internet (for LAN resilience)local stratum 10
logdir /var/log/chronyThe local stratum 10 directive tells Chrony to keep serving time to LAN clients even when it can’t reach the internet — just from the local clock. Stratum 10 signals to clients that this is a fallback, not an authoritative source.
Open the NTP port in your firewall:
sudo ufw allow from 192.168.1.0/24 to any port 123 proto udpOn all your client machines:
# /etc/chrony.conf — CLIENT config
# Point to your LAN NTP server instead of (or in addition to) the internetserver 192.168.1.10 iburst prefer# Optional fallback to internet poolpool 2.pool.ntp.org iburst
driftfile /var/lib/chrony/driftmakestep 1.0 3rtcsyncsudo systemctl restart chronydchronyc sources -v# Should show your LAN server as the preferred sourcesystemd-timesyncd: The Lightweight Option
If all you need is basic time sync and you don’t want to run a full Chrony daemon, systemd-timesyncd is built into systemd and works fine for simple setups.
# Check statustimedatectl statustimedatectl show-timesync
# Configure itsudo nano /etc/systemd/timesyncd.conf[Time]NTP=192.168.1.10 time.cloudflare.comFallbackNTP=pool.ntp.orgsudo systemctl restart systemd-timesyncdtimedatectl timesync-statusThe honest comparison:
- systemd-timesyncd: good for desktops, simple servers, anything not doing distributed systems. Zero configuration, works out of the box.
- Chrony: better for servers, handles network hiccups better, more accurate convergence, supports serving time to other machines, has better monitoring tools.
For your home lab server running Docker containers and services that care about time? Use Chrony. For a desktop that just needs to not be 5 minutes off? Timesyncd is fine.
Common Issues and Fixes
Clock won’t sync: Check firewall rules — NTP uses UDP 123. Check if another daemon is conflicting (systemctl status systemd-timesyncd).
Large initial offset: Chrony by default won’t step the clock (sudden jumps) after the initial sync period, only slew (gradual adjustment). If your clock is very wrong, force a step:
sudo chronyc makestepVM with bad timekeeping: VMs are notoriously bad at keeping time — the hypervisor can steal CPU time mid-tick. Add maxdistance 1.0 to your chrony.conf to be more tolerant, and consider the kvm_clock or VMware Tools time sync as a supplement.
Leap seconds: Chrony handles these automatically with the smooth directive if you want gradual adjustment instead of a 1-second step:
leapsecmode slewmaxslewrate 1000smoothtime 400 0.001 leaponlyTime is infrastructure. Get it right once, never think about it again.