ntpd-rs on Ubuntu 24.04 on Azure User Guide
Overview
This guide covers the deployment and configuration of ntpd-rs on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. ntpd-rs is a modern, memory safe implementation of the Network Time Protocol, written in Rust by the Pendulum project. It is a full NTP client and server in one: it keeps this machine's own clock disciplined against the public NTP pool, and it serves accurate time to the other machines on your network, so you can run your own internal time source instead of relying on external servers for every device.
The image installs ntpd-rs 1.9.0 from the official pinned, checksum verified package, run under systemd as the ntpd-rs.service daemon. The daemon runs as an unprivileged ntpd-rs system user with only the capabilities it needs to read network time and set the system clock, and it cleanly replaces the base image's default time synchronisation. It is administered with the ntp-ctl command line tool over a local Unix observation socket, so the management interface is never exposed to the network. The only network facing surfaces the image opens are the NTP service on udp/123 and SSH.
Secure by default — no known credentials. Serving network time requires no login and no password, so this image ships with none. The management and observation interface is bound to a local Unix socket, so it can never be reached from the network, and the Prometheus metrics exporter is left disabled. On the first boot of every VM a one shot service records this instance's reachable address in a root only file, so you know exactly where to point your clients. ntpd-rs is fully capable of Network Time Security (NTS) for authenticated time; it is left off by default because it needs a certificate your clients can trust, and this guide shows how to enable it with your own certificate.
What is included:
-
ntpd-rs 1.9.0 installed from the official pinned, checksum verified release, run under systemd as
ntpd-rs.service -
A client that disciplines this machine's clock from four distinct public NTP pool sources
-
A server that answers time requests from your machines on
0.0.0.0:123(udp) -
A local Unix observation socket (
/var/run/ntpd-rs/observe) thatntp-ctluses, never reachable from the network -
The Prometheus metrics exporter present but disabled by default (no management TCP port)
-
This instance's reachable server address recorded to
/root/ntpd-rs-credentials.txt(root only) on first boot
Prerequisites
-
Active Azure subscription, SSH public key, VNet + subnet in target region
-
Subscription to the ntpd-rs listing on Azure Marketplace
-
Network Security Group rules allowing TCP 22 (administration) and UDP 123 (the NTP service) from the machines that will use this server for time
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is ample for a time server for a small to medium network. ntpd-rs is an extremely lightweight daemon; larger sizes are only needed for very large client populations.
Step 1: Deploy from the Azure Portal
Search ntpd-rs in Marketplace, select the cloudimg publisher, and click Create. Configure the Network Security Group to allow UDP 123 from the machines that will use this server for time, and TCP 22 for administration.
Step 2: Deploy from the Azure CLI
RG="time-prod"; LOCATION="eastus"; VM_NAME="ntp1"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/ntpd-rs-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az vm create \
--resource-group "$RG" --name "$VM_NAME" \
--image "$GALLERY_IMAGE_ID" \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values "$SSH_KEY" \
--public-ip-sku Standard
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 123 --priority 1001
The NTP service is UDP; make sure the NSG rule you add for port 123 uses the UDP protocol so clients can reach the server.
Step 3: First boot — find your server address
On first boot the image records this instance's reachable address so you know where to point your clients, and starts ntpd-rs. This completes within seconds. SSH in as azureuser and read the record:
sudo cat /root/ntpd-rs-credentials.txt
The file records this server's NTP_SERVER_IP (the address your clients point at), the NTP_SERVER_PORT (123), and the local OBSERVE_SOCKET path used for management. There are no passwords in this file, because serving network time needs none. If your VM sits behind a Standard SKU public IP that the platform metadata does not expose, the recorded address may show the VM's private address — substitute your VM's public IP when configuring clients outside the VNet.
Step 4: Confirm the server is running
ntpd-rs.service is active, ntp-daemon --version reports 1.9.0, and ntp-ctl status shows the upstream pool sources this server disciplines its own clock from — that is the client role. When the daemon first starts it reports Stratum 16 (not yet synchronised); within a few minutes it selects sources and settles to a low stratum (typically 2 or 3).
ntp-daemon --version
systemctl is-active ntpd-rs.service
sudo ntp-ctl status

Step 5: Confirm it is serving time
This is the reason to run the appliance: the server answers time requests from your machines. The daemon binds the NTP service on 0.0.0.0:123 (udp), and a real NTP client query returns a valid mode-4 (server) response carrying the current time. The offset shown is the difference from the querying client's clock, in seconds.
sudo ss -ulnp sport = :123
python3 /opt/ntpd-rs/ntp-query.py 127.0.0.1 123

