Chrony NTP Time Server on Ubuntu 24.04 on Azure User Guide
Overview
This guide covers the deployment and configuration of Chrony on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Chrony is a versatile, high accuracy implementation of the Network Time Protocol (NTP) and the modern default time daemon on most Linux systems. This image ships it as a self hosted NTP time server for a network: the chronyd service disciplines the local clock from upstream time sources and serves precise time to other machines on UDP port 123.
Accurate time is foundational infrastructure. TLS certificate validation, Kerberos and Active Directory authentication, distributed databases, log correlation and audit timestamps all break in subtle ways when clocks drift. Chrony keeps this server, and every client that synchronises against it, correct to well under a millisecond, and it recovers from network interruptions and large offsets far better than the legacy ntpd it replaces.
The image runs Chrony 4.5 from the official Ubuntu package under systemd (chrony.service). There is no web interface and no database: /etc/chrony/chrony.conf is the whole configuration, and the server is administered from the command line with chronyc.
Secure by default — not an open amplifier. A naively deployed public NTP server can be abused for reflection and amplification attacks. This image avoids that: the shipped configuration serves NTP only to the local host and the private RFC1918 network ranges, a documented and scoped allow list rather than allow all, so a query arriving from a public internet address is simply dropped. The chronyc command interface is bound to the local host only, and Chrony has no legacy monlist command at all. An NTP server has no login, so no shared or default credential of any kind ships in the image.
What is included:
-
Chrony 4.5 from the official Ubuntu package, run under systemd as
chrony.service -
An NTP service on
0.0.0.0:123/udp, serving the local host and your private networks -
On Azure, the Hyper V host hardware clock added automatically on first boot as a high accuracy PTP reference clock, giving sub millisecond, network independent synchronisation
-
The public NTP pool configured as a resilient fallback time source
-
A scoped
allowlist (loopback plus the RFC1918 private ranges) so the server is not an open amplifier until you deliberately widen it to your own subnets -
The
chronyccommand and monitoring interface bound to the local host only, with no amplification vectors
Prerequisites
-
Active Azure subscription, SSH public key, VNet plus subnet in target region
-
Subscription to the Chrony listing on Azure Marketplace
-
Network Security Group rules allowing UDP 123 (NTP) from the clients that will synchronise against the server, and TCP 22 for administration
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is ample for a departmental or virtual network time server. Chrony is extremely light; larger sizes are only needed for very large client populations.
Step 1: Deploy from the Azure Portal
Search Chrony in Marketplace, select the cloudimg publisher, and click Create. Configure the Network Security Group to allow UDP 123 from the clients that will synchronise against the server, and TCP 22 for administration. Keep UDP 123 restricted to the subnets you serve; there is no need to expose it to the public internet for an internal time server.
Step 2: Deploy from the Azure CLI
RG="time-prod"; LOCATION="eastus"; VM_NAME="chrony1"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/chrony-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
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 22 --priority 1002
Open UDP 123 only to the client subnets you intend to serve.
Step 3: First boot
On first boot the image detects the Azure Hyper V host clock and adds it as a high accuracy PTP reference, resolves this VM's address, writes the informational note /root/chrony-info.txt, and restarts chrony so it is serving time within seconds. SSH in as azureuser and read the note:
sudo cat /root/chrony-info.txt
The note records this server's role, the address and port it serves, the detected PTP reference clock, and the path to the configuration. Chrony has no password, so the note holds no secret.
Step 4: Confirm the service is running
chrony.service is active, chronyd --version reports 4.5, and ss confirms chronyd is listening on UDP 123 on all interfaces, ready to answer clients on your allowed networks.
systemctl is-active chrony.service
chronyd --version
sudo ss -ulnp | grep -E ':123'
The ss output shows chronyd bound to 0.0.0.0:123 and [::]:123 — the NTP server port. (The command port 323 is bound only to 127.0.0.1 and ::1, so it is never reachable off the box.)

Step 5: Confirm the clock is synchronised
chronyc tracking reports the server's own synchronisation state. Leap status : Normal means the server is synchronised and safe to serve time. The Reference ID and Stratum identify the upstream source the server is locked to, and the System time and RMS offset lines show how tightly the clock is disciplined, typically well under a millisecond.
chronyc tracking
On Azure the server locks to the Hyper V host clock (shown as PHC0, stratum 0 to 1) or a stratum 2 NTP pool server, and reports Leap status : Normal with a sub millisecond system time offset.

Step 6: Review the time sources
chronyc sources lists every source the server is disciplining from, with a state marker in the first column: * is the currently selected best source, + is a combined source, # marks a local hardware reference clock such as the Azure PHC, and ? or - are sources that are not currently selected. chronyc sourcestats shows the measured offset and skew for each.
chronyc sources
chronyc sourcestats
On Azure you will see the PHC0 hardware reference clock alongside several ntp.ubuntu.com and pool servers, with one source marked * as the current best.

