Raspberry Pi NTP Server - Part 2

Raspberry Pi NTP Server - Part 2

Setting up a Raspberry Pi with Chrony

First thing's first. Let's get a Raspberry Pi prepared and then install Chrony to act as an NTP server. If you just want a simple NTP server for internal use (to reduce your impact on public NTP servers), this page is the only one you really need.


  • Raspberry Pi 3B or 4B
  • SD Card of 4 GB or more

Setting up the Raspberry Pi

The first step is to use the Raspberry Pi Imager to write the operating system to your SD Card. I recommend the 64-bit edition of Raspberry Pi OS Lite, shown below, to minimise the footprint of the system and get the most out of the processor.

Don't forget to enable SSH and define a username and password in the imager settings!

Once the SD card is imaged and verified, plug it into the Raspberry Pi, connect the ethernet cable (you really don't want to use WiFi for this!) and power it on.

After a couple of minutes, you should be able to connect to the Pi using your SSH client of choice. (I like PuTTY on Windows and Remmina on Linux)

Installing Chrony

Now that the Pi is up and running, the first step is to update the installed packages

sudo apt update 
sudo apt upgrade

Now we can install Chrony itself. This will automatically replace systemd-timesyncd, which is the Raspberry Pi's default (and much more basic) tool for maintaining time sync.

sudo apt install chrony

Configuring Chrony

Chrony is now set up and running with its default settings. We need to open the config file and ensure that the server accepts incoming connections. If you're going to feed time to the NTP pool you also need to change the 'server' and 'pool' lines so that you're not trying to get time from the NTP pool.

sudo nano /etc/chrony/chrony.conf

Making the server accessible

To make the server publicly accessible, you just need to add the following line to the bottom of chrony.conf

# Make publicly accessible
allow all

We need to set this so that Chrony can be queried. If you want to keep your server local (ie not on the NTP pool), you could try setting just your internal network's IP range instead of 'all'. Full documentation for the allow statement can be found here.

Defining time sources

If you just want to serve time to your local network and reduce the load on public time servers, you can skip this step. If you want to supply time to the NTP Pool, then chrony.conf needs a few more edits.

Select some good NTP servers close to where you live. Since I'm in the Netherlands, I've selected five reliable Stratum 1 servers in the Netherlands and Belgium.

You can find an excellent list of public Stratum 2 time servers here, as well as public Stratum 1 servers here.

Replace the lines that start with 'server' or 'pool' with your own server list. Adding 'iburst' to the end of the line allows Chrony to do a burst of queries when first starting up, allowing it to achieve time sync faster. Documentation on 'iburst' and the other server options can be found here.

It's highly recommended to leave the polling rates untouched. Chrony will automatically work out what rate it needs for good sync - manually setting too high a rate will place unnecessary load on public servers.

# Stratum 1 servers
server time.esa.int iburst
server ntp.vsl.nl iburst
server ntp1.oma.be iburst
server time.kpn.net iburst
server ntp.time.nl iburst

Checking that it works

You can now restart the Raspberry Pi and after a couple of minutes, SSH back in and check that it's working OK.

chronyc sources

You should see something like the following:

MS Name/IP address         Stratum Poll Reach LastRx Last sample               
^- time1.esa.int                 1   6    37     8   -498us[ -498us] +/- 3545us
^- static.ip-031-223-173-22>     1   6    37     7   +967us[ +967us] +/- 3655us
^- ntp-main-2.oma.be             1   6    37     7   -564us[ -564us] +/- 6362us
^* 213-75-85-246.dc.kpn.net      1   6    37     8    +15us[ -355us] +/- 2123us
^- ntp2.time.nl                  1   6    37     8   +561us[ +561us] +/- 3563us

We can see that the five servers are reachable, and that Chrony has selected one (in this case KPN's server) as a preferred source. This is indicated by an asterisk at the left. More info on the 'chronyc sources' command can be found here.

We can now check the tracking information. Explanation of the 'chronyc tracking' command can be found here.

chronyc tracking

The result should look something like this:

Reference ID    : D54B55F6 (213-75-85-246.dc.kpn.net)
Stratum         : 2
Ref time (UTC)  : Thu Jan 04 12:42:12 2024
System time     : 0.000055144 seconds fast of NTP time
Last offset     : +0.000036835 seconds
RMS offset      : 0.000275004 seconds
Frequency       : 6.864 ppm fast
Residual freq   : +0.014 ppm
Skew            : 0.632 ppm
Root delay      : 0.003368698 seconds
Root dispersion : 0.000462307 seconds
Update interval : 65.0 seconds
Leap status     : Normal

We can see here that we're running at Stratum 2 (because the sources are Stratum 1), and that RMS offset is pretty low. This is a long term average of the offset between the local clock and the sources (after Chrony's corrections).

Lastly, let's run timedatectl


The result should look something like this:

               Local time: Thu 2024-01-04 13:45:17 CET
           Universal time: Thu 2024-01-04 12:45:17 UTC
                 RTC time: n/a
                Time zone: Europe/Amsterdam (CET, +0100)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

All looks good! This tells us that the Raspberry Pi's clock is properly synchronised to NTP.

With any luck, your server is now working and accessible in your local network.

Page Index