This howto explains the process for setting up an IPV6 (or optionally 4) network time server using chrony, uptronics RPI GPS/RTC card, and a RPI4 with a POE+Hat (we assume that you have a POE compatiable switch!). I am including 'strike outs' for my failed attempt at getting this working with chronyd and manjaro rpi image. This new one uses straight rasbian. I am including my actual image (with user changed to user and password changed to changeme). I might host it on github for fun.
The hardware
You could probably use a different unit, but for simplicity sake I would recommend a rpi4 with 4gb (could be less), the uptutronics card (which gets rid of the need for a separate rtc clock). Also the uptronics card has a built in supercap, so you NEVER NEED a battery!
Other things to make this nice
- Get yourself a 4&40 Pin extra tall header (push fit version) for extending the gpio header through the
poe+hat.non-compatible 4 pin header which keeps you from seating the card with a standard length rpi header. - An external active gps antenna
- an SD card (I like these). DO NOT buy ANY SD CARDS on ScamaZon.
- standoffs for separating your cards
- a decent power supply I like Anker, I used the same one I used for my android and it worked fine
- a nice sd card burner or this
- actually this card is NOT compatible with the poe hat. I found this out the hard way. The 4 pin header that is used for the POE interferes physically with the poe hat.
- The clock card should have 'caps up' with usb-c header facing towards the 'ethernet' jack side of the pi.

