Skip to content
SumGuy's Ramblings
Go back

Time Is a Lie and Chrony Is Here to Fix It: NTP for Home Labs

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:

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:

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:

# 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:

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:

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.


Share this post on:

Previous Post
n8n + LLM: Building Automations That Actually Think
Next Post
Text Generation Web UI vs KoboldCpp: Power User LLM Interfaces