NTP Client

This role installs and configures Chrony as an NTP (Network Time Protocol) client for accurate time synchronization.

ARA Ansible Bash Chrony Debian NTP OPNsense RedHat

NTP Client Role

Overview

This role installs and configures Chrony as an NTP (Network Time Protocol) client for accurate time synchronization. It installs the chrony package, configures it to sync with the OPNsense firewall as the local NTP server, enables RTC (Real-Time Clock) synchronization, configures clock stepping for large time offsets, and ensures the chronyd service is enabled and started.

Purpose

  • Time Synchronization: Keep system clocks accurate and synchronized
  • Local NTP Server: Sync from OPNsense firewall (not public internet)
  • Clock Drift Management: Track and compensate for hardware clock drift
  • Large Offset Handling: Automatically step clock for large time differences
  • RTC Sync: Synchronize hardware real-time clock with system time
  • Client-Only Mode: Doesn’t listen for incoming NTP requests
  • Logging: Track synchronization status and issues

Requirements

  • Ansible 2.9 or higher
  • Linux system (RedHat or Debian-based)
  • Network connectivity to NTP server (OPNsense)
  • Proper sudo/root permissions
  • OPNsense firewall configured as NTP server

What is NTP?

NTP (Network Time Protocol) is a protocol for synchronizing computer clocks:

  • Provides accurate time from reference clocks
  • Compensates for network latency
  • Adjusts for clock drift
  • Essential for logs, authentication, certificates, distributed systems
  • Uses UDP port 123

Chrony is a modern NTP implementation that:

  • Performs better than traditional ntpd
  • Handles intermittent network connections
  • Faster synchronization
  • Better accuracy
  • Lower resource usage

Role Variables

Optional Variables

VariableDefaultDescription
ntp_client_chrony_packagechronyPackage name to install
ntp_client_driftfile/var/lib/chrony/driftDrift file location
ntp_client_drift_seconds1Drift measurement interval
ntp_client_drift_limit3Maximum drift before stepping
ntp_client_port0NTP listen port (0 = client-only)
ntp_client_log_path/var/log/chronyLog directory
ntp_client_config_pathOS-specificConfig directory path
ntp_client_config_file_namechrony.confConfig file name

Variable Details

ntp_client_driftfile

File where Chrony stores clock drift information.

Default: /var/lib/chrony/drift

Purpose: Helps Chrony quickly adjust after restart by remembering previous drift rate.

ntp_client_drift_seconds and ntp_client_drift_limit

Controls the makestep directive for handling large time offsets.

Default:

ntp_client_drift_seconds: 1  # Step if offset > 1 second
ntp_client_drift_limit: 3    # Only for first 3 sync attempts

Meaning: If clock is off by more than 1 second, step (jump) the time immediately, but only during the first 3 synchronization attempts. After that, slew (gradually adjust) the time.

Why this matters:

  • Stepping: Instant time change (can break running services)
  • Slewing: Gradual adjustment (slower but safer)
  • First 3 attempts: Allows quick correction on boot
  • After 3 attempts: Prevents sudden jumps during operation

ntp_client_port

NTP listening port.

Default: 0 (client-only mode)

Values:

  • 0: Client only (doesn’t serve time to others)
  • 123: Server mode (accepts requests from other clients)

This role configures client-only mode - doesn’t serve NTP to other systems.

ntp_client_config_path

OS-specific configuration directory.

Default:

ntp_client_config_path:
  debian: /etc/chrony
  redhat: /etc

Results:

  • Debian/Ubuntu: /etc/chrony/chrony.conf
  • RHEL/CentOS: /etc/chrony.conf

Dependencies

This role has no dependencies on other Ansible roles, but requires:

  • OPNsense firewall configured as NTP server
  • Network connectivity to OPNsense on VLAN10
  • OPNsense IP available in inventory (hostvars['opnsense']['ip_vlan10'])

Example Playbook

Basic Usage

---
- name: Configure NTP Client
  hosts: all
  become: true

  roles:
    - ntp_client

Custom NTP Server