Step 6: Secure by default
The only network facing surface besides SSH is the NTP service on udp/123. The management and observation interface that ntp-ctl uses is a local Unix socket, never a TCP port, so it can never be reached from the network. The Prometheus metrics exporter, which would open a TCP port, is left disabled.
sudo ss -tlnH
ls -l /var/run/ntpd-rs/observe
systemctl is-enabled ntpd-rs-metrics.service || true
The only non-loopback TCP listener is SSH on port 22; the 127.0.0.53/127.0.0.54 port 53 entries are the local DNS stub resolver and are not reachable from the network. The observe interface is a srw Unix socket, and the metrics service reports disabled.

Step 7: Point a client at this server
To use this server for time, point your machines at its NTP_SERVER_IP (from Step 3). You can confirm the server answers on its own reachable address from the VM itself:
SERVER_IP="$(hostname -I | awk '{print $1}')"
echo "This server: ${SERVER_IP}"
python3 /opt/ntpd-rs/ntp-query.py "${SERVER_IP}" 123
On a Linux client that uses systemd-timesyncd, set this server as the time source in /etc/systemd/timesyncd.conf (this is a configuration snippet for the client machine, not a command to run on the server):
[Time]
NTP=<NTP_SERVER_IP>
Then apply it on the client with sudo systemctl restart systemd-timesyncd and confirm with timedatectl show-timesync --property=ServerName. On Windows clients, set the NTP server with w32tm /config /manualpeerlist:<NTP_SERVER_IP> /syncfromflags:manual /update. Any standard NTP client works — the server speaks standard NTPv4.

Step 8: Serve additional networks and tune sources
The server configuration lives in /etc/ntpd-rs/ntp.toml. It ships serving on all interfaces (0.0.0.0:123) and disciplining its clock from the ntpd-rs.pool.ntp.org pool. To point at your own upstream servers instead of the public pool, or to add a specific upstream, edit the [[source]] blocks (configuration snippet):
# Discipline this server's clock from your own upstream NTP servers.
[[source]]
mode = "server"
address = "upstream1.example.com"
[[source]]
mode = "server"
address = "upstream2.example.com"
After editing the configuration, apply it with sudo systemctl restart ntpd-rs and re-check with sudo ntp-ctl status. Keep at least three upstream sources so the algorithm can reject a single misbehaving source.
Step 9: Enable Network Time Security (NTS)
ntpd-rs is fully capable of Network Time Security (RFC 8915), which authenticates and protects NTP against tampering. It is off by default because an NTS-KE (key exchange) server needs a TLS certificate your clients can trust. To enable it, obtain a certificate for a hostname your clients resolve (for example with Let's Encrypt), then add an NTS-KE server block to /etc/ntpd-rs/ntp.toml referencing your certificate and key (configuration snippet):
[[nts-ke-server]]
listen = "0.0.0.0:4460"
certificate-chain-path = "/etc/ntpd-rs/tls/fullchain.pem"
private-key-path = "/etc/ntpd-rs/tls/privkey.pem"
Keep the private key 0600 root:root (it is generated on your own instance and never leaves it), open TCP 4460 in the NSG for NTS-KE, apply with sudo systemctl restart ntpd-rs, and point NTS-capable clients at your hostname. Because the certificate and key are yours and are created on your own VM, no secret is ever baked into the image.
Troubleshooting
-
A client cannot reach the server. Confirm the NSG allows UDP 123 (not TCP) from the client, and that the client points at this server's reachable address from Step 3. From the server,
python3 /opt/ntpd-rs/ntp-query.py 127.0.0.1 123proves the daemon is serving locally. -
The server shows Stratum 16. That is normal immediately after start; ntpd-rs is conservative and takes a few minutes of polling to declare synchronisation.
sudo ntp-ctl statusshows the sources and their offsets converging. If it never leaves Stratum 16, confirm the VM has outbound UDP 123 to the internet so it can reach the pool. -
Check server health.
sudo ntp-ctl statusshows the synchronisation state and every source;sudo systemctl status ntpd-rsandsudo journalctl -u ntpd-rsshow service logs.
Support
cloudimg provides 24/7 technical support for this ntpd-rs image. Our engineers help with designing your time hierarchy, pointing clients at this server, tuning upstream sources, enabling Network Time Security with your own certificate, turning on loopback Prometheus metrics for monitoring, firewall and NSG rules, and ntpd-rs version upgrades. Critical issues receive a one hour average response time. Contact support@cloudimg.co.uk.
All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.