Accurate time synchronization is not optional on a server — it is a hard requirement. TLS certificate validation fails when the clock is wrong by more than a few minutes. Kerberos authentication rejects tickets with a clock skew over 5 minutes. Cron jobs fire at the wrong time. Log correlation across multiple servers becomes impossible when event timestamps disagree. Database replication and distributed systems depend on clock agreement for conflict resolution. On RHEL 9, chronyd is the default NTP implementation, replacing the older ntpd daemon that was standard through RHEL 6. Chrony synchronizes faster after a reboot, handles intermittent network connections better, and achieves better accuracy on virtual machines than the classic NTP daemon.

This guide covers installing and configuring Chrony on RHEL 9, choosing NTP server pools, enabling hardware timestamping, configuring a local server as an NTP source for your internal network, verifying synchronization, and hardening the Chrony configuration.

Prerequisites

  • RHEL 9 server with root or sudo access
  • Outbound UDP port 123 allowed in your firewall
  • Network connectivity to NTP servers

Step 1 — Install Chrony

Chrony is included in the default RHEL 9 installation. If it was removed, reinstall it:

dnf install -y chrony

Enable and start the service:

systemctl enable --now chronyd

Confirm it is running:

systemctl status chronyd

Step 2 — Understand the Chrony Configuration File

The main configuration is at /etc/chrony.conf. View the default:

cat /etc/chrony.conf

Key directives:

  • pool / server — NTP sources. pool uses DNS round-robin to select multiple servers from a pool automatically. server specifies a single server.
  • iburst — sends a burst of 4 requests on startup to achieve fast initial synchronization (recommended).
  • driftfile — records the system clock drift rate so Chrony can correct it proactively between syncs.
  • makestep — allows a large one-time step adjustment. Without this, Chrony only slews the clock slowly, which can take hours to correct a large offset.
  • rtcsync — periodically updates the hardware RTC clock from the system clock.
  • allow — networks that are allowed to query this server (when running as an NTP server for other hosts).

Step 3 — Configure NTP Server Pools

Edit /etc/chrony.conf to use appropriate NTP pools:

vi /etc/chrony.conf
# Use the RHEL NTP pool (recommended for RHEL servers)
pool 2.rhel.pool.ntp.org iburst

# Alternative: use the global NTP Pool Project
# pool 0.pool.ntp.org iburst
# pool 1.pool.ntp.org iburst
# pool 2.pool.ntp.org iburst
# pool 3.pool.ntp.org iburst

# For AWS servers, use the AWS Time Sync Service (169.254.169.123 is link-local, always reachable)
# server 169.254.169.123 prefer iburst

# For Google Cloud, use the GCP NTP service
# server metadata.google.internal prefer iburst

# Record clock drift
driftfile /var/lib/chrony/drift

# Allow a one-time step on startup if the clock is more than 1 second off
# The second argument (3) means: allow stepping only in the first 3 clock updates
makestep 1.0 3

# Sync the hardware RTC periodically
rtcsync

# Enable hardware timestamping on all interfaces that support it
hwtimestamp *

# Log tracking, measurements, and statistics
logdir /var/log/chrony
log tracking measurements statistics

Restart Chrony after any configuration change:

systemctl restart chronyd

Step 4 — Verify Time Synchronization

The chronyc command-line tool is the primary interface for Chrony monitoring:

# Show tracking information (offset, RMS offset, frequency error)
chronyc tracking
Reference ID    : 51483EB6 (ntp1.example.com)
Stratum         : 3
Ref time (UTC)  : Mon May 19 14:22:45 2025
System time     : 0.000012345 seconds fast of NTP time
Last offset     : +0.000012123 seconds
RMS offset      : 0.000018234 seconds
Frequency       : 5.234 ppm slow
Residual freq   : +0.002 ppm
Skew            : 0.123 ppm
Root delay      : 0.012345678 seconds
Root dispersion : 0.000543210 seconds
Update interval : 64.2 seconds
Leap status     : Normal
# Show NTP sources and their status
chronyc sources -v

The * symbol in the first column means that source is currently selected as the reference. + means it is acceptable. ? means it cannot be reached.

# Show source statistics (offset, jitter history)
chronyc sourcestats

# Force an immediate sync (useful after changing NTP servers)
chronyc makestep

Step 5 — Open the Firewall for Outbound NTP

NTP uses UDP port 123. Your server needs outbound access to UDP 123 to reach NTP servers:

# Add NTP service to the public zone
firewall-cmd --permanent --zone=public --add-service=ntp
firewall-cmd --reload

Step 6 — Configure the Server as an NTP Source for Your Internal Network

If you have multiple servers and want them all to sync from one internal NTP server rather than all reaching out to public pools, configure one server as the NTP source and point the others to it. On the NTP server, add allow directives:

vi /etc/chrony.conf
# Allow all hosts on the 10.0.0.0/24 internal network to query this server
allow 10.0.0.0/24

# Also serve time even if not synchronised (useful in airgapped networks)
local stratum 10
systemctl restart chronyd

# Open UDP 123 for the internal network
firewall-cmd --permanent --zone=internal --add-service=ntp
firewall-cmd --reload

On all other internal servers, point Chrony at the internal NTP server:

server 10.0.0.1 iburst prefer

Step 7 — Harden the Chrony Configuration

vi /etc/chrony.conf
# Restrict commands to localhost only (no remote chronyc access)
cmdallow 127.0.0.1
cmdallow ::1

# Disable NTP client mode on specific interfaces (if server has multiple NICs)
bindcmdaddress 127.0.0.1
bindcmdaddress ::1

# Use NTP authentication to verify server identity
# Generate a key file: chronyc keygen 1 MD5 256
keyfile /etc/chrony.keys

Step 8 — Check Time Zone Configuration

Chrony synchronizes UTC time. The system displays local time based on the timezone setting:

# View current timezone
timedatectl

# List available timezones
timedatectl list-timezones | grep America

# Set timezone to UTC (recommended for servers)
timedatectl set-timezone UTC

# Or set to a specific timezone
timedatectl set-timezone America/New_York

Server best practice: always use UTC for servers. Set the timezone of your log viewers and monitoring tools instead. This avoids daylight saving time complications in log files and cron schedules.

Verification Checklist

# Full tracking summary
chronyc tracking

# Is NTP synchronized?
timedatectl | grep synchronized

# Current sources
chronyc sources

# Current date and time
date

# Hardware clock vs system clock
hwclock --show

Troubleshooting

  • chronyc tracking shows no reference clock — Chrony cannot reach any NTP servers. Check outbound UDP 123 in your firewall, and confirm the NTP server hostnames resolve: dig 2.rhel.pool.ntp.org.
  • System time drifts rapidly even with Chrony running — common on VMs under heavy host load. Enable hardware timestamping with hwtimestamp * and consider using the cloud provider’s internal NTP source (e.g., AWS 169.254.169.123) which is more tolerant of VM time drift.
  • makestep not applying after config change — run chronyc makestep manually to force an immediate step correction, then verify with chronyc tracking.

Conclusion

Your RHEL 9 server now has Chrony configured for accurate, reliable time synchronization. You have selected appropriate NTP pools, enabled hardware timestamping, set the correct timezone, and optionally configured the server as an internal NTP source for your network. Accurate time is the invisible prerequisite for TLS, Kerberos, distributed systems, and log analysis.

Next steps: How to Configure Automatic Security Updates on RHEL 9, How to Configure the Firewall on RHEL 9 with firewalld, and How to Audit System Activity with auditd on RHEL 9.