---
- name: Configure NTP with Custom Server
  hosts: all
  become: true

  vars:
    # Override template to use different server
    # (requires modifying template)

  roles:
    - ntp_client

With Custom Drift Settings

---
- name: Configure NTP with Custom Drift
  hosts: all
  become: true

  vars:
    ntp_client_drift_seconds: 0.5  # Step if offset > 0.5 seconds
    ntp_client_drift_limit: 5       # Allow stepping for first 5 syncs

  roles:
    - ntp_client

What This Role Does

  1. Installs chrony package

    • Package name: chrony
    • Includes chronyd daemon and chronyc client tool
  2. Deploys chrony.conf from template:

    • Sets NTP server to OPNsense IP (VLAN10)
    • Configures drift file location
    • Enables RTC synchronization
    • Sets makestep parameters
    • Configures log directory
    • Sets port to 0 (client-only)
  3. Creates backup of existing config (if any)

  4. Restarts chronyd service (via handler):

    • Applies new configuration
    • Ensures service is enabled (starts on boot)

Chrony Configuration

Generated Configuration File

Location:

  • Debian: /etc/chrony/chrony.conf
  • RedHat: /etc/chrony.conf

Content:

# Use asus-opnsense as NTP server
server 192.168.x.x iburst

# Driftfile used to help synchronisation
driftfile /var/lib/chrony/drift

# RTC synchronisation
rtcsync

# Allow chrony to step up the time directly if it's offset by at least one second for the first 3 synchronisation attempts
makestep 1 3

# Where logs are stored
logdir /var/log/chrony

# Listen port : 0 for clients, 123 for servers
port 0

Configuration Directives Explained

server: NTP server to synchronize with

server 192.168.x.x iburst
  • iburst: Send burst of packets at startup for faster sync

driftfile: Store clock drift rate

driftfile /var/lib/chrony/drift
  • Remembers how fast/slow hardware clock runs
  • Helps maintain accuracy across reboots

rtcsync: Sync hardware clock every 11 minutes

rtcsync
  • Keeps RTC (BIOS clock) synchronized
  • Important for dual-boot systems

makestep: Handle large time offsets

makestep 1 3
  • First parameter: Threshold in seconds (1)
  • Second parameter: Number of updates to allow stepping (3)
  • After 3 updates, only slew (gradual adjustment)

logdir: Log file location

logdir /var/log/chrony
  • Stores measurements, statistics, tracking logs

port: Listening port

port 0
  • 0: Don’t listen (client-only)
  • 123: Standard NTP port (server mode)

NTP Server Configuration

OPNsense as NTP Server

The role configures clients to sync from OPNsense firewall:

server {{ hostvars['opnsense']['ip_vlan10'] }} iburst

Why OPNsense?

  • Central network device (always on)
  • Already synced with upstream NTP servers
  • Low latency (local network)
  • Single source of truth for network time
  • Reduces external NTP traffic

OPNsense NTP Configuration (manual setup required):

  1. Log into OPNsense
  2. Services → NTP (ntpd)
  3. Enable NTP server
  4. Configure upstream servers (e.g., pool.ntp.org)
  5. Allow NTP access from internal networks

Alternative: Public NTP Servers

To use public servers instead (requires template modification):

server 0.pool.ntp.org iburst
server 1.pool.ntp.org iburst
server 2.pool.ntp.org iburst
server 3.pool.ntp.org iburst

Service Management

Chronyd Service

# Check status
systemctl status chronyd

# Start service
systemctl start chronyd

# Stop service
systemctl stop chronyd

# Restart service
systemctl restart chronyd

# Enable on boot
systemctl enable chronyd

# View logs
journalctl -u chronyd -f

Chrony Client Commands

# Check sync status
chronyc tracking

# Show time sources
chronyc sources

# Show detailed source stats
chronyc sourcestats

# Force sync (for testing)
chronyc makestep

# Check NTP activity
chronyc activity

Verifying Time Synchronization

Check Tracking Status