That picture was the woe of an entire weekend. NO ONE tells you this is the proper way to install the uptronics card. Also the extended header is ESSENTIAL since the 4 pin header on the pi will cause the card to 'hit and never properly insert'. This link has VERY nice INfo I had wish I had when I started. It tells you how to enable the rtc, among other things.
Nor will anyone tell you where pin 1 is actually located. I put this picture here since its WAY easier to see this and NO ONE seemed to be kind enough to take a simple photograph. You are welcome!
I tried every which way until I finally figured this out. Fortunately the card didn't short out! It did get REALLY hot though. The thing runs at 115200, and to look at how it works, I ran minicom to see if the port was happy. You have to install minicom for this to work: **apt-get install minicom** I had tried to use chrony, but the directions simply didn't work. I did find Mr Eric Raymond's nice (if slightly outdated) faq here. Its a very nice faq, and actually works unlike the old one. And for whatever reason, this version of gpsd actually seems to understand how to talk to the serial port without using the usb-c once I had to do for the 'old' one using chrony.
One thing you may need to do from the old guide is set the card (uputronics) via the usb-c port with gpsd to only serve time. The command is (you may need to change the device to /dev/ttyACM0 if you have plugged in the usb to usb-c. Reboot after doing this (better yet power cycle)
gpsctl -p /dev/ttyACM0 -a --reset
gpsctl -p /dev/ttyACM0 -a -B 115200 --configure_for_timing --save_config -v
To see how happy the serial port is you can use:
**minicom -b 115200 -o -D /dev/ttyAMA0**
Alas, this was NOT the end of the story. I simply gave up on trying to use the @#$#$%@#$#6 serial port. Since the PPS is going on our gpio, the 'delay' involved with the usb is trivial. So I simply got a usba-c converter and plugged into the back of the pi. The easiest thing to do is simply create a symlink to serial0 ~~ln -s /dev/ttyACM0 /dev/serial0~~ its a good idea to make this permananent as part of a rc.local script or something.
The software
This is mostly the software you will need in order to get things going. I do include an automagic ipv6 ip address register hickey for cloudflare), since its nice to have the ipv6 name automagically determined and taken care of. We will be using rasbian, ntpsec, fish shell (since bash sucks), golang (alas you need the 1.21 verison as of this writing), and ddns for cloudflare.
Install Manjaro to the SD card
The FAQ that I used is this, but it doesn't go the 'full monty' for full NTS. I hybridize that later. Dont' do the time things until you get the gps hardware installed. It caused lots of problems for me.
Initial things to make things happy
I recommend running the following commands:
pacman-mirrors -i
<choose your fastest mirrors>
pacman -Syu
reboot
Once this is done, you can proceed with the next section which sets up your hostname with automagic cloudflare goodness.
Make my hostname automagically reister with cloudflare
I use cloudflare for my dns, since I am lazy and tired of managing a dnssec, api driven dns service thats free. This utility will let you get things started. Some general hints since I don't want to use the 'docker' method. You will need to find out the current version from this website. I do use fish shell syntax since I HATE BASH.
#!/usr/bin/env bash
apt-get install wget
apt-get install git fish ncdu lnav
wget https://go.dev/dl/go<version>.linux-arm64.tar.gz
tar -C /usr/local -xzf go<version>.linux-arm64.tar.gz
fish_add_path /usr/local/go/bin
cd /usr/src
git clone https://github.com/favonia/cloudflare-ddns.git
cd cloudflare-ddns/cmd/ddns
set -gx LDFLAGS -s -w
go build
strip --strip-all ddns
cp ddns /usr/bin
adduser ddns
You will have to vipw to ensure that the ddns user uses /usr/bin/nologin since this is the user we use for the ddns service in systemd.
Making the thing start automagically
Its important to follow the directions in the contrib directory which will show you how to actually set systemd up. I am not going to repeat things. Also you will need to create a cloudflare token, which I recommend that you setup to ONLY work from your network, as well as ONLY working on the zone you want to update. DO NOT use a global token, since they can blow away EVERYTHING with that. Basically you stuff the token into the env file, put it in the right directory, create a new file in /etc/systemd/cloudflare-ddns.service file, and issue the systemctl daemon-reload and systemctl enable --now cloudflare-ddns.service. which will start the daemon. You should see something like this if you do a 'journalctl --unit cloudflare-ddns.service'
Aug 19 22:41:43 chronoskimmer systemd[1]: Started Cloudflare DDNS Updater.
Aug 19 22:41:43 chronoskimmer ddns[3841]: 🌟 Cloudflare DDNS
Aug 19 22:41:43 chronoskimmer ddns[3841]: 🥷 Dropping privileges . . .
Aug 19 22:41:43 chronoskimmer ddns[3841]: 🔸 Use default PUID=1001
Aug 19 22:41:43 chronoskimmer ddns[3841]: 🔸 Use default PGID=1001
Aug 19 22:41:43 chronoskimmer ddns[3841]: 📖 Reading settings . . .
Aug 19 22:41:43 chronoskimmer ddns[3841]: 🔸 Use default IP4_PROVIDER=cloudflare.trace
Aug 19 22:41:43 chronoskimmer ddns[3841]: 🔸 Use default IP6_PROVIDER=cloudflare.trace
Aug 19 22:41:43 chronoskimmer ddns[3841]: 🔸 Use default UPDATE_CRON=@every 5m
Aug 19 22:41:43 chronoskimmer ddns[3841]: 🔸 Use default UPDATE_ON_START=true
Aug 19 22:41:43 chronoskimmer ddns[3841]: 🔸 Use default DELETE_ON_STOP=false
Aug 19 22:41:43 chronoskimmer ddns[3841]: 🔸 Use default CACHE_EXPIRATION=6h0m0s
Aug 19 22:41:43 chronoskimmer ddns[3841]: 🔸 Use default TTL=1
Aug 19 22:41:43 chronoskimmer ddns[3841]: 🔸 Use default PROXIED=false
that means its happy.
Aug 19 22:39:32 chronoskimmer systemd[1]: cloudflare-ddns.service: Failed to load environment files: No such file or directory
Aug 19 22:39:32 chronoskimmer systemd[1]: cloudflare-ddns.service: Failed to run 'start' task: No such file or directory
Aug 19 22:39:32 chronoskimmer systemd[1]: cloudflare-ddns.service: Failed with result 'resources'.
Aug 19 22:39:32 chronoskimmer systemd[1]: Failed to start Cloudflare DDNS Updater.
Aug 19 22:39:32 chronoskimmer systemd[1]: cloudflare-ddns.service: Scheduled restart job, restart counter is at 5.
Aug 19 22:39:32 chronoskimmer systemd[1]: Stopped Cloudflare DDNS Updater.
Aug 19 22:39:32 chronoskimmer systemd[1]: cloudflare-ddns.service: Start request repeated too quickly.
Aug 19 22:39:32 chronoskimmer systemd[1]: cloudflare-ddns.service: Failed with result 'resources'.
Aug 19 22:39:32 chronoskimmer systemd[1]: Failed to start Cloudflare DDNS Updater.
that means its sad.
Be sure to read the docs on the ddns page for more info if you need to do custom things.
Actually getting the time to work
Finally, after the rather unpleasant beginning (the whole pin 1 thing was infurating), we are now ready to test our GPS/PPS (pulse per second) things. There are also some compile steps necessary to get things going.
This guide was the starting point and inspiration for my project. I have somewhat expanded it since a lot of information was missing (aka no pictures!), as well as the "network time server" secure part. This one just does plain old boring boring ntp.
Follow the guide (somewhat loosely)
Mr. Raymond (of Cathedral and Bazaar fame) was kind enough to document here how to do things. Alas, it hasn't aged perfectly well, and has some obsolete (and nonworking) scripts, which alas are not automagic anymore. I couldn't get clockmaker to work (I suspect its a python2/3 thing and I just gave up). So I followed his guide, with differences noted here. I am including copies of my working configuration files for convenience.
Flashing the sd card
The guide is a bit pedantic about how to get the image to an sdcard.I just use usbimager, (search for it), and have an sd card writer attached to my computer. I then just 'image' it. You could also use the rpi imager if you are using a debian derivitive, but I use manjaro, so it was easiest just to flash using this. Plug the card into the rpi4, and boot with appropriate monitor/mouse/etc. combos.
Relevant files to copy pasta in
dtoverlay=pps-gpio,gpio_pin=18
dtparam=audio=on
camera_auto_detect=1
display_auto_detect=1
dtoverlay=vc4-kms-v3d
max_framebuffers=2
arm_64bit=1
disable_overscan=1
[cm4]
otg_mode=1
[pi4]
# Run as fast as firmware / board allows
arm_boost=1
[all]
dtoverlay=disable-wifi
dtoverlay=disable-bt
enable_uart=1
config.txt with necessary packets per second and gpio configured, as well as disabling wifi and bluetooth.
console=tty1 root=PARTUUID=940276ca-02 rootfstype=ext4 fsck.repair=yes rootwait nohz=off
cmdline.txt which removes console access and sets our ticks to off so we have more stable time sources.
#!/bin/sh
### BEGIN INIT INFO
# Provides: timeserver
# Required-Start: $network $remote_fs $syslog
# Required-Stop: $network $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 1
# Short-Description: Start time service
### END INIT INFO
PATH=/sbin:/bin:/usr/sbin:/usr/bin/:/usr/local/bin
. /lib/lsb/init-functions
DAEMON1=/usr/local/sbin/gpsd
PIDFILE1=/var/run/gpsd.pid
DAEMON2=/usr/local/sbin/ntpd
PIDFILE2=/var/run/ntpd.pid
test -x $DAEMON1 || exit 5
test -x $DAEMON2 || exit 5
LOCKFILE=/var/lock/ntpdate
lock_ntpdate() {
if [ -x /usr/bin/lockfile-create ]; then
lockfile-create $LOCKFILE
lockfile-touch $LOCKFILE &
LOCKTOUCHPID="$!"
fi
}
unlock_ntpdate() {
if [ -x /usr/bin/lockfile-create ] ; then
kill $LOCKTOUCHPID
lockfile-remove $LOCKFILE
fi
}
NTPD_OPTS=-g
RUNASUSER=ntp
UGID=$(getent passwd $RUNASUSER | cut -f 3,4 -d:) || true
if test "$(uname -s)" = "Linux"; then
NTPD_OPTS="$NTPD_OPTS -u $UGID"
fi
# It's best to have gpsd start first. That way when ntpd restarts it has
# a good local time handy. If ntpd starts first, it will set the local
# clock using a remote, probably pool, server. Then ntpd has to spend a
# whole day undoing the damage done to the PLL.
case $1 in
start)
# Make sure the UART device is in a good state
# Adafruit HAT starts at some weird baud rate unknown to man.
stty -F /dev/ttyAMA0 115200 cs8 clocal -cstopb
# Launch gpsd
log_daemon_msg "Starting GPSD server" "timeservice"
start-stop-daemon --start --quiet --oknodo --pidfile $PIDFILE1 --startas $DAEMON1 -- -P $PIDFILE1 $GPSD_OPTS /dev/ttyAMA0
status=$?
log_end_msg $status
lock_ntpdate
# Launch ntpd
log_daemon_msg "Starting NTP server" "timeservice"
start-stop-daemon --start --quiet --oknodo --pidfile $PIDFILE2 --startas $DAEMON2 -- -p $PIDFILE2 $NTPD_OPTS
status=$?
log_end_msg $status
unlock_ntpdate
;;
stop)
log_daemon_msg "Stopping gpsd" "timeservice"
start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE1
log_end_msg $?
log_daemon_msg "Stopping ntpd" "timeservice"
start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE2
log_end_msg $?
rm -f $PIDFILE1 $PIDFILE2
;;
restart|force-reload)
$0 stop && sleep 2 && $0 start
;;
try-restart)
if $0 status >/dev/null; then
$0 restart
else
exit 0
fi
;;
reload)
exit 3
;;
status)
status_of_proc $DAEMON1 "time service"
status_of_proc $DAEMON2 "time service"
;;
*)
echo "Usage: $0 {start|stop|restart|try-restart|force-reload|status}"
exit 2
;;
esac
# end
timeserver system5 script to start and stop timesync d when everything is working! Put in /etc/init.d
Timeservice file from the original, but modified with proper baud and device names. You could use /dev/serial0 if you want to, it seems that the pi automatically aliases that (at least it did on my build probably from the unsuccessful runs of the clockmaker script). I use use /dev/ttyAMA0 throughout the guide rather than /dev/gps0.
# /etc/ntp.conf, configuration for ntpd; see ntp.conf(5) for help
# Prefer the precise clock at unit 1 over the imprecise one at unit 0.
# GPS PPS reference (NTP1)
refclock shm unit 1 refid PPS
# GPS Serial data reference (NTP0)
refclock shm unit 0 refid GPS
# Check servers
# If you have no other local chimers to help NTP perform sanity checks
# then you can use some public chimers from the NTP public pool:
# http://www.pool.ntp.org/en/
#
# iburst tells it to send the first few requests at 2 second intervals rather
# than wait for the poll interval which defaults to 64 seconds. That greatly
# speeds up the time for ntpd to set the system time and start responding to
# requests.
#
# Notice we use the 'us' country code servers, otherwise we might get
# pool servers from opposite sides of the planet accuracy would likely
# be poor. If you are not in the USA, then it will probably work to
# change the 'us' to your two letter country code.
#
# Major Internet-using countries with pools include:
# us gb de fr ru au at ca cn jp de fi it be br cz hk
#
# If you don't know your country code, find it at
#
# https://en.wikipedia.org/wiki/ISO_3166-1
#
# and then try pinging prepending it to ".pool.ntp.org" and pinging that.
# hostname. If you get a response, you can use it.
#
pool us.pool.ntp.org iburst
# By default, exchange time with everybody, but don't allow configuration.
restrict default kod limited nomodify nopeer noquery
restrict -6 default kod limited nomodify nopeer noquery
# Local users may interrogate the NTP server more closely.
restrict 127.0.0.1
restrict -6 ::1
# Drift file etc.
# Ensure that the directory exists, and is writable by whichever user
# the ntpd daemon runs as.
driftfile /var/lib/ntp/ntp.drift
# end
ntp.conf (probably without ntps configuration, which we can fix later) goes in /etc/
[Unit]
Description=Manage time service daemons
[Service]
Type=oneshot
ExecStart=/etc/init.d/timeservice start
ExecStop=/etc/init.d/timeservice stop
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
systemd unit file goes in /etc/systemd/system
Enabling ssh access
Modern rpi systems will ask you what user you should use. I tend to use my secretz user (actually something else), and make a long evil password. For better information on this, you should probably secure your ssh program like this. The guide's options are a little outdated. ALWAYS disable password access once you get your keys installed ssh-copy-id chronoskimmer assuming that the name of your rpi is chronoskimmer! will automatically copy any ssh keys on your system to the remote (assuming linux/mac). POOF automatic login, then you can disable your ssh passwords.
Host *
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com
HostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com
Secure your client code snippet. Server is similarly configured using guides listed above. This goes in your ~/.ssh/config file
Ignoring clockmaker script
The clockmaker scrip alas, does NOT seem to work anymore, and I hate python, so I gave up. If you want to get it working good luck.
Steps to perform next
- Fully update your os
- Skip the 'enable your uart, and remove console lines' and paste in the file I had in the top over cmdline.txt and config.txt in the /boot partition of the pi.
- skip disable bluetooth and remap console devices
systemctl disable hciuart- skip configure the 1pps gpio pin (my files fix this)
- skip gpu memory split (since we have a pi with 4gb it really doesnt' matter anymore)
- skip the config.txt file, since its NOT for ours, and is specific to the pi3.
- in opt system performance skip nohz=off. follow remainder
- in smoke test the gps/hat combo modify lines as follows
stty -F /dev/ttyAMA0 raw 115200 cs8 clocal -cstopb cat /dev/ttyAMA0- live-test the gps follow exactly (just substitute user pi for your user)
- Copy his install commands:
apt install git scons libncurses-dev python-dev bc- modify lines as follows
sudo su -
cd /usr/src
git clone https://gitlab.com/gpsd/gpsd.git
chown -R : gpsd
exit
cd gpsd
scons timeservice=yes magic_hat=yes nmea0183=yes ublox=yes mtk3301=yes fixed_port_speed=115200 fixed_stop_bits=1
- Alas the tests that he suggests no longer work, so after 'sudo to root and run this do'
cd /usr/src/gpsd/gpsd-~dev/gpsd
./gpsmod /dev/ttyAMA0
- And modify the next lines for testing ntpshmmon (these don't work in the original).
cd ../
cd clients
./ntpshmmon
- his checks and veribaige is correct, just that the commands don't seem to work if you don't know how to hunt for them. For the interested, I just used find command to look for relevant commands, which were hidden here, and NOT according to original docs.
- build and configure ntpsec all are correct, except for the directories, which I change to /usr/src/. Repeat the steps as above, (for git clone), making sure you are in /usr/src, and chown them back to your user when you are done.
- I like to put the ntp.conf directly in the ntpsec directory where he has you compile everything. Its the one referenced in the first part of the guide.
- the "Smoke test NTPSEC" section seems fine, except you need to add a
mkdir /var/NTP
chown -r ntp:ntp /var/NTP
- after you do his commands, for some reason the daemon screams if this directory isn't present. Be sure to change /home/pi to your homedirectory.
- For running newly built ntpd, use the following command:
cd /usr/src/ntpsec/build/main/ntpd
./ntpd -g -c ntpsec/ntp.conf
- To check use the following commands:
cd ../
cd ntpclients
./ntpq -p
- Follow the things remaining there.
- skip the pinup script which seems to hate modern raspberry pi 4's.
- And for the timeservice section, just copy the file I included as per the directions.
- Same for the systemd configurations
- on "secure the machine" I could go into this in detail, but basically you want to enable something like fail2ban so wierdos don't try to hack your machine if its on the outside. I also like to enable autopatching, (use apt-cron) to enable regular updating. I haven't figured out how to patch ntp daemons yet, but will keep things apprised. I also added myself on the usermod command like he said.
- I did the simplificaiton and optimization sections. I skipped remove dhcpd, and avahi, I don't bother with statics, since I can use dns to get my servername.
Overall this guide actually worked, as opposed to the earlier one with chrony, which never worked. I got it partially working, but the gpsd stuff never properly read the serial port w/o having to configure it. You might have to save the timings (as per the old guide), but overall, this guide actually worked. I am sad that it hasn't been updated since 2018, but it mostly is correct.