Close Menu
DPC Virtual Tips
    Read More

    Linux ss, lsof, and fuser Commands: A Practical Guide

    August 4, 2026

    Linux Commands to Investigate High Disk Partition Usage

    July 20, 2026

    Essential Slurm Administration Commands Every HPC Administrator Should Know

    July 15, 2026
    • Home
    • About Us
    • Contact
    • Cookie Policy
    • Comment Policy
    • Privacy Policy
    • Terms of Use
    • Disclaimer
    Tuesday, August 4
    DPC Virtual Tips
    • Home
    • Operating Systems
    • CLI
    • PowerFlex
    • HPC
    • Virtualization
    • About Us
    • Contact
    DPC Virtual Tips
    Home » Creating an Ansible Playbook for Keeping NTP Client Settings
    Operating Systems

    Creating an Ansible Playbook for Keeping NTP Client Settings

    DaniloBy DaniloOctober 23, 2025Updated:August 2, 2026No Comments10 Mins Read
    Facebook Twitter Pinterest LinkedIn Tumblr Email
    Ansible chrony playbook
    Share
    Facebook Twitter LinkedIn Pinterest Email

    Creating an Ansible chrony playbook is an effective way to automate NTP client configuration across multiple Linux servers. Consistent time synchronization is essential for reliable communication, authentication, logging, and monitoring in distributed environments.

    Chrony is an implementation of the Network Time Protocol (NTP) commonly used on Linux systems to synchronize system clocks with external time sources. By combining Ansible automation with chrony configuration templates, administrators can maintain consistent settings across many managed nodes.

    In this guide, we will create an Ansible playbook that deploys a standardized chrony configuration, uses variables and templates, and validates time synchronization after applying the changes. This approach helps simplify configuration management in enterprise Linux environments.

    Are you new to Ansible? Don’t worry about that!
    We’ve written an article explaining what Ansible is and what you need to do to use Ansible for the first time. Click here to read the article 🙂

    Accurate timekeeping is one of the most critical configurations required for reliable communication between network-based computers. Computers can utilize the Network Time Protocol (NTP) to synchronize their time to an upstream time server or a pool of servers to maintain accurate time.

    First and foremost: What is chrony?

    Chrony is an implementation of the Network Time Protocol (NTP). Many Linux distributions have it installed by default. We can use Chrony:

    • to synchronize the system clock with NTP servers.
    • to synchronize the system clock with a reference clock, for example, a GPS receiver.
    • to synchronize the system clock with a manual time input.
    • as an NTPv4(RFC 5905) server or peer providing a time service to other computers on the network.

    Chrony consists of chronyd, a daemon that runs in user space, and chronyc, a command-line program which can be used to monitor the performance of chronyd and to change various operating parameters when it is running. The default configuration file for chronyd is /etc/chrony.conf.

    Creating the Inventory

    Before going forward, our environment is composed of:

    • One Control VM (A CentOS 9 VM that runs Ansible).
    • Six Managed VMs (Red Hat 8 VMs that Ansible manages).

    Let’s create a directory to store the inventory file:

    mkdir -p /root/ansible/inventory

    Create the hosts.ini file:

    touch /root/ansible/inventory/hosts.ini

    And then, add the hosts to hosts.ini. In this case, for instance, our inventory file has some groups

    [hpc2_login_nodes]    # This is a group
    hpc2-login
    
    [hpc2_head_nodes]     # This is a group
    hpc2-head
    
    [hpc2_compute_nodes]   # This is a group
    hpc2-node[01:06]
    
    [all:vars]             # This is a variable section
    ansible_user=root      # This is a variable applies to all hosts

    Creating the Template

    Let’s create a directory to store the template file:

    mkdir -p /root/ansible/templates

    Create the template for chrony configuration (the template content must be written using the Jinja2 format):

    touch /root/ansible/templates/chrony.conf.j2

    And then, add the following content to the chrony.conf.j2:

    #+++++++++++++++++++++
    # Managed by Ansible #
    #+++++++++++++++++++++
    {#
    Template for chrony.conf
    Variables:
      chrony_pool      - NTP pool or server
      chrony_driftfile - path to driftfile
      chrony_makestep  - makestep value
      chrony_keyfile   - path to keyfile
      chrony_leapsectz - leap second timezone
      chrony_logdir    - log directory
    #}
    pool {{ chrony_pool }} iburst
    driftfile {{ chrony_driftfile }}
    makestep {{ chrony_makestep }}
    rtcsync
    keyfile {{ chrony_keyfile }}
    leapsectz {{ chrony_leapsectz }}
    logdir {{ chrony_logdir }}

    Note: The template contains many variables (in red). For example, “chrony_pool” is a variable. All variables will be defined in a YAML file and will be used by the Playbook to generate the final chrony.conf file!

    Creating the Variables File

    Ansible can get variables from many places. If the same variable exists in multiple places, it follows a priority order (precedence) to decide which one wins. Basically (there is a complete list in the Ansible documentation):

    From highest priority → lowest (it means that if you have declared a variable named test inside the playbook and the same variable in the main.yml file, the variable inside the playbook “wins” because it will be preferable):

    1. Variables defined directly inside a playbook (vars: section)
    2. vars_prompt inside the playbook
    3. Variables loaded with vars_files: in a playbook
    4. host_vars/.yml
    5. group_vars/.yml
    6. Facts collected from the host (gather_facts)
    7. Role defaults (roles/*/defaults/main.yml) (lowest priority)

    Let’s create a directory to store the variables file. In this case, we’re using the “group_vars” dir inside the inventory dir that was previously created:

    mkdir -p /root/ansible/inventory/group_vars

    Create a var file all.yml (the file extension can be .yaml or .yml):

    touch /root/ansible/inventory/group_vars/all.yml

    And then, add the following content – as we can see, there are all variables defined on the template chrony.conf.j2 and their values 😉

    Note: Adjust the variable values according to your environment. Being honest, the only variable that you must change is the “chrony_pool” 😉

    chrony_pool: "192.168.255.3"
    chrony_driftfile: "/var/lib/chrony/drift"
    chrony_makestep: "1.0 3"
    chrony_keyfile: "/etc/chrony.keys"
    chrony_leapsectz: "right/UTC"
    chrony_logdir: "/var/log/chrony"

    Creating the Playbook

    Let’s create a directory to store the playbook files:

    mkdir -p /root/ansible/playbooks

    Inside the playbooks directory, generate the playbook file chrony.yml:

    touch /root/ansible/playbooks/chrony.yml

    And then, add the following content:

    ---
    - name: Deploy chrony configuration and validate (auto-fix)
      hosts: all
      become: yes
    
      tasks:
        - name: Deploy chrony.conf from template
          template:
            src: ../templates/chrony.conf.j2     # Here is the template file
            dest: /etc/chrony.conf
            owner: root
            group: root
            mode: '0644'
          notify: Restart chronyd
    
        - name: Pause to allow chronyd restart
          pause:
            seconds: 3
    
        - name: Check if chrony is synchronized
          shell: chronyc sources | grep '\^\*'
          register: chrony_sync
          failed_when: false      # don't fail here
          changed_when: false
    
        - name: Restart chronyd if not synchronized
          service:
            name: chronyd
            state: restarted
          when: chrony_sync.rc != 0
    
        - name: Wait and re-check synchronization after fix
          shell: chronyc sources | grep '\^\*'
          register: chrony_sync_after
          when: chrony_sync.rc != 0
          failed_when: false
          changed_when: false
    
        - name: Final validation — fail if still not synced
          fail:
            msg: "Chrony is still NOT synchronized after auto-fix"
          when: (chrony_sync_after is defined) and ((chrony_sync_after.rc | default(0)) != 0)
    
        - name: Success message
          debug:
            msg: "Chrony synchronized successfully"
          when: (chrony_sync_after is defined) and ((chrony_sync_after.rc | default(0)) != 0)
    
      handlers:
        - name: Restart chronyd
          service:
            name: chronyd
            state: restarted

    When we run the playbook, the chrony.conf file will be dynamically created and sent to all nodes!

    Running the Playbook

    To run the playbook:

    ansilble-playbook -i inventory/hosts.ini playbooks/chrony.yml

    Output’s example:

    PLAY [Deploy chrony configuration and validate (auto-fix)] ******************************************************************************************************************
    
    TASK [Gathering Facts] ******************************************************************************************************************************************************
    ok: [hpc2-login]
    ok: [hpc2-node01]
    ok: [hpc2-node03]
    ok: [hpc2-head]
    ok: [hpc2-node02]
    ok: [hpc2-node04]
    ok: [hpc2-node05]
    ok: [hpc2-node06]
    
    TASK [Deploy chrony.conf from template] *************************************************************************************************************************************
    ok: [hpc2-node01]
    ok: [hpc2-node02]
    ok: [hpc2-login]
    ok: [hpc2-head]
    ok: [hpc2-node03]
    ok: [hpc2-node04]
    ok: [hpc2-node06]
    ok: [hpc2-node05]
    
    TASK [Pause to allow chronyd restart] ***************************************************************************************************************************************
    Pausing for 3 seconds
    (ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)
    ok: [hpc2-login]
    
    TASK [Check if chrony is synchronized] **************************************************************************************************************************************
    ok: [hpc2-login]
    ok: [hpc2-node02]
    ok: [hpc2-head]
    ok: [hpc2-node03]
    ok: [hpc2-node01]
    ok: [hpc2-node04]
    ok: [hpc2-node05]
    ok: [hpc2-node06]
    
    TASK [Restart chronyd if not synchronized] **********************************************************************************************************************************
    skipping: [hpc2-login]
    skipping: [hpc2-head]
    skipping: [hpc2-node01]
    skipping: [hpc2-node02]
    skipping: [hpc2-node03]
    skipping: [hpc2-node04]
    skipping: [hpc2-node05]
    skipping: [hpc2-node06]
    
    TASK [Wait and re-check synchronization after fix] **************************************************************************************************************************
    skipping: [hpc2-login]
    skipping: [hpc2-head]
    skipping: [hpc2-node01]
    skipping: [hpc2-node02]
    skipping: [hpc2-node03]
    skipping: [hpc2-node04]
    skipping: [hpc2-node05]
    skipping: [hpc2-node06]
    
    TASK [Final validation — fail if still not synced] **************************************************************************************************************************
    skipping: [hpc2-login]
    skipping: [hpc2-head]
    skipping: [hpc2-node01]
    skipping: [hpc2-node02]
    skipping: [hpc2-node03]
    skipping: [hpc2-node04]
    skipping: [hpc2-node05]
    skipping: [hpc2-node06]
    
    TASK [Success message] ******************************************************************************************************************************************************
    skipping: [hpc2-login]
    skipping: [hpc2-head]
    skipping: [hpc2-node01]
    skipping: [hpc2-node02]
    skipping: [hpc2-node03]
    skipping: [hpc2-node04]
    skipping: [hpc2-node05]
    skipping: [hpc2-node06]
    
    PLAY RECAP ******************************************************************************************************************************************************************
    hpc2-head                  :ok=3 changed=0    unreachable=0    failed=0skipped=4 rescued=0    ignored=0
    hpc2-login                 :ok=4 changed=0    unreachable=0    failed=0skipped=4 rescued=0    ignored=0
    hpc2-node01                :ok=3 changed=0    unreachable=0    failed=0skipped=4 rescued=0    ignored=0
    hpc2-node02                :ok=3 changed=0    unreachable=0    failed=0skipped=4 rescued=0    ignored=0
    hpc2-node03                :ok=3 changed=0    unreachable=0    failed=0skipped=4 rescued=0    ignored=0
    hpc2-node04                :ok=3 changed=0    unreachable=0    failed=0skipped=4 rescued=0    ignored=0
    hpc2-node05                :ok=3 changed=0    unreachable=0    failed=0skipped=4 rescued=0    ignored=0
    hpc2-node06                :ok=3 changed=0    unreachable=0    failed=0skipped=4 rescued=0    ignored=0

    Note: The playbook has many “plays”, each with tasks to achieve a specific purpose (or maintain a desired state)!

    Let’s take a look at the “PLAY RECAP” result of the Playbook:

    ok=3 changed=0

    That means:

    — It checked the state of each item.
    — It found that the system already matches the desired state.
    — It did not re-run or reinstall anything.
    — No redundant action, no disruption — safe by design.

    Now, let’s do a test:

    • For testing purposes, I’ll delete all chrony.conf configuration file for all managed hosts.
    • Additionally, I’ll stop the chronyd service too.
    • And afterward, I’ll run the Playbook again. Let’s analyze the results:
    PLAY [Deploy chrony configuration and validate (auto-fix)] ******************************************************************************************************************
    
    TASK [Gathering Facts] ******************************************************************************************************************************************************
    ok: [hpc2-node01]
    ok: [hpc2-login]
    ok: [hpc2-head]
    ok: [hpc2-node03]
    ok: [hpc2-node02]
    ok: [hpc2-node06]
    ok: [hpc2-node04]
    ok: [hpc2-node05]
    
    TASK [Deploy chrony.conf from template] *************************************************************************************************************************************
    ok: [hpc2-login]
    ok: [hpc2-head]
    changed: [hpc2-node02]
    changed: [hpc2-node03]
    changed: [hpc2-node01]
    changed: [hpc2-node04]
    changed: [hpc2-node05]
    changed: [hpc2-node06]
    
    TASK [Pause to allow chronyd restart] ***************************************************************************************************************************************
    Pausing for 3 seconds
    (ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)
    ok: [hpc2-login]
    
    TASK [Check if chrony is synchronized] **************************************************************************************************************************************
    ok: [hpc2-head]
    ok: [hpc2-node03]
    ok: [hpc2-node02]
    ok: [hpc2-node01]
    ok: [hpc2-login]
    ok: [hpc2-node04]
    ok: [hpc2-node06]
    ok: [hpc2-node05]
    
    TASK [Restart chronyd if not synchronized] **********************************************************************************************************************************
    skipping: [hpc2-login]
    skipping: [hpc2-head]
    changed: [hpc2-node02]
    changed: [hpc2-node04]
    changed: [hpc2-node03]
    changed: [hpc2-node01]
    changed: [hpc2-node05]
    changed: [hpc2-node06]
    
    TASK [Wait and re-check synchronization after fix] **************************************************************************************************************************
    skipping: [hpc2-login]
    skipping: [hpc2-head]
    ok: [hpc2-node01]
    ok: [hpc2-node04]
    ok: [hpc2-node02]
    ok: [hpc2-node03]
    ok: [hpc2-node05]
    ok: [hpc2-node06]
    
    TASK [Final validation — fail if still not synced] **************************************************************************************************************************
    skipping: [hpc2-login]
    skipping: [hpc2-head]
    fatal: [hpc2-node01]: FAILED! => {"changed": false, "msg": "Chrony is still NOT synchronized after auto-fix"}
    fatal: [hpc2-node02]: FAILED! => {"changed": false, "msg": "Chrony is still NOT synchronized after auto-fix"}
    fatal: [hpc2-node03]: FAILED! => {"changed": false, "msg": "Chrony is still NOT synchronized after auto-fix"}
    fatal: [hpc2-node04]: FAILED! => {"changed": false, "msg": "Chrony is still NOT synchronized after auto-fix"}
    fatal: [hpc2-node05]: FAILED! => {"changed": false, "msg": "Chrony is still NOT synchronized after auto-fix"}
    fatal: [hpc2-node06]: FAILED! => {"changed": false, "msg": "Chrony is still NOT synchronized after auto-fix"}
    
    TASK [Success message] ******************************************************************************************************************************************************
    skipping: [hpc2-login]
    skipping: [hpc2-head]
    
    PLAY RECAP ******************************************************************************************************************************************************************
    hpc2-head                  :ok=3 changed=0    unreachable=0    failed=0skipped=4 rescued=0    ignored=0
    hpc2-login                 :ok=4 changed=0    unreachable=0    failed=0skipped=4 rescued=0    ignored=0
    hpc2-node01                :ok=5changed=2 unreachable=0 failed=1 skipped=0    rescued=0    ignored=0
    hpc2-node02                :ok=5changed=2 unreachable=0 failed=1 skipped=0    rescued=0    ignored=0
    hpc2-node03                :ok=5changed=2 unreachable=0 failed=1 skipped=0    rescued=0    ignored=0
    hpc2-node04                :ok=5changed=2 unreachable=0 failed=1 skipped=0    rescued=0    ignored=0
    hpc2-node05                :ok=5changed=2 unreachable=0 failed=1 skipped=0    rescued=0    ignored=0
    hpc2-node06                : ok=5    changed=2    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

    As we can see in the previous section, at the “PLAY RECAP” section, we can confirm that changes occurred and failed too. The failure was just to keep the NTP sync okay – it’s normal because after restarting the chronyd service, it takes some seconds to sync the date and time details from the NTP server.

    If we run the Playbook after a few seconds, everything will be fine 🙂

    PLAY RECAP ******************************************************************************************************************************************************************
    hpc2-head                  :ok=3 changed=0    unreachable=0    failed=0skipped=4 rescued=0    ignored=0
    hpc2-login                 :ok=4 changed=0    unreachable=0    failed=0skipped=4 rescued=0    ignored=0
    hpc2-node01                :ok=3 changed=0    unreachable=0    failed=0skipped=4 rescued=0    ignored=0
    hpc2-node02                :ok=3 changed=0    unreachable=0    failed=0skipped=4 rescued=0    ignored=0
    hpc2-node03                :ok=3 changed=0    unreachable=0    failed=0skipped=4 rescued=0    ignored=0
    hpc2-node04                :ok=3 changed=0    unreachable=0    failed=0skipped=4 rescued=0    ignored=0
    hpc2-node05                :ok=3 changed=0    unreachable=0    failed=0skipped=4 rescued=0    ignored=0
    hpc2-node06                :ok=3 changed=0    unreachable=0    failed=0skipped=4 rescued=0    ignored=0

    So, as we can see, Ansible works to keep the “desired state” (in simple words, to keep everything working fine)!

    That’s it for now 🙂

    Share. Facebook Twitter Pinterest LinkedIn Tumblr Email
    Previous ArticleCreating Your First Ansible Playbook
    Next Article Creating an Ansible Playbook for Keeping DNS Client Settings
    Danilo

    Infrastructure Engineer with experience in Virtualization, Linux, Windows Server and learning automation using Python. DPC Virtual Tips was created to share practical tutorials, lab experiences and troubleshooting guides focused on enterprise infrastructure technologies.

    Related Posts

    Linux ss, lsof, and fuser Commands: A Practical Guide

    August 4, 2026

    Linux Commands to Investigate High Disk Partition Usage

    July 20, 2026

    How to Install, Configure, and Use tmux on Linux

    July 14, 2026

    Comments are closed.

    Search
    Categories
    • CLI (7)
    • HPC (7)
    • Operating Systems (81)
    • PowerFlex (22)
    • Virtualization (122)
    Read More
    Operating Systems

    Linux ss, lsof, and fuser Commands: A Practical Guide

    By DaniloAugust 4, 20260
    Operating Systems

    Linux Commands to Investigate High Disk Partition Usage

    By DaniloJuly 20, 20260
    HPC

    Essential Slurm Administration Commands Every HPC Administrator Should Know

    By DaniloJuly 15, 20260
    Operating Systems

    How to Install, Configure, and Use tmux on Linux

    By DaniloJuly 14, 20260
    HPC

    Getting Started with Lustre File System

    By DaniloJuly 13, 20260
    Latest Posts

    Linux ss, lsof, and fuser Commands: A Practical Guide

    August 4, 2026

    Linux Commands to Investigate High Disk Partition Usage

    July 20, 2026

    Essential Slurm Administration Commands Every HPC Administrator Should Know

    July 15, 2026
    Images from Gallery
    hpc main commands
    linux commands
    install rock linux
    lustre fs
    shell scripting
    vSAN Trace Files
    Categories
    • CLI
    • HPC
    • Operating Systems
    • PowerFlex
    • Virtualization
    • Home
    • About Us
    • Contact
    • Cookie Policy
    • Comment Policy
    • Privacy Policy
    • Terms of Use
    • Disclaimer
    Copyright © 2026, DPC Virtual Tips. All rights reserved.

    Type above and press Enter to search. Press Esc to cancel.

    We use cookies to ensure your best experience on our website. If you continue using our website, we'll assume you agree to our cookie policy