$ chronyc tracking
Reference ID    : C0A80A01 (192.168.x.x)
Stratum         : 3
Ref time (UTC)  : Wed Jan 08 10:30:45 2026
System time     : 0.000123456 seconds fast of NTP time
Last offset     : +0.000098765 seconds
RMS offset      : 0.000234567 seconds
Frequency       : 5.432 ppm slow
Residual freq   : +0.001 ppm
Skew            : 0.123 ppm
Root delay      : 0.001234567 seconds
Root dispersion : 0.000123456 seconds
Update interval : 64.5 seconds
Leap status     : Normal

Key fields:

  • Reference ID: NTP server IP (OPNsense)
  • Stratum: Distance from reference clock (lower is better)
  • System time: Offset from NTP time
  • Last offset: Most recent time adjustment
  • Frequency: Clock drift rate

Check Time Sources

$ chronyc sources
MS Name/IP address         Stratum Poll Reach LastRx Last sample
===============================================================================
^* 192.168.x.x                  2   6   377    34    +12us[  +25us] +/-   15ms

Symbols:

  • ^: Server
  • *: Currently selected source
  • +: Acceptable source
  • ?: Connectivity issues
  • x: False ticker (large offset)

Fields:

  • Reach: Reachability (377 = all recent polls successful)
  • LastRx: Seconds since last response
  • Last sample: Offset from source

Check System Time

# Show system time
date

# Compare with NTP time
chronyc tracking | grep "System time"

# Check hardware clock
hwclock --show

Clock Drift Explanation

What is Clock Drift?

Hardware clocks aren’t perfect - they run slightly fast or slow:

  • Temperature changes affect crystal oscillators
  • Aging components drift over time
  • Typical drift: 1-50 ppm (parts per million)
  • 10 ppm = 0.864 seconds per day

How Chrony Handles Drift

  1. Measures drift by comparing to NTP server
  2. Stores in drift file: /var/lib/chrony/drift
  3. Applies correction continuously
  4. Updates RTC every 11 minutes (rtcsync)

Example drift file:

5.432

Meaning: Clock runs 5.432 ppm slow (needs speeding up)

Makestep Behavior

Stepping vs Slewing

Stepping: Instant time change

  • Pros: Fast correction
  • Cons: Can break applications, logs, databases
  • Used for: Large offsets at startup

Slewing: Gradual adjustment

  • Pros: Safe for running applications
  • Cons: Slow correction (max 0.5ms per second)
  • Used for: Small offsets during normal operation

Role configuration:

makestep 1 3
  • Step if offset > 1 second
  • Only during first 3 sync attempts
  • After that, always slew

When Does Stepping Occur?

  1. System boot: Clock likely wrong, step immediately
  2. After suspend: Time jumped, step on first sync
  3. Large manual change: If someone sets wrong time

After 3 successful syncs, Chrony assumes clock is roughly correct and only slews.

Firewall Configuration

OPNsense firewall needs to allow NTP:

# OPNsense firewall rule
Protocol: UDP
Source: Internal networks (VLAN10, VLAN12, etc.)
Destination: OPNsense (this firewall)
Port: 123
Action: Allow

Clients also need firewall rules (if firewall is active):

# Allow NTP client (outgoing to OPNsense)
firewall-cmd --permanent --add-service=ntp
firewall-cmd --reload

Security Considerations

  • Client-Only Mode: Port 0 prevents serving time to others
  • Single NTP Source: OPNsense only (reduces attack surface)
  • No Authentication: Not configured (consider NTP authentication for high-security)
  • Local Network: NTP traffic stays within network (not internet)
  • Firewall Protection: OPNsense filters NTP traffic

Tags

This role does not define any tags. Use playbook-level tags if needed:

- hosts: all
  roles:
    - ntp_client
  tags:
    - ntp
    - time
    - chrony
    - sync

Notes

  • Role uses Chrony (not ntpd)
  • Syncs from OPNsense firewall (not public NTP servers)
  • Client-only mode (port 0)
  • RTC synchronization enabled
  • Makestep allows quick boot-time correction
  • Configuration backed up before changes
  • Service automatically enabled on boot
  • Works on both RedHat and Debian-based systems

Troubleshooting

Time not synchronizing

Check chronyd status:

systemctl status chronyd

