> For the complete documentation index, see [llms.txt](https://docs.harmony.one/home/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.harmony.one/home/network/validators/node-setup/0.-recommended-chrony-setup-for-validator-nodes.md).

# 0. Recommended Chrony Setup for Validator Nodes

Accurate system time is important for blockchain validator nodes. Clock drift can cause timestamp-related issues, missed blocks, rejected blocks, or unstable node behavior.

This guide shows a simple Chrony setup using:

* Cloudflare as a fixed NTP source
* `pool.ntp.org` as a rotating public NTP pool
* stricter startup clock correction
* RTC synchronization
* conservative clock frequency update handling

{% hint style="info" %}
&#x20;Please don't be scared by lots of NTP related terms, all of them are described in the [#id-8.-advanced-config-explanation](#id-8.-advanced-config-explanation "mention")
{% endhint %}

{% hint style="info" %}
If you are Ansible user go straight to the [#id-11.-advanced-ansible-role](#id-11.-advanced-ansible-role "mention"), it has all steps below automated
{% endhint %}

### 0. Disable other time synchronization services

Before enabling Chrony, make sure other NTP/time-sync daemons are stopped and disabled.

Running multiple time synchronization services at the same time can cause confusing behavior because more than one daemon may try to discipline the system clock.

Common conflicting services:

```
ntp
ntpd
ntpsec
openntpd
systemd-timesyncd
```

Check what is active:

```bash
systemctl is-active ntp ntpd ntpsec openntpd systemd-timesyncd 2>/dev/null
```

Stop and disable common alternatives, if someone from the list is active:

```bash
sudo systemctl disable --now ntp 2>/dev/null || true
sudo systemctl disable --now ntpd 2>/dev/null || true
sudo systemctl disable --now ntpsec 2>/dev/null || true
sudo systemctl disable --now openntpd 2>/dev/null || true
sudo systemctl disable --now systemd-timesyncd 2>/dev/null || true
```

### 1. Install Chrony

Install Chrony using your distribution package manager.

#### Ubuntu/Debian

```bash
sudo apt update
sudo apt install -y chrony
sudo systemctl enable chrony
sudo systemctl start chrony
```

Default config path is usually:

```
/etc/chrony/chrony.conf
```

Default service name is usually:

```
chrony
```

#### RHEL/CentOS/Rocky/Alma/Amazon Linux/Fedora

```bash
sudo dnf install -y chrony
sudo systemctl enable chronyd
sudo systemctl start chronyd
```

On older systems:

```bash
sudo yum install -y chrony
sudo systemctl enable chronyd
sudo systemctl start chronyd
```

Default config path is usually:

```
/etc/chrony.conf
```

Default service name is usually:

```
chronyd
```

### 2. Find your active Chrony config path

Before editing, check which config file your running daemon actually uses.

```bash
ps -ef | grep 'chronyd'
```

If you see:

```
/usr/sbin/chronyd -f /etc/chrony/chrony.conf
```

edit:

```bash
sudo vim /etc/chrony/chrony.conf
```

If you see:

```
/usr/sbin/chronyd -f /etc/chrony.conf
```

edit:

```bash
sudo vim /etc/chrony.conf
```

If there is no `-f` flag, use your distro default:

```
Ubuntu/Debian: /etc/chrony/chrony.conf
RHEL-like:     /etc/chrony.conf
```

You can also inspect the systemd unit:

```bash
systemctl cat chrony
systemctl cat chronyd
```

For the rest of this guide, replace `/etc/chrony/chrony.conf` with `/etc/chrony.conf` if your distro uses the RHEL-like layout.

### 3. Configure Chrony

Edit the active Chrony config file.

Ubuntu/Debian example:

```bash
sudo vim /etc/chrony/chrony.conf
```

RHEL-like example:

```bash
sudo vim /etc/chrony.conf
```

Use this config:

```conf
# List of NTP servers to use
server time.cloudflare.com iburst minpoll 6 maxpoll 8

# List of NTP pools to use
pool pool.ntp.org iburst minpoll 6 maxpoll 8

# Validator-focused Chrony tuning.
# Keep frequency correction conservative: reject updates when the estimated
# frequency skew is above 100 ppm. This helps avoid learning bad drift
# corrections from noisy or unstable NTP measurements.
maxupdateskew 100

# Drift file
driftfile /var/lib/chrony/drift

# Step the clock if the adjustment is larger than 1 second,
# but only during the first 3 updates after chronyd starts.
makestep 1 3

# Keep UTC date and time for RTC
rtconutc

# Enable RTC synchronization
rtcsync

# Leap second data
leapsectz right/UTC

# Listen for commands only on localhost
bindcmdaddress 127.0.0.1
bindcmdaddress ::1

# Log path
logdir /var/log/chrony
```

### 4. Restart Chrony

Use the service name for your distribution.

Ubuntu/Debian:

```bash
sudo systemctl restart chrony
sudo systemctl status chrony --no-pager
```

RHEL-like systems:

```bash
sudo systemctl restart chronyd
sudo systemctl status chronyd --no-pager
```

### 5. Validate the configuration

Use the same config path your daemon uses.

Ubuntu/Debian example:

```bash
sudo chronyd -Q -f /etc/chrony/chrony.conf
```

RHEL-like example:

```bash
sudo chronyd -Q -f /etc/chrony.conf
```

A healthy result looks similar to:

```
Disabled control of system clock
System clock wrong by -0.000983 seconds (ignored)
chronyd exiting
```

This means Chrony parsed the config and checked time successfully.

If you see errors like this, fix the config before restarting the service:

```
Invalid directive
Could not parse
Fatal error
```

### 6. Check synchronization status

```bash
chronyc tracking
```

Healthy output should include:

```
Leap status     : Normal
```

Example healthy output:

```
Reference ID    : A29FC801 (time.cloudflare.com)
Stratum         : 4
System time     : 0.000509224 seconds slow of NTP time
Last offset     : +0.000423205 seconds
RMS offset      : 0.000776182 seconds
Skew            : 6.698 ppm
Leap status     : Normal
```

Also check:

```bash
timedatectl
```

Healthy output should include:

```
System clock synchronized: yes
NTP service: active
RTC in local TZ: no
```

`RTC in local TZ: no` means the hardware clock is using UTC, which matches the `rtconutc` setting.

### 7. Check NTP sources

```bash
chronyc sources -v
```

Example healthy output:

```
MS Name/IP address         Stratum Poll Reach LastRx Last sample
===============================================================================
^* time.cloudflare.com           3   6   377    42   +725us[ +604us] +/-   12ms
^+ pool-source.example           2   6   377    43   +759us[ +638us] +/-   25ms
^- another-pool-source           2   6   377    43  +1468us[+1348us] +/-   68ms
```

Source state meaning:

```
^* = selected best source
^+ = good source combined into final estimate
^- = valid source, but not currently combined
?  = unusable source
x  = source may be wrong
~  = source is too variable
```

It is normal for only one source to be selected. Pool sources can still be available as fallback even if they are not currently selected.

### 8. \[Advanced] - config explanation

| Directive                                               | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                   | Why it is used                                                                                                                                                                                                                              |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `server time.cloudflare.com iburst minpoll 6 maxpoll 8` | <p>Uses Cloudflare as a fixed NTP provider. <code>iburst</code> speeds up initial sync. <code>minpoll 6</code> means minimum poll interval is <code>2^6 = 64</code> seconds. <code>maxpoll 8</code> means maximum poll interval is <code>2^8 = 256</code> seconds.<br>Note: cloudflare allow to everyone to use it -<a href="https://blog.cloudflare.com/secure-time/"><https://blog.cloudflare.com/secure-time/></a></p> | Provides a predictable primary time source without being too aggressive with polling.                                                                                                                                                       |
| `pool pool.ntp.org iburst minpoll 6 maxpoll 8`          | Uses the public NTP pool. Chrony can resolve multiple servers based of your location from this pool and replace bad or unreachable ones.                                                                                                                                                                                                                                                                                  | Provides fallback and source diversity.                                                                                                                                                                                                     |
| `maxupdateskew 100`                                     | Makes Chrony more conservative about accepting unstable clock frequency estimates. The unit is `ppm`, or parts per million. `1 ppm ≈ 0.0864 seconds/day`, `100 ppm ≈ 8.64 seconds/day`, `1000 ppm ≈ 86.4 seconds/day`.                                                                                                                                                                                                    | Helps avoid learning bad drift corrections from noisy or unstable NTP measurements. This does not mean the clock is currently 8.64 seconds wrong; it means Chrony rejects frequency corrections if their estimated uncertainty is too high. |
| `makestep 1 3`                                          | Allows Chrony to step the clock if the offset is larger than 1 second, but only during the first 3 updates after startup.                                                                                                                                                                                                                                                                                                 | Corrects large startup drift quickly while avoiding unexpected large time jumps during normal long-running operation.                                                                                                                       |
| `rtconutc`                                              | Documents that the hardware RTC is expected to use UTC. RTC means real-time clock: the machine’s hardware clock, usually used mainly at boot.                                                                                                                                                                                                                                                                             | Keeps RTC handling predictable. A correct `timedatectl` output should show `RTC in local TZ: no`.                                                                                                                                           |
| `rtcsync`                                               | Enables RTC synchronization. Chrony normally synchronizes the Linux system clock using NTP. With `rtcsync`, Chrony tells Linux to periodically copy the synchronized system clock into the RTC.                                                                                                                                                                                                                           | Keeps the hardware clock synced from the corrected system clock. This helps the machine start closer to correct time after reboot and helps tools like `timedatectl` report synchronization state correctly.                                |
| `leapsectz right/UTC`                                   | Uses the `right/UTC` timezone data for leap second information.                                                                                                                                                                                                                                                                                                                                                           | Provides leap second data if available on the system. Validate with `sudo chronyd -Q -f /etc/chrony/chrony.conf`. If validation fails because of `right/UTC`, remove the line or install the required timezone data.                        |

### 9. \[Advanced] Prometheus node exporter checks

If you monitor nodes with Node-exporter -> Prometheus stack, useful time metrics include:

```promql
node_timex_sync_status
```

This indicates whether the kernel considers the system clock synchronized:

```
 1 == clock synchronized
 0 == clock isn't synchronized
```

Recommended alert for alert manager:

```yaml
- alert: NodeClockUnsynchronised
  expr: node_timex_sync_status == 0
  for: 5m
  labels:
    severity: critical
  annotations:
    summary: "Node clock is not synchronized"
```

### 10. \[Advanced] Grafana dashboard with NTP related graphs

Want to see NTP metrics as a dashboard on Grafana like on the screenshot below?&#x20;

<figure><img src="/files/xsbsxDeeWznFkJqRTetL" alt=""><figcaption></figcaption></figure>

Please use Node Exporter full from one of the links:

{% embed url="<https://github.com/rfmoz/grafana-dashboards>" %}

{% embed url="<https://grafana.com/grafana/dashboards/1860-node-exporter-full/>" %}

### 11. \[Advanced] Ansible role

If you are Ansible fan, you can use ansible-role-chrony:

{% hint style="warning" %}
Right this role doesn't support the `maxupdateskew` need to our setup, but we can do it via Ansible way
{% endhint %}

{% embed url="<https://github.com/Frzk/ansible-role-chrony>" %}

<pre class="language-yaml" data-title="install-update-chrony.yaml" data-line-numbers data-expandable="true"><code class="lang-yaml">---
- name: Install and configure chrony Daemon
  hosts: "{{ inventory }}"

  vars:
    # Note: use the following for the RHEL/CentOS/Rocky/Alma/Amazon Linux/Fedora
    # chrony_service_name: chronyd
<strong>    # chrony_service_executable: /usr/bin/chronyd
</strong><strong>    # Debian/Ubuntu
</strong>    chrony_service_name: chrony
    chrony_config_file: /etc/chrony/chrony.conf
    chrony_config_driftfile: /var/lib/chrony/drift
    chrony_ntp_servers:
      - time.cloudflare.com iburst minpoll 6 maxpoll 8
    chrony_ntp_pools:
      - pool.ntp.org iburst minpoll 6 maxpoll 8
    chrony_ntp_peers: []
    chrony_makestep_threshold: 1
    chrony_makestep_limit: 3
    chrony_maxupdateskew: 100

  pre_tasks:
    - name: Import chrony git role
      ansible.builtin.git:
        repo: "https://github.com/Frzk/ansible-role-chrony"
        dest: roles/Frzk.chrony
        single_branch: true
        version: main
        depth: 1
      connection: local
      delegate_to: localhost
      ignore_errors: true

  tasks:
    - name: Include chrony role
      ansible.builtin.include_role:
        name: Frzk.chrony
        apply:
          become: true

  post_tasks:
    - name: Add validator chrony tuning
      become: true
      ansible.builtin.blockinfile:
        path: "{{ chrony_config_file }}"
        marker: "# {mark} ANSIBLE MANAGED BLOCK - validator chrony tuning"
        insertbefore: "^# Drift file"
        block: |
          maxupdateskew {{ chrony_maxupdateskew }}
        create: false
      notify: Restart chrony

    - name: Remove git role from localhost after apply
      ansible.builtin.file:
        path: roles/Frzk.chrony
        state: absent
      delegate_to: localhost
      connection: local

  handlers:
    - name: Restart chrony
      become: true
      ansible.builtin.service:
        name: "{{ chrony_service_name }}"
        state: restarted

</code></pre>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.harmony.one/home/network/validators/node-setup/0.-recommended-chrony-setup-for-validator-nodes.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
