1 Before you start
- This procedure was developed and tested on a Raspberry Pi 5 with 8 GB of RAM, running Raspberry Pi OS (Trixie).
- The reference clock is a Waveshare NEO-M8T GNSS Timing HAT. It plugs onto the GPIO header and is powered by the Pi.
- You need terminal access (SSH or a keyboard and screen) and should be comfortable running commands and editing files with
nano.
How the GNSS module is configured
These are the settings used on this server, each with the u-center configuration message (UBX-CFG-...) where it is found. Configuring the module is a topic of its own and outside the scope of this guide, so consult the manufacturer's manual for how to apply them.
- Satellite systems (
UBX-CFG-GNSS): the module runs in multi-GNSS mode, using GPS, Galileo and GLONASS satellites at the same time. It can also use BeiDou, and QZSS together with GPS; the manufacturer's manual lists the combinations it supports. The rest of this guide works with any of them. - Survey-in (
UBX-CFG-TMODE2): the module was surveyed in over 10 sidereal days (861,641 seconds), after which it entered timing mode. SBAS (here WAAS, the North American system; elsewhere the regional one, such as EGNOS in Europe) was enabled during the survey for the best position accuracy, then switched off once it finished, for the best pulse precision. - Time pulse (
UBX-CFG-TP5): the box Align Pulse to TOW=0 as soon as GNSS time is locked and valid is ticked, and in the list below it 0 - UTC Time is selected, so the pulse is aligned to UTC and not to one satellite system's own time. - UTC standard (
UBX-CFG-NAV5): UTC Standard is set to 0 - Automatic. The receiver then chooses the version of UTC from the satellite systems that are enabled, in a fixed order of preference: UTC(USNO) if GPS is enabled, otherwise UTC(SU) if GLONASS is, then UTC(NTSC) for BeiDou, and finally the European version for Galileo. This is u-blox's usual order, but other receivers and newer firmware can use a different one, so check the manual for yours. - Serial port (
UBX-CFG-PRT): Baudrate is set to 115200 on UART1, the port wired to the Pi, and the configuration is saved (UBX-CFG-CFG) so that it survives a power cycle. The factory setting of 9600 is too slow once several satellite systems are enabled: the receiver's messages then take most of each second to send, and gpsd can match the pulse to the wrong second. The serial settings in step 6 and step 9 assume 115200, so adjust them if your device uses a different rate. - NMEA version (
UBX-CFG-NMEA): the module uses NMEA version 4.10.
Never let the timing HAT draw power from its USB port while the Pi is also powering it. To connect the board to a computer (for example, to run u-center), use a USB power blocker that passes data but blocks power. This server uses a Portapow USB Power Blocker.
2 Configure the firewall
Your Pi has to be reachable from the internet for time requests, for NTS key exchange, for your website and for certificate renewal.
Open these ports on your firewall or router and forward them to the Raspberry Pi:
- Port 80 (TCP): Let's Encrypt uses it to validate and renew your certificate.
- Port 443 (TCP): visitors reach your website over HTTPS.
- Port 123 (UDP): clients send their time requests to it, with or without NTS.
- Port 4460 (TCP): NTS clients use it for the key exchange.
3 Prepare the serial port
The HAT talks to the Pi over the hardware serial port. By default Linux uses that port for a login console, so it must be released first.
Start the configuration tool:
sudo raspi-config
- Choose Interface Options, then Serial Port.
- When asked whether you would like a login shell to be accessible over serial, answer No.
- When asked whether you would like the serial port hardware to be enabled, answer Yes.
- Exit raspi-config and reboot the Pi.
4 Install the required packages
gpsd reads the GNSS receiver, gpsd-clients adds the tools for watching it (cgps, gpsmon and gpspipe), pps-tools provides PPS utilities, setserial shows serial port details, chrony is the NTP and NTS server, and apache2 with Certbot obtains the certificates that NTS needs.
Refresh the package lists first, because lists that are out of date can make the installation fail:
sudo apt update
Then install the packages:
sudo apt install gpsd gpsd-clients pps-tools setserial chrony apache2 python3-certbot-apache
5 Prepare the kernel for precise timing
This sets two kernel options for precise timing. The first keeps the kernel's regular timer tick running (nohz=off). Without it, an idle processor can take a little longer to react to the pulse-per-second signal, which adds a small amount of jitter. The second, below, turns off PCIe power saving. The GPIO pin that carries the pulse is set in step 7.
Open the kernel command line file:
sudo nano /boot/firmware/cmdline.txt
Add the following to the end of the existing line, with a space before it. This file must stay a single line, so do not press Enter.
nohz=off
Turn off PCIe power saving
On the Raspberry Pi 5, the GPIO pins and Ethernet connect to the main processor through the RP1 chip, over a PCIe link. With power saving (ASPM) on, this link can enter a low-power state when idle. Turning power saving off keeps the link out of that state, which makes the system clock more accurate.
Check the current state. Each line shows whether power saving is on for one PCIe link:
sudo lspci -vv | grep -E "LnkCtl:.*ASPM"
cmdline.txt must stay a single line, and a mistake here could stop the Pi booting normally. Make a backup copy first. If the Pi will not boot, you can restore the copy by putting its SD card in another computer.
sudo cp /boot/firmware/cmdline.txt /boot/firmware/cmdline.txt.bak
Open the kernel command line file:
sudo nano /boot/firmware/cmdline.txt
Add the following to the end of the existing line, with a space before it. Do not press Enter.
pcie_aspm.policy=performance
Reboot:
sudo reboot
Confirm the change with the same command as before. Every line should say ASPM Disabled.
sudo lspci -vv | grep -E "LnkCtl:.*ASPM"
The trade-off is slightly higher power use and possibly a little more heat. To undo the change, remove pcie_aspm.policy=performance from the file and reboot.
6 Set up the serial port at every boot
A small script sets the serial port parameters (115200 baud, the speed set on the receiver, and raw mode), and a systemd service runs it on every boot so the GNSS data stream is always readable.
Create the script:
sudo nano /usr/local/bin/configure-ttyAMA0.sh
Put this in it:
#!/bin/bash
/bin/stty -F /dev/ttyAMA0 raw 115200 cs8 clocal -cstopb -echo
Make it executable:
sudo chmod +x /usr/local/bin/configure-ttyAMA0.sh
Create the service that runs it:
sudo nano /etc/systemd/system/configure-serial-port.service
Put this in it:
[Unit]
Description=Configure Serial Port ttyAMA0 Settings
# Wait for the serial device, and finish before gpsd opens it
BindsTo=dev-ttyAMA0.device
After=dev-ttyAMA0.device
Before=gpsd.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/configure-ttyAMA0.sh
[Install]
WantedBy=multi-user.target
Enable and start the service, running these one after the other:
sudo systemctl daemon-reload
sudo systemctl enable configure-serial-port.service
sudo systemctl start configure-serial-port.service
7 Set up the PPS overlay and the fan
This enables the device tree overlay for the PPS signal, which loads its kernel driver and creates the /dev/pps0 device. It also sets how the fan behaves, in the same file.
Open the boot configuration:
sudo nano /boot/firmware/config.txt
Add this at the bottom:
# PPS on GPIO pin 18
dtoverlay=pps-gpio,gpiopin=18
The Waveshare NEO-M8T HAT sends its pulse to GPIO 18. On a board that uses a different pin, put that pin's number after gpiopin=, and in the comment too.
Keep the fan from cycling
The Pi's clock crystal is sensitive to temperature. With the default settings, the official Active Cooler, or any other fan on the Pi 5's fan connector, can switch on and off every few minutes, and each switch causes a noticeable jump in the clock's frequency. Choose the option below that suits where your Pi is kept.
Add the lines for your option at the end of /boot/firmware/config.txt, under [all]. Use only one option. If you switch from one to the other, remove the old lines first.
sudo nano /boot/firmware/config.txt
Option A: unheated or variable locations, such as a garage or shed. The fan stays off in cool conditions and, once it is needed, runs at one steady speed until the Pi has cooled well down.
# Fan: stays off when cool, then runs at one steady speed until the Pi has cooled well down
dtparam=fan_temp0=60000
dtparam=fan_temp0_hyst=25000
dtparam=fan_temp0_speed=125
dtparam=fan_temp1=65000
dtparam=fan_temp1_hyst=4000
dtparam=fan_temp1_speed=175
dtparam=fan_temp2=70000
dtparam=fan_temp2_hyst=4000
dtparam=fan_temp2_speed=215
dtparam=fan_temp3=75000
dtparam=fan_temp3_hyst=4000
dtparam=fan_temp3_speed=255
| Pi temperature | Fan |
|---|---|
| Below 60 °C, and the fan is off | Stays off |
| Reaches 60 °C | Switches on at about half speed (125 of 255) |
| Between 35 °C and 65 °C, and the fan is on | Stays at that speed |
| Falls below about 35 °C | Switches off |
| 65 °C, 70 °C or 75 °C | Steps up to a faster speed as a safety measure, and back down once about 4 °C cooler |
In mild weather, the fan may still switch on and off a few times a day.
Option B: a stable, temperature-controlled room, such as a room at home. The fan stays off most of the time, switches on only if the Pi reaches 60 °C, and switches off again once it has cooled to 50 °C.
# Fan: runs at a low speed only when the Pi is warm
dtparam=fan_temp0=60000
dtparam=fan_temp0_hyst=10000
dtparam=fan_temp0_speed=75
dtparam=fan_temp1=65000
dtparam=fan_temp1_hyst=5000
dtparam=fan_temp1_speed=125
dtparam=fan_temp2=70000
dtparam=fan_temp2_hyst=5000
dtparam=fan_temp2_speed=175
dtparam=fan_temp3=75000
dtparam=fan_temp3_hyst=5000
dtparam=fan_temp3_speed=250
| Pi temperature | Fan |
|---|---|
| Below 60 °C, and the fan is off | Stays off |
| Reaches 60 °C | Switches on at a low speed (75 of 255) |
| Between 50 °C and 65 °C, and the fan is on | Stays at that speed |
| Falls below 50 °C | Switches off |
| 65 °C, 70 °C or 75 °C | Steps up to a faster speed as a safety measure, and back down once about 5 °C cooler |
Option B works best when the Pi stays well below 60 °C without the fan, so the fan rarely runs. After applying it, watch the temperature for a few hours with the command below. If the fan keeps switching on and off, the Pi runs too warm for this option. In that case, run the fan constantly at a low speed instead, by using only these lines:
# Fan: runs constantly at a low speed
dtparam=fan_temp0=10000
dtparam=fan_temp0_hyst=5000
dtparam=fan_temp0_speed=75
The fan then never switches off in a normal room, so the Pi's temperature stays steady. The trade-off is that the fan is always running and audible, and may wear out sooner.
Save the file, then reboot:
sudo reboot
Check the temperature and fan speed. This prints one reading a minute; press Ctrl+C to stop.
while true; do echo "$(date +%T) $(vcgencmd measure_temp) rpm=$(cat /sys/devices/platform/cooling_fan/hwmon/hwmon*/fan1_input 2>/dev/null)"; sleep 60; done
rpm is 0 while the fan is off. With the constant-speed setting, it should stay steady and never drop to 0. To undo either option, remove the lines and reboot.
8 Set up Network Time Security (NTS)
NTS needs a TLS certificate. Certbot obtains one from Let's Encrypt, and a renewal hook copies each renewed certificate to chrony and restarts the services, so the server keeps working without any manual steps.
Obtain the certificate (replace time.yourdomain.com with your own domain):
sudo certbot --apache -d time.yourdomain.com
Copy the certificate to chrony. Again, replace time.yourdomain.com with your domain in the commands below. First create a folder for it:
sudo mkdir -p /etc/chrony/certs
Copy the certificate. Everyone on the Pi may read this copy, because a certificate is public:
sudo install -m 644 /etc/letsencrypt/live/time.yourdomain.com/fullchain.pem /etc/chrony/certs/fullchain.pem
Copy the private key. This copy can be read only by root and chrony (-m 640 -g _chrony), because the key must stay secret:
sudo install -m 640 -g _chrony /etc/letsencrypt/live/time.yourdomain.com/privkey.pem /etc/chrony/certs/privkey.pem
Renewals keep these permissions, because the renewal hook below writes into these existing files instead of creating new ones.
Create the renewal hook script:
sudo nano /etc/letsencrypt/renewal-hooks/deploy/chrony_NTS_certs.sh
Put this in it:
#!/bin/bash
FULLCHAIN_PATH="${RENEWED_LINEAGE}/fullchain.pem"
PRIVKEY_PATH="${RENEWED_LINEAGE}/privkey.pem"
cat "${FULLCHAIN_PATH}" > /etc/chrony/certs/fullchain.pem
cat "${PRIVKEY_PATH}" > /etc/chrony/certs/privkey.pem
systemctl restart chronyd
systemctl restart gpsd
Make it executable:
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/chrony_NTS_certs.sh
9 Set up gpsd
This points gpsd at both the serial data (/dev/ttyAMA0) and the PPS signal (/dev/pps0) so it can combine them into a time source for chrony.
Open the gpsd defaults:
sudo nano /etc/default/gpsd
Change the file so it matches this. -s 115200 tells gpsd the serial speed, so it does not have to try other speeds when it starts. Use your receiver's speed if it is different:
DEVICES="/dev/ttyAMA0 /dev/pps0"
GPSD_OPTIONS="-n -s 115200"
USBAUTO="false"
chrony must start before gpsd, because chrony creates the socket that gpsd sends its time to. Create a folder for a small addition to gpsd's service:
sudo mkdir -p /etc/systemd/system/gpsd.service.d
Create the addition:
sudo nano /etc/systemd/system/gpsd.service.d/start-after-chrony.conf
Put this in it:
[Unit]
After=chrony.service
10 Set up chrony
This is the heart of the server: it turns on NTS, adds rate limiting to protect a public server, and defines the GNSS receiver as the primary reference clock.
Open the daemon options:
sudo nano /etc/default/chrony
Set them to the following. These options turn on chrony's system-call filter and make it reload its saved measurement history at startup.
DAEMON_OPTS="-F 1 -r"
Open the chrony configuration:
sudo nano /etc/chrony/chrony.conf
Make these changes. The first two lines comment out the default pool and the DHCP-provided sources, and the rest is added or set as shown:
#pool 2.debian.pool.ntp.org iburst
#sourcedir /run/chrony-dhcp
dumpdir /run/chrony
makestep 1 1
# Set maximum client log size
clientloglimit 2147483648
# Set rate limits
ratelimit
ntsratelimit
# Allow all
allow
# Save NTS keys and cookies
ntsdumpdir /var/lib/chrony
# Certificate in the PEM format
ntsservercert /etc/chrony/certs/fullchain.pem
# Private key in the PEM format
ntsserverkey /etc/chrony/certs/privkey.pem
# Number of helper processes for NTS
ntsprocesses 4
# Maximum number of concurrent NTS-KE connections per process
maxntsconnections 512
# Main reference clock using socket interface
refclock SOCK /run/chrony.pps0.sock refid GNSS precision 1e-9
ntsprocesses is set to 4 to match the four processor cores of a Raspberry Pi 5.
The name of the socket on the refclock line follows the PPS device that gpsd uses (step 9): /dev/pps0 gives /run/chrony.pps0.sock. If your PPS device has a different number, change the name to match.
Choose the client log size for your Pi's memory
clientloglimit sets how much memory chrony may use to keep track of the clients it serves, which it needs for its rate limits. The value above, 2147483648 (2 GB), is chrony's maximum, and it suits a Raspberry Pi with 8 GB of RAM like this server. Chrony only fills it under very heavy traffic, but on a Pi with less memory that could leave too little for everything else, so use the value for your Pi. Each value is about a quarter of the memory; even the smallest lets chrony keep track of over two million clients at once.
| Pi memory (RAM) | clientloglimit | Clients tracked |
|---|---|---|
| 1 GB | 268435456 | 2,097,152 |
| 2 GB | 536870912 | 4,194,304 |
| 4 GB | 1073741824 | 8,388,608 |
| 8 GB (this server) | 2147483648 | 16,777,216 |
| 16 GB | 2147483648 | 16,777,216 |
The Raspberry Pi 5 comes with 1, 2, 4, 8 or 16 GB of RAM. On a 16 GB Pi the value stays at 2147483648, because chrony uses no more than that. Chrony always keeps track of a power-of-two number of clients, which is why these values are powers of two as well.
Add backup servers
With only the GNSS receiver, your server has nothing to fall back on if the receiver or its antenna stops working. Add at least four network time servers of your choice to chrony.conf, one line each, preferably ones that support NTS. RFC 8633 recommends at least four independent sources. Chrony keeps using the receiver while it works, because it is far more accurate, and switches to the backups if it fails. Each line looks like this, with the name of a server you have chosen in place of ntp.example.org:
server ntp.example.org iburst nts
You do not need to add the trust option to the receiver yourself, as long as at least one backup is authenticated (with NTS or a key) and at least one is not. chrony's authselectmode setting defaults to mix, and when a configuration has both kinds, that mode gives the authenticated servers and the reference clock the require and trust options. Servers without authentication can then be used only if they agree with those.
Enable the services so they start on boot, running these one after the other:
sudo systemctl enable gpsd
sudo systemctl enable chrony
Then reboot:
sudo reboot
Optional: turn on hardware timestamping
Hardware timestamping has the Pi's network chip record exactly when each time packet arrives and leaves. That makes the timestamps on packets to and from clients more exact, but it does not remove uneven delays on the internet, so most clients gain only a little. Leaving it off is a perfectly reasonable choice, especially if it does not work cleanly on your Pi.
First check that your Pi supports it. Under Capabilities, look for hardware-transmit and hardware-receive. If they are not listed, skip the rest of this section.
ethtool -T eth0
Confirm that eth0 is up. The output should include state UP.
ip link show eth0
If chrony cannot turn on hardware timestamping on an interface named in its configuration, such as eth0, it treats that as a fatal error and stops. This can happen at boot, when chrony may start before eth0 is up. The addition below is therefore required: it makes chrony wait for the network and retry if it still fails. Without the retry, your public server could stay down after a power cut until you restart it yourself.
Open an addition to chrony's service:
sudo systemctl edit chrony
Add this in the section the editor marks for your changes, then save and close it:
# Start chrony once the network is up, and retry if it fails
[Unit]
After=network-online.target
Wants=network-online.target
[Service]
Restart=on-failure
RestartSec=10
Open the chrony configuration:
sudo nano /etc/chrony/chrony.conf
Add these lines:
# Enable hardware timestamping
hwtimestamp eth0
Restart chrony, which stops the time service briefly:
sudo systemctl restart chrony
Then restart gpsd, which must connect to chrony again:
sudo systemctl restart gpsd
Check that chrony stayed up. This should print active.
systemctl is-active chrony
If it prints failed, remove the hwtimestamp line, and restart chrony and gpsd as above to get the server running again. Then read chrony's latest log lines to see why:
journalctl -u chrony -n 20 --no-pager
If it prints active, reboot to test the start-up as well:
sudo reboot
After the reboot, run systemctl is-active chrony again. Then, once clients have been using the server for a while, run this twice, a minute or so apart. NTP hardware RX timestamps should be rising.
sudo chronyc serverstats
Finally, check that the receiver is still feeding chrony. After a few minutes, the GNSS line should start with #*. If it does not, restart gpsd as above, because chrony may have started again after gpsd did.
chronyc sources
11 Turn on automatic security updates
A public server should receive security fixes without waiting for you. This sets the Pi to install them by itself every day.
Copy the whole block, from sudo bash -c ' down to the final ', paste it into the terminal in one go, and press Enter:
sudo bash -c '
set -e
apt-get update
apt-get install -y unattended-upgrades
cat > /etc/apt/apt.conf.d/52unattended-upgrades-local <<"EOF"
// Install Debian security updates and Raspberry Pi updates (kernel, firmware, Chromium)
#clear Unattended-Upgrade::Origins-Pattern;
Unattended-Upgrade::Origins-Pattern {
"origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
"origin=Raspberry Pi Foundation,codename=${distro_codename},label=Raspberry Pi Foundation";
};
EOF
cat > /etc/apt/apt.conf.d/20auto-upgrades <<"EOF"
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
EOF
systemctl enable --now apt-daily.timer apt-daily-upgrade.timer
echo
echo "=== Setup complete. Sources that will update automatically: ==="
unattended-upgrade --dry-run --debug 2>&1 | grep -i "allowed origins" || true
echo
echo "=== Next scheduled runs: ==="
systemctl list-timers --no-pager "apt-daily*"
'
It takes a minute or two. At the end you should see:
- Setup complete, followed by a line naming the two update sources (
Debian-SecurityandRaspberry Pi Foundation) with your release name filled in. - Next scheduled runs, listing two timers.
apt-daily-upgradeis the one that installs updates, daily at around 6:00 a.m. If the Pi is off at that time, the update runs shortly after it next starts up.
What this installs automatically
- Debian security updates: security fixes for the operating system and its software.
- Raspberry Pi updates: the Pi's kernel, firmware and Chromium browser. Their security fixes only come through this source. It has no separate security-only channel, so other Raspberry Pi package updates come along too.
It will not upgrade the Pi to a new Raspberry Pi OS release, and it does not touch software installed outside the normal package system.
Optional: let the Pi reboot itself when needed
Kernel updates only take effect after a restart. To have the Pi reboot at 3:00 a.m., only on nights when an update needs it, paste this whole block. The time service is briefly interrupted during such a reboot.
sudo tee -a /etc/apt/apt.conf.d/52unattended-upgrades-local > /dev/null <<'EOF'
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";
EOF
Undo: turn automatic updates off
To reverse everything above, including the optional reboot setting, paste this whole block. You should see Done at the end. From then on, the Pi only updates when you run the updates yourself. To turn automatic updates back on, paste the first block of this step again.
sudo bash -c '
set -e
# Cancel a reboot scheduled by the automatic updates, if there is one
shutdown -c 2>/dev/null || true
# Delete the settings files created during setup
rm -f /etc/apt/apt.conf.d/52unattended-upgrades-local /etc/apt/apt.conf.d/20auto-upgrades
# Uninstall the updater and its default settings (waits if an update is running right now)
apt-get purge -y -o DPkg::Lock::Timeout=600 unattended-upgrades
echo
if dpkg -s unattended-upgrades >/dev/null 2>&1; then
echo "=== Something went wrong: unattended-upgrades is still installed. ==="
else
echo "=== Done. Automatic updates are off and the updater has been removed. ==="
fi
'
Handy commands
See what has been updated, and when:
sudo grep -h "Packages that will be upgraded" /var/log/unattended-upgrades/unattended-upgrades.log
Install pending updates right now:
sudo unattended-upgrade -v
Check whether the Pi is waiting for a reboot. It says "No such file" if no reboot is needed:
ls /var/run/reboot-required
If apt ever says it could not get a lock, the automatic update is running in the background. Wait a few minutes and try again.
12 Maintenance and troubleshooting
Use these commands to check that the GNSS receiver and PPS signal are healthy, to test NTS from the outside, and to manage the services and certificates.
Test NTS
Run this from a machine on a different internet connection, with your own domain in place of time.yourdomain.com. It performs the NTS key exchange (TCP 4460) and then an authenticated NTP exchange (UDP 123), so a pass means both ports work. Testing from inside your own network often gives misleading results.
chronyd -Q -t 5 "server time.yourdomain.com iburst nts maxsamples 1"
Serial port and PPS diagnostics
Show the serial port settings. This confirms the script from step 6 ran, and shows the baud rate and other parameters:
stty -F /dev/ttyAMA0 -a
Show hardware-level details of the serial port:
sudo setserial -v /dev/ttyAMA0
Print the raw data stream from the GNSS module. You should see NMEA sentences such as $GPGGA or $GNGGA. If the port is reported as busy, gpsd has it open; stop gpsd (see below) and try again. If you see unreadable characters instead, gpsd may have switched the receiver to its binary u-blox format. That is normal, and it still shows that the receiver is sending data.
cat /dev/ttyAMA0
Check that the kernel is receiving the pulse-per-second signal. You should see one timestamp per second. This is the definitive test of your high-accuracy time source.
sudo ppstest /dev/pps0
Open a live view of satellite fix status, position and time as seen by gpsd:
cgps -s
Show gpsd's raw NMEA sentences and status, which is useful for deeper debugging of the GNSS connection:
gpsmon
Stop and start gpsd
gpsd takes exclusive control of the serial device. Stop it, together with its socket, if you want to inspect the port directly:
sudo systemctl stop gpsd
sudo systemctl stop gpsd.socket
Start it again afterwards, socket first, so chrony gets its GNSS data back:
sudo systemctl start gpsd.socket
sudo systemctl start gpsd
Service status and logs
Check that chrony is running and see its latest log lines:
sudo systemctl status chronyd
Check the Apache web server, which Certbot needs for certificate renewal:
sudo systemctl status apache2
Read chrony's full journal, which is the first place to look if it fails to start or synchronise:
journalctl -xeu chrony.service
Let's Encrypt
Test your renewal hook script with a simulated renewal. The hook runs with your current certificate, and chrony and gpsd restart:
sudo certbot renew --dry-run --run-deploy-hooks
Simulate a renewal without running the hook, saving any certificates or touching the live server. This is the safest way to test your setup:
sudo certbot renew --dry-run
Force a real renewal immediately. Use this only when you need a new certificate, because each one counts against Let's Encrypt's rate limits:
sudo certbot renew --force-renewal