# If not running:
systemctl start chronyd
systemctl enable chronyd

Check sources:

chronyc sources

# Look for:
# ^* (currently synced)
# ^? (no response - connectivity issue)

Check connectivity to OPNsense:

# Ping OPNsense
ping 192.168.x.x

# Test NTP port
nc -u -v 192.168.x.x 123

“No suitable source found” error

Cause: Can’t reach NTP server

Solution:

# Check OPNsense IP
grep "^server" /etc/chrony/chrony.conf

# Verify OPNsense NTP service running
# (check on OPNsense: Services → NTP)

# Check firewall rules allow NTP
firewall-cmd --list-services | grep ntp

Time jumps after role execution

Expected behavior: If system time was wrong, role allows stepping during first 3 syncs.

Check offset:

chronyc tracking | grep "System time"

If large offset:

  • First sync: May step immediately
  • Subsequent syncs: Gradual adjustment

Clock drift too high

Check drift file:

cat /var/lib/chrony/drift

# Typical values: -50 to +50 ppm
# High values (>100): Hardware issue or incorrect NTP server

Solutions:

  • Wait for drift measurement to stabilize (hours/days)
  • Check hardware (temperature, failing RTC battery)
  • Verify NTP server is accurate

Logs showing “No suitable source”

Check logs:

journalctl -u chronyd -n 50

# Look for:
# "No suitable source" - can't reach any servers
# "Selected source" - successfully synced

Solutions:

  • Verify OPNsense IP correct in config
  • Check network connectivity
  • Verify OPNsense NTP service running
  • Check firewall rules

Testing After Role Execution

Verify Chrony Installed

# Check package
rpm -q chrony  # RedHat
dpkg -l chrony  # Debian

# Check version
chronyd -v

Verify Configuration

# Check config file exists
ls -l /etc/chrony/chrony.conf  # Debian
ls -l /etc/chrony.conf         # RedHat

# View configuration
cat /etc/chrony/chrony.conf

# Should show OPNsense as server

Verify Service Running

# Check service active
systemctl is-active chronyd
# Should output: active

# Check service enabled
systemctl is-enabled chronyd
# Should output: enabled

Verify Time Synchronization

# Check tracking
chronyc tracking

# Should show:
# Reference ID: OPNsense IP
# System time: Small offset (< 1ms)

# Check sources
chronyc sources

# Should show:
# ^* 192.168.x.x  (star means synced)

Verify RTC Sync

# Check system time
date

# Check hardware clock
hwclock --show

# Should be very close (within seconds)

Performance Considerations

  • CPU Usage: Minimal (<0.1% CPU)
  • Memory Usage: ~2-4 MB RAM
  • Network Traffic: Very low (~1 KB every 64 seconds)
  • Disk I/O: Minimal (drift file updates occasionally)

Best Practices

  1. Use local NTP server (OPNsense) not public servers
  2. Enable RTC sync (already configured by role)
  3. Allow stepping at boot (makestep directive)
  4. Monitor sync status regularly
  5. Check logs for synchronization issues
  6. Verify drift file updates over time
  7. Test after timezone changes
  8. Document NTP server (OPNsense in this case)
  9. Keep chronyd running (critical service)
  10. Verify accuracy periodically with external source

Why Time Synchronization Matters

Critical for:

  • Logging: Correlate events across systems
  • Authentication: Kerberos, LDAP time-sensitive
  • SSL/TLS: Certificate validity checks
  • Databases: Transaction ordering, replication
  • Monitoring: Accurate metrics timestamps
  • Compliance: Audit trails require accurate time
  • Distributed Systems: Consensus, ordering

What Happens Without NTP?

  • System clocks drift apart (seconds to minutes per day)
  • Log timestamps inconsistent
  • Authentication failures (time-based tokens)
  • Certificate validation errors
  • Monitoring alerts with wrong times
  • Database replication issues

This role is often used with:

  • System setup roles: Configure time before other services
  • Logging roles: Accurate timestamps for log aggregation
  • Monitoring roles: Precise metrics collection times

License

MIT

Author

Created for homelab infrastructure management.