Nr
Networking Azure

ntpd-rs on Ubuntu 24.04 on Azure User Guide

| Product: ntpd-rs 1.9.0 on Ubuntu 24.04 LTS on Azure

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) that ntp-ctl uses, 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

ntp-daemon reports version 1.9.0, ntpd-rs.service reports active, the first boot service recorded this server address, and ntp-ctl status shows the four upstream public NTP pool sources this server disciplines its own clock from, confirming the client role is working

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

the ss command shows ntp-daemon listening on 0.0.0.0:123 for the NTP service, and a real NTP client query to the server returns NTP OK with mode 4 and a sub-second offset, confirming the server is genuinely serving accurate time to clients

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.

ss shows the only non-loopback TCP listener is SSH on port 22 with the DNS stub resolver bound to loopback only, the ntpd-rs observe interface is a local Unix domain socket rather than a TCP port, and the Prometheus metrics exporter is disabled, confirming the server is secure by default with no network reachable management interface

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.

the server reports its own reachable address recorded in the root only per instance credentials file with 0600 permissions, and an NTP query to that address returns NTP OK with mode 4, confirming a client can reach this server on its network address and receive the current time, with systemd-timesyncd client configuration shown

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 123 proves 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 status shows 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 status shows the synchronisation state and every source; sudo systemctl status ntpd-rs and sudo journalctl -u ntpd-rs show 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.