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/Ubuntu
sudo apt install chrony
# RHEL/CentOS/Fedora (often pre-installed)
sudo dnf install chrony
# Check if it's running
sudo systemctl status chronyd
If you also have systemd-timesyncd running (Ubuntu installs this by default), disable it first — they conflict:
sudo systemctl disable --now systemd-timesyncd
sudo systemctl enable --now chronyd
chrony.conf: The Important Parts
The main config is at /etc/chrony.conf or /etc/chrony/chrony.conf depending on your distro.
# /etc/chrony.conf
# Use pool.ntp.org — pick your region
pool 2.pool.ntp.org iburst
# Or use specific servers for more reliability
server time.cloudflare.com iburst prefer
server time.google.com iburst
server ntp.ubuntu.com iburst
# Record the rate at which the system clock gains/loses time
driftfile /var/lib/chrony/drift
# Enable kernel synchronization of the hardware clock
rtcsync
# Allow stepping the clock in the first 3 updates if off by more than 1 second
makestep 1.0 3
# Log tracking, measurements, and statistics
logdir /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/24
Key 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 chronyd
chronyc: Checking What’s Happening
# Show current tracking status
chronyc tracking
Sample output:
Reference ID : A29FC801 (time.cloudflare.com)
Stratum : 3
Ref time (UTC) : Fri Apr 03 14:22:31 2026
System time : 0.000012345 seconds slow of NTP time
Last offset : -0.000008234 seconds
RMS offset : 0.000015432 seconds
Frequency : 2.456 ppm slow
Residual freq : -0.023 ppm
Skew : 0.089 ppm
Root delay : 0.021345678 seconds
Root dispersion : 0.001234567 seconds
Update interval : 64.3 seconds
Leap status : Normal
What 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 status
chronyc sources -v
# Show source statistics
chronyc sourcestats
# Force an immediate sync check
chronyc makestep
# Check if time is synchronized (returns 0 or 1)
chronyc waitsync
Understanding 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 iburst
server time.cloudflare.com iburst prefer
server time.google.com iburst
driftfile /var/lib/chrony/drift
rtcsync
makestep 1.0 3
# Allow clients on your LAN to sync from this server
allow 192.168.1.0/24
# Serve time even if not synced to internet (for LAN resilience)
local stratum 10
logdir /var/log/chrony
The 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 udp
On all your client machines:
# /etc/chrony.conf — CLIENT config
# Point to your LAN NTP server instead of (or in addition to) the internet
server 192.168.1.10 iburst prefer
# Optional fallback to internet pool
pool 2.pool.ntp.org iburst
driftfile /var/lib/chrony/drift
makestep 1.0 3
rtcsync
sudo systemctl restart chronyd
chronyc sources -v
# Should show your LAN server as the preferred source
systemd-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 status
timedatectl status
timedatectl show-timesync
# Configure it
sudo nano /etc/systemd/timesyncd.conf
[Time]
NTP=192.168.1.10 time.cloudflare.com
FallbackNTP=pool.ntp.org
sudo systemctl restart systemd-timesyncd
timedatectl timesync-status
The 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 makestep
VM 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 slew
maxslewrate 1000
smoothtime 400 0.001 leaponly
Time is infrastructure. Get it right once, never think about it again.