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.
Requirements
- 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.
This will work best in Western Europe - particularly in the Benelux region.
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
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.
Making use of your server
There are a few ways to make your new server useful on your network. One approach is to reconfigure all the devices to use your server's IP as their time source. This is a pretty manual approach though, and not all devices are configurable.
Every router is different, but you can generally set the NTP server address somewhere. If network clients contact the router and ask for the time, the router will ask your server. Sometimes, you can even set a server address to be passed on to the clients, cutting out the middleman.
In my network, I use DNS rules to redirect all queries to common timeservers to my server. This means that if Windows pings time.microsoft.com, or Ubuntu pings pool.ntp.org, it all goes to my local server. The benefit is that you don't need to reconfigure all your devices individually. The drawback is that if your server fails, all your NTP requests are going to get lost until you disable the redirect rule!
If you'd like to make your server accessible to the public and help strengthen the resilience of the internet's timekeeping, you can even jump straight to Part 7 of the guide and join the NTP Pool. It's useful to the project even now!
But if you want to chase performance, read on...