Step 7: Confirm it serves time and is not an open amplifier
This is the security posture that matters for an NTP server. The shipped allow list answers only the local host and the RFC1918 private ranges, so a query from a public source address is dropped. Read the allow list, confirm the command port is loopback only, then prove the server answers a real NTP client query on port 123 with the bundled self check.
sudo grep -E '^(allow|cmdport|bindcmdaddress)' /etc/chrony/chrony.conf
sudo ss -ulnp | grep -E ':323'
python3 /usr/local/sbin/ntp-selfcheck.py 127.0.0.1
The allow lines are scoped to 127.0.0.1, ::1 and the private ranges — never allow all. The command port 323 is bound only to loopback. The self check sends a real NTP client request to port 123 and prints OK with the returned time, proving the server serves NTP while remaining scoped to your private networks.

Step 8: Point your clients at the server
Configure your other machines to use this instance as their NTP server, using its private IP address (shown in the info note from Step 3). The examples below are run on each client, not on this server.
# --- Linux client running chrony ---
# Add a line to the client's /etc/chrony/chrony.conf, then restart chrony:
# server <server-ip> iburst
#
# --- Linux client running systemd-timesyncd ---
# Set NTP=<server-ip> in /etc/systemd/timesyncd.conf, then:
# sudo systemctl restart systemd-timesyncd
#
# --- Windows client ---
# w32tm /config /manualpeerlist:"<server-ip>" /syncfromflags:manual /update
#
# Verify from a Linux client that it is using this server:
# chronyc sources # the server should appear, selected with a '*'
Replace <server-ip> with this instance's private IP address. Because the default allow list already covers the RFC1918 private ranges, clients on your virtual network can synchronise immediately with no change on the server.
Step 9: Widen the allow list to your network
The shipped allow list covers the whole RFC1918 private space. To narrow it to your exact virtual network, or to serve a network outside RFC1918, edit the allow lines in /etc/chrony/chrony.conf and restart the service. Only add networks you control — never use allow all, which turns the server into an open amplifier.
# Edit /etc/chrony/chrony.conf and set the allow list to your exact VNet CIDR, e.g.
# allow 10.20.0.0/16
# Remove the broader ranges you do not need, then apply the change:
# sudo systemctl restart chrony
# Confirm the new scope:
# sudo grep -E '^allow' /etc/chrony/chrony.conf
Chrony validates the configuration on restart; if there is a syntax error the service will report it in systemctl status chrony and you can correct it before clients are affected.
Step 10: Review the configuration and info note
Everything Chrony does is in /etc/chrony/chrony.conf, and the per instance details are in the info note. Both are readable with sudo.
sudo cat /etc/chrony/chrony.conf
sudo cat /root/chrony-info.txt
The info note is 0600 root:root and holds no secret — an NTP server has no login. It simply records the server's role, the address it serves, and the detected PTP reference for convenience.
Step 11: Security recommendations
-
Restrict the NSG to your use case. Allow UDP 123 only from the client subnets you serve. There is no need to expose an internal time server to the public internet.
-
Keep the allow list tight. Only add subnets you control to the
allowlines. Never useallow all— that turns the server back into an open amplifier that can be abused for reflection attacks. -
Leave the command port loopback only. The
chronyccommand interface is bound to127.0.0.1and::1so it cannot be reached or reflected from off the box. Keep it that way; administer the server over SSH. -
Prefer the hardware reference on Azure. The Hyper V host clock (
PHC0) is a stratum 0 reference disciplined to Microsoft's time sources and is the most accurate option; it is configured automatically on first boot. -
Keep the OS patched. Unattended security upgrades remain enabled on the running VM.
Step 12: Support and Licensing
Chrony is an open source project distributed under the GNU General Public License version 2. This cloudimg image bundles the unmodified official Chrony package from Ubuntu. cloudimg provides the packaging, the systemd hardening, the secure by default scoped allow configuration, the Azure PTP reference clock automation, the paired deploy guide, and 24/7 support with a guaranteed 24 hour response SLA.
cloudimg is not affiliated with or endorsed by the Chrony project. Chrony is a mark of its respective owner and is used here only to identify the software.
Deploy on Azure
Find Chrony NTP Time Server on Ubuntu 24.04 LTS on the Azure Marketplace, published by cloudimg. Deploy from the Portal or the Azure CLI as shown above.
Need Help?
Email support@cloudimg.co.uk for deployment help, configuration questions, or licensing enquiries.