Accurate, consistent time is a hard requirement for log correlation, TLS certificate validation, Kerberos authentication, database replication, distributed locks, and almost every protocol that signs or sequences events. A drift of a few seconds is enough to break Kerberos tickets, void TOTP codes, or make MongoDB and Elasticsearch refuse to elect a primary. The only sane fix is to put every host on a known good time source — and on a Linux fleet, that source is NTP.
This guide walks through installing, configuring, and operating an NTP server on CentOS 7 using the two daemons available in the base repos: chronyd (the default) and ntpd (the classic reference implementation). It also covers firewall rules, verification, and how to wire NTP health into ongoing monitoring.
Heads up — CentOS 7 EOL. CentOS 7 reached end-of-life on June 30, 2024. If you are deploying a new time server, do it on RHEL 8/9, Rocky Linux 8/9, AlmaLinux 8/9, or Oracle Linux. The configuration shown here is identical on those distributions; only the package commands differ (
dnfinstead ofyum). This article remains useful for legacy CentOS 7 fleets you still need to keep on time while you migrate.
chronyd or ntpd — pick one
CentOS 7 ships two NTP implementations and both are in the base repos:
| chronyd | ntpd | |
|---|---|---|
| Package | chrony |
ntp |
| Default on CentOS 7 | Yes | No |
| Recovers from large offsets | Quickly (slew or step) | Slowly (slew only by default) |
| Works well on intermittently connected / mobile hosts | Yes | Less so |
| Can serve clients | Yes (allow directive) |
Yes (default) |
| Hardware reference clocks (GPS/PPS) | Yes | Yes (more mature) |
| Replaced upstream by | — (active) | chronyd on RHEL/CentOS 7+ |
For 95% of fleets — physical servers, VMs, containers — use chronyd. It is the default for a reason: faster sync, better behavior on hosts that suspend or move networks, and a simpler config surface. Reach for ntpd only if you have a hard requirement (a tooling integration, a hardware reference clock workflow, or compliance language that names ntpd).
You should run only one at a time. Both daemons want UDP/123 and will fight over the system clock if they run concurrently.
Prerequisites
- A CentOS 7 host with network access (either to the public NTP pool or to your upstream internal NTP servers).
sudo/ root access.- The system clock close enough to reality that a sync is feasible. If the clock is years off (common after a CMOS battery failure on bare metal), force a one-shot correction first — see Bootstrap a wildly wrong clock below.
Option A — chronyd (recommended)
1. Install
sudo yum install -y chrony
chrony is usually already installed on CentOS 7. If ntpd is also installed, stop and disable it before continuing:
sudo systemctl stop ntpd
sudo systemctl disable ntpd
2. Configure /etc/chrony.conf
The default file is well annotated. The minimum changes to turn this host into an NTP server for your network are:
# Use the public NTP pool as upstream sources. Replace with internal
# stratum-1/2 servers if you have them.
server 0.centos.pool.ntp.org iburst
server 1.centos.pool.ntp.org iburst
server 2.centos.pool.ntp.org iburst
server 3.centos.pool.ntp.org iburst
# Or, if you have multiple chronyd hosts that should share knowledge:
#peer ntp-b.example.com
# Record the rate at which the system clock gains/loses time.
driftfile /var/lib/chrony/drift
# Allow large initial step if the clock is more than 1s off
# during the first three updates after start.
makestep 1.0 3
# Enable kernel synchronization of the real-time clock (RTC).
rtcsync
# ---- Server mode ----
# Allow clients on these networks to query this server.
allow 10.0.0.0/8
allow 192.168.0.0/16
# allow 0.0.0.0/0 # only if you intend to be a *public* NTP server
# Optional: serve time even when this host has no upstream sync
# (useful on isolated networks). Local stratum 10 = "I know I am not great,
# but I'm better than nothing."
local stratum 10
# Log files for diagnostics.
logdir /var/log/chrony
log measurements statistics tracking
Key directives explained:
server— an upstream NTP source.iburstsends a burst of 4 packets at startup so chrony reaches sync in seconds instead of minutes.peer— a symmetric peer of equal stratum; both sides exchange time. Use this between your own chronyd servers, not toward the public pool.allow/deny— ACL for serving time. Default is to not serve any client.makestep— step the clock if the offset is larger than the threshold during the first N updates. After that, chronyd slews (gradual adjustment) to avoid jumping the clock under live workloads.local stratum— broadcast a stratum even when no upstream is reachable, so internal clients on an air-gapped LAN keep agreeing with each other.
3. Enable and start
sudo systemctl enable --now chronyd
Confirm it is running and sourcing time:
sudo systemctl status chronyd
chronyc tracking
chronyc sources -v
chronyc tracking is the one-liner status:
Reference ID : 8B26F6C0 (time.cloudflare.com)
Stratum : 3
Ref time (UTC) : Sat May 09 09:14:08 2026
System time : 0.000018221 seconds slow of NTP time
Last offset : -0.000031542 seconds
RMS offset : 0.000284901 seconds
Frequency : 12.503 ppm slow
Residual freq : -0.012 ppm
Skew : 0.052 ppm
Root delay : 0.024812 seconds
Root dispersion : 0.000412 seconds
Update interval : 1027.6 seconds
Leap status : Normal
What to look for:
- Stratum ≤ 4 for a server (1 = atomic clock / GPS, 2 = peers of stratum 1, …). Anything higher means you are too far from a reference clock.
- System time offset under ~50 ms on a wired network, under a few hundred ms on Wi-Fi or constrained VMs.
- Leap status =
Normal.Not synchronisedmeans chronyd has no usable upstream.
4. Open the firewall
NTP runs on UDP/123. CentOS 7 uses firewalld:
sudo firewall-cmd --permanent --add-service=ntp
sudo firewall-cmd --reload
Or by port:
sudo firewall-cmd --permanent --add-port=123/udp
sudo firewall-cmd --reload
Restrict the source if appropriate. Public-facing NTP servers attract abuse (monlist reflection attacks were a long-running problem with old ntpd; chronyd does not implement monlist, but you should still scope the ACL).
Option B — ntpd (classic)
Use this only if you specifically need ntpd. Otherwise skip to verification.
1. Install
sudo systemctl stop chronyd
sudo systemctl disable chronyd
sudo yum install -y ntp
2. Configure /etc/ntp.conf
driftfile /var/lib/ntp/drift
# Restrict default access; permit query and notrap, deny configure/modify.
restrict default nomodify notrap nopeer noquery
restrict 127.0.0.1
restrict ::1
# Permit clients on these networks to query this server.
restrict 10.0.0.0 mask 255.0.0.0 nomodify notrap
restrict 192.168.0.0 mask 255.255.0.0 nomodify notrap
# Upstream sources.
server 0.centos.pool.ntp.org iburst
server 1.centos.pool.ntp.org iburst
server 2.centos.pool.ntp.org iburst
server 3.centos.pool.ntp.org iburst
# Optional: serve as orphan stratum 10 if upstreams are unreachable.
tos orphan 10
# Log file.
logfile /var/log/ntp.log
Key bits:
restrict default ... noquerydenies time queries from everyone unless explicitly allowed below. This is what shields you frommonlist-style reflection abuse.- The two
restrict <network>lines allow internal clients to query without granting modify or peer rights. tos orphan 10is thentpdequivalent of chrony'slocal stratum.
3. Enable and start
sudo systemctl enable --now ntpd
sudo systemctl status ntpd
4. Verify
ntpq -pn
remote refid st t when poll reach delay offset jitter
==============================================================================
*162.159.200.1 10.27.57.53 3 u 23 64 377 8.412 -0.144 0.522
+162.159.200.123 10.27.57.53 3 u 18 64 377 7.918 0.082 0.611
+5.2.79.49 193.79.237.14 2 u 42 64 377 33.211 -0.017 1.044
-216.197.156.83 136.243.135.7 2 u 28 64 377 78.502 1.220 2.811
Read the leading column:
*— the system peer (the source ntpd is currently using).+— a candidate good enough to be selected as a peer.-— rejected by the clustering algorithm.x— falseticker (the source disagrees with the rest).(blank)— discarded outright.
You want at least one * and a couple of + candidates. Stratum (st) 1 or 2 is ideal for upstreams. reach should reach 377 (octal for "all 8 of the last 8 polls succeeded") within a few minutes.
ntpstat gives a one-line summary:
sudo yum install -y ntpstat
ntpstat
# synchronised to NTP server (162.159.200.1) at stratum 3
# time correct to within 38 ms
# polling server every 64 s
5. Firewall
Same as chronyd:
sudo firewall-cmd --permanent --add-service=ntp
sudo firewall-cmd --reload
Pointing clients at your new server
On every CentOS 7/8/9, RHEL, Rocky, or Alma client, point chrony or ntpd at your internal server(s):
chronyd
# /etc/chrony.conf
server ntp-a.example.com iburst
server ntp-b.example.com iburst
ntpd
# /etc/ntp.conf
server ntp-a.example.com iburst
server ntp-b.example.com iburst
Restart the daemon and confirm with chronyc sources -v or ntpq -pn. Two or three internal NTP servers are typical — clients run their own clustering algorithm and benefit from having more than one upstream so a single failed server does not desynchronise the fleet.
Time zone
NTP synchronises UTC. The wall-clock time you see in date is UTC + your time zone. On CentOS 7:
timedatectl # show current state
timedatectl list-timezones # browse zones
sudo timedatectl set-timezone UTC # set the system zone
For server fleets, set every host to UTC. Local-time servers create off-by-one bugs in cron, log correlation, and audit trails that surface only at DST transitions. Render local time in the dashboards that humans look at, not in the kernel.
Hardware clock (RTC)
The real-time clock (battery-backed clock on the motherboard or hypervisor) drifts independently. On boot, the kernel reads it; while running, ntpd/chronyd writes back to it periodically.
sudo hwclock --show # show the RTC
sudo hwclock --systohc # write system time to RTC (one-shot)
Decide whether the RTC stores UTC or local time:
sudo timedatectl set-local-rtc 0 # 0 = UTC (recommended on Linux-only hosts)
sudo timedatectl set-local-rtc 1 # 1 = local (only for dual-boot with Windows)
UTC in the RTC is the right answer for a server. Only flip to local if you dual-boot Windows on the same hardware and want the clock to read correctly under both OSes.
Bootstrap a wildly wrong clock
If the host's clock is hours, days, or years off (commonly after a dead CMOS battery on bare metal, or a fresh VM clone), chronyd may refuse to step it during normal operation. Two options:
One-shot, before starting the daemon:
sudo systemctl stop chronyd
sudo chronyd -q 'server pool.ntp.org iburst'
sudo systemctl start chronyd
chronyd -q queries the server, sets the clock, and exits. Once the clock is in the right ballpark, the daemon takes over.
Or, with ntpd:
sudo systemctl stop ntpd
sudo ntpdate pool.ntp.org
sudo systemctl start ntpd
(ntpdate is deprecated upstream and not installed by default on newer distros — chronyd -q or chronyc -a makestep is preferred.)
Operational tips
- Use at least four upstream sources. NTP's clustering algorithm needs ≥3 to detect and exclude a falseticker; four gives you fault tolerance.
- Mix sources. A pool entry resolves to multiple servers, but on the same network. Mix
pool.ntp.orgwith one or two named anchors (time.cloudflare.com,time.google.com) so a single network event cannot blind you. - Internal NTP servers should peer. With chrony, use
peerbetween two or three internal servers so they cross-check; with ntpd, list each other aspeer(or usepoolandrestrict). - Don't run both daemons. They both bind UDP/123 and will fight.
systemctl is-active chronyd ntpdshould show exactly one asactive. - Watch out for VMs and containers. VMware, Hyper-V, and KVM can inject time into the guest from the host (VMware Tools' "Synchronize guest time with host", Hyper-V time provider, KVM PTP/
kvm_ptp). Decide on one authority — either the hypervisor or NTP — and disable the other. Two competing time sources causes erratic ~1-second corrections. - Containers inherit the host clock and cannot run their own NTP daemon. Sync the host, not the containers.
- Keep
trackingandsourcesunder monitoring. Stratum, offset, andLast offset(chrony) oroffset/jitter(ntpd) are the three numbers that tell you whether the server is healthy. A jump in stratum is your earliest signal that upstream connectivity is broken.
Monitoring NTP health
A silent NTP failure is one of the worst kinds because the symptoms — auth failures, log timestamp gaps, replication lag — show up far away from the cause. Wire NTP state into your monitoring rather than relying on incident-time discovery.
Useful checks:
- chrony: parse
chronyc -c tracking(CSV-friendly output) and alert onLeap status != Normal,Stratum > 4, or|Last offset| > 100 ms. - ntpd: parse
ntpq -c rv 0 'stratum,offset,sys_jitter,leap'and alert on the same thresholds. - Synthetic check: from another host, run
chronyd -Q 'server <yourserver> iburst'(one-shot, no daemon) and assert the offset is small.
Xitoring's server monitoring collects time-sync state alongside CPU, memory, disk, and network so you see drift on the same dashboard as the workload it impacts. The agent runs on CentOS 7/8/9, RHEL, Rocky, Alma, Ubuntu, Debian, and Windows — it picks up chronyd or ntpd automatically.
Troubleshooting
chronyc trackingsaysLeap status: Not synchronised. No upstream is currently usable. Checkchronyc sources -v— if every source has?orx, you have a network/firewall issue or every upstream is temporarily down.ntpq -pnshows no*peer. ntpd has not selected a system peer yet. Wait 5–10 minutes after start; if it still does not select, the upstreams are unreachable, the offset is too large for ntpd to step (usentpdateor restart withtinker panic 0set), or all sources are flagged falsetickers.- Clients cannot reach the server. Verify with
chronyc -h <server> trackingfrom a client (requirescmdallowon the server) or simplync -uvz <server> 123. Re-checkfirewall-cmd --list-all, SELinux denials (ausearch -m AVC -ts recent), and security groups / VPC ACLs in cloud environments. - Stratum 16 forever. Stratum 16 means "no synchronisation". Common causes: outbound UDP/123 blocked, every
serverline points to the same network and that network is down, or the system clock is so far off that the daemon refuses to step. - Time jumps 1 second backwards every minute. A second NTP source is fighting chronyd/ntpd — usually VMware Tools' or Hyper-V's host-time-sync. Disable one side.
chronydcomplains aboutRTC.rtcsyncwrites to/dev/rtcperiodically. In some virtualised environments the RTC is read-only or absent — droprtcsyncfrom the config there.- High
jitteron Wi-Fi or congested links. Wi-Fi adds 5–30 ms of variable latency. Increaseminpoll/maxpollif you are willing to trade responsiveness for stability, or pick upstreams that are network-closer.
Summary
For a fresh CentOS 7 NTP server, the minimum viable setup is:
- Install
chrony(skipntpunless you need it). - Edit
/etc/chrony.conf— list 4 upstreamserverlines,allowyour client networks, keepmakestep 1.0 3andrtcsync. systemctl enable --now chronyd.- Open UDP/123 in firewalld for the client networks only.
- Verify with
chronyc tracking(Leap statusNormal, stratum ≤ 4) andchronyc sources -v. - Monitor stratum, offset, and leap status continuously — silent NTP failures are expensive.
If you are starting fresh today, do this on RHEL 9 / Rocky 9 / Alma 9 instead — the steps are identical apart from dnf install chrony. Either way, the goal is the same: every host on your network agrees on what time it is, within tens of milliseconds, all the time.