Introduction
Accurate time synchronization is critical for distributed systems — log correlation, TLS certificate validation, database replication, and Kerberos authentication all depend on it. Chrony is the default NTP implementation on modern Linux distributions (RHEL 8+, Ubuntu 20.04+), replacing the legacy ntpd. Ansible makes it easy to configure Chrony consistently across your entire fleet.
Basic Chrony Deployment
---
- name: Configure time synchronization with Chrony
hosts: all
become: true
vars:
ntp_servers:
- 0.pool.ntp.org
- 1.pool.ntp.org
- 2.pool.ntp.org
- 3.pool.ntp.org
chrony_timezone: "UTC"
tasks:
- name: Install Chrony
ansible.builtin.package:
name: chrony
state: present
- name: Set timezone
community.general.timezone:
name: "{{ chrony_timezone }}"
- name: Configure Chrony
ansible.builtin.template:
src: chrony.conf.j2
dest: /etc/chrony.conf
owner: root
group: root
mode: "0644"
backup: true
notify: restart chrony
- name: Enable and start Chrony
ansible.builtin.systemd:
name: chronyd
enabled: true
state: started
- name: Wait for time sync
ansible.builtin.command:
cmd: chronyc waitsync 10 0.1
register: sync_result
changed_when: false
failed_when: false
- name: Check synchronization status
ansible.builtin.command:
cmd: chronyc tracking
register: tracking
changed_when: false
- name: Display sync status
ansible.builtin.debug:
msg: "{{ tracking.stdout_lines }}"
handlers:
- name: restart chrony
ansible.builtin.systemd:
name: chronyd
state: restarted
Configuration Template
# templates/chrony.conf.j2
# Ansible managed — {{ template_host }}
# NTP servers
{% for server in ntp_servers %}
server {{ server }} iburst
{% endfor %}
{% if inventory_hostname in groups.get('ntp_servers', []) %}
# This host is an NTP server — allow clients
{% for network in chrony_allow_networks | default(['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16']) %}
allow {{ network }}
{% endfor %}
local stratum 10
{% endif %}
# Record the rate at which the system clock gains/loses time
driftfile /var/lib/chrony/drift
# Allow the system clock to be stepped in the first three updates
makestep 1.0 3
# Enable kernel synchronization of the real-time clock (RTC)
rtcsync
# Specify directory for log files
logdir /var/log/chrony
# Select which information is logged
log measurements statistics tracking
NTP Server + Client Hierarchy
# group_vars/ntp_servers.yml
ntp_servers:
- 0.pool.ntp.org
- 1.pool.ntp.org
- 2.pool.ntp.org
chrony_allow_networks:
- "10.0.0.0/8"
# group_vars/all.yml (clients point to internal NTP servers)
ntp_servers: "{{ groups['ntp_servers'] | map('extract', hostvars, 'ansible_host') | list }}"
# inventory
[ntp_servers]
ntp-1 ansible_host=10.0.0.10
ntp-2 ansible_host=10.0.0.11
[webservers]
web-1 ansible_host=10.0.1.10
web-2 ansible_host=10.0.1.11
[databases]
db-1 ansible_host=10.0.2.10
Firewall Configuration
- name: Configure firewall for NTP
hosts: ntp_servers
become: true
tasks:
- name: Allow NTP traffic (firewalld)
ansible.posix.firewalld:
service: ntp
permanent: true
state: enabled
immediate: true
when: ansible_facts.services['firewalld.service'] is defined
- name: Allow NTP traffic (ufw)
community.general.ufw:
rule: allow
port: "123"
proto: udp
when: ansible_os_family == "Debian"
Monitoring and Validation
- name: Validate time synchronization
hosts: all
become: true
tasks:
- name: Check Chrony sources
ansible.builtin.command:
cmd: chronyc sources -v
register: sources
changed_when: false
- name: Check tracking info
ansible.builtin.command:
cmd: chronyc tracking
register: tracking
changed_when: false
- name: Extract system clock offset
ansible.builtin.set_fact:
clock_offset_ms: "{{ tracking.stdout | regex_search('System time\\s+:\\s+([\\d.]+)', '\\1') | first | float * 1000 }}"
- name: Assert time is synchronized
ansible.builtin.assert:
that:
- clock_offset_ms | float < 100 # Less than 100ms offset
fail_msg: "Clock offset {{ clock_offset_ms }}ms exceeds threshold"
success_msg: "Clock offset {{ clock_offset_ms }}ms — OK"
- name: Report NTP source count
ansible.builtin.debug:
msg: "{{ sources.stdout_lines | select('match', '^\\^') | list | length }} NTP sources configured"
Troubleshooting
# Check sync status
chronyc tracking
# List NTP sources with status
chronyc sources -v
# Check source statistics
chronyc sourcestats
# Force immediate sync
chronyc makestep
# Check NTP activity
chronyc activity
# Verify NTP port is reachable
nc -zuv ntp-server 123
Related Articles
- Ansible systemd Service Management
- Ansible Configuration Drift Detection
- Ansible Firewalld UFW Module
- Ansible assert Module
- Ansible Compliance as Code
Conclusion
Chrony is the modern standard for NTP on Linux. With Ansible, you can deploy a consistent time synchronization hierarchy — dedicated NTP servers syncing to public pools, with all other hosts pointing to your internal servers. Add monitoring assertions to catch clock drift before it causes authentication failures or log correlation issues.