In the interest of Time
- Sep 14
- 7 min read
How to create a secure Network Time Server (NTP) & configure NTP clients

Build Document:
The accuracy of a device's time settings is often underrated. All independent clocks drift over time and become inaccurate. Essential logs which are utilized to troubleshoot problems and used forensically to examine security incidents are heavily dependent having accurate time stamps. In addition it is more secure to create a local NTP server on your network and have NTP clients connect to it rather than have each client on your network connect to the Internet for time services.
First step install chrony, the modern network time server.
$ sudo apt update && apt -y install chronyNext configure chrony to pull time from secure, trusted public NTP servers - To configure chrony in this way we need to edit /etc/chrony/chrony.conf - Backup /etc/chrony/chrony.conf then comment out the default NTP servers to make chrony.conf appear similar to the below. -
sosuser@opensosntp:/etc/chrony$ sudo cp /etc/chrony/chrony.conf /etc/chrony/chrony.conf_ORIG
sosuser@opensosntp:/etc/chrony$ sudo nano /etc/chrony/chrony.conf
# Welcome to the chrony configuration file. See chrony.conf(5) for more
# information about usable directives.
# Include configuration files found in /etc/chrony/conf.d.
confdir /etc/chrony/conf.d
# This will use (up to):
# - 4 sources from ntp.ubuntu.com which some are ipv6 enabled
# - 2 sources from 2.ubuntu.pool.ntp.org which is ipv6 enabled as well
# - 1 source from [01].ubuntu.pool.ntp.org each (ipv4 only atm)
# This means by default, up to 6 dual-stack and up to 2 additional IPv4-only
# sources will be used.
# At the same time it retains some protection against one of the entries being
# down (compare to just using one of the lines). See (LP: #1754358) for the
# discussion.
# About using servers from the NTP Pool Project in general see (LP: #104525).
# Approved by Ubuntu Technical Board on 2011-02-08.
# See http://www.pool.ntp.org/join.html for more information.
#pool ntp.ubuntu.com iburst maxsources 4
#pool 0.ubuntu.pool.ntp.org iburst maxsources 1
#pool 1.ubuntu.pool.ntp.org iburst maxsources 1
#pool 2.ubuntu.pool.ntp.org iburst maxsources 2
# Use secure public NTP servers (choose a trusted pool or specific servers)
server time.cloudflare.com iburst
server time.google.com iburst
server time.facebook.com iburst
server time.windows.com iburst
# Allow internal network to sync time from this server, add YOUR networks
# allow 10.0.0.0/8
allow 192.168.6.0/24
# Listen for incoming NTP requests
bindaddress 0.0.0.0
# Optional: local stratum fallback if no external servers are reachable
local stratum 10
####
# Use time sources from DHCP.
sourcedir /run/chrony-dhcp
# This directive specify the location of the file containing ID/key pairs for
# NTP authentication.
keyfile /etc/chrony/chrony.keys
# This directive specify the file into which chronyd will store the rate
# information.
driftfile /var/lib/chrony/chrony.drift
# Save NTS keys and cookies.
ntsdumpdir /var/lib/chrony
# Uncomment the following line to turn logging on.
#log tracking measurements statistics
# Log files location.
logdir /var/log/chrony
# Stop bad estimates upsetting machine clock.
maxupdateskew 100.0
# This directive enables kernel synchronisation (every 11 minutes) of the
# real-time clock. Note that it can't be used along with the 'rtcfile' directive.
rtcsync
# Step the system clock instead of slewing it if the adjustment is larger than
# one second, but only in the first three clock updates.
makestep 1 3
# Get TAI-UTC offset and leap seconds from the system tz database.
# This directive must be commented out when using time sources serving
# leap-smeared time.
leapsectz right/UTC
In the above /etc/chrony/chrony.conf file remember to change "allow 192.168.6.0"
to a value that represents the network(s) you will allow NTP clients to connect from.
Before proceeding, ensure that your system’s local clock is accurately configured. If the NTP server we are configuring is unable to reach upstream time sources, it will fall back on the local clock—so it must be correct to maintain reliable time across clients.
sosuser@opensosntp:/etc/chrony$ timedatectl status
Local time: Sun 2025-06-29 22:20:04 UTC
Universal time: Sun 2025-06-29 22:20:04 UTC
RTC time: Sun 2025-06-29 22:20:04
Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
NTP service: active
RTC in local TZ: no
Lets change the local clock to the correct time, by setting the correct time zone. For us to be able to change the local clock we will need to disable time synchronization (sudo timedatectl set-ntp false) so that we can force changes to the time on the local clock.
$ sudo timedatectl set-timezone America/New_York
$ sudo timedatectl set-ntp false
$ sudo timedatectl set-time "2025-06-29 17:52:00"
$ timedatectl status
Local time: Sun 2025-06-29 17:54:50 EDT
Universal time: Sun 2025-06-29 21:54:50 UTC
RTC time: Sun 2025-06-29 21:54:51
Time zone: America/New_York (EDT, -0400)
System clock synchronized: no
NTP service: inactive
RTC in local TZ: no
Now that local time on the server is correctly configured and verified its time to start chrony and verify the status of the chrony client.
$ sudo systemctl start chrony
$ sudo systemctl enable chrony
$ systemctl status chrony
● chrony.service - chrony, an NTP client/server
Loaded: loaded (/usr/lib/systemd/system/chrony.service; enabled Active: active (running) since Sun 2025-06-29 18:03:54 EDT; 1h 1min ago
$ chronyc activity
200 OK
4 sources online
0 sources offline
0 sources doing burst (return to online)
0 sources doing burst (return to offline)
0 sources with unknown address
$ chronyc sources -v
.-- Source mode '^' = server, '=' = peer, '#' = local clock.
/ .- Source state '*' = current best, '+' = combined, '-' = not combined,
| / 'x' = may be in error, '~' = too variable, '?' = unusable.
|| .- xxxx [ yyyy ] +/- zzzz
|| Reachability register (octal) -. | xxxx = adjusted offset,
|| Log2(Polling interval) --. | | yyyy = measured offset,
|| \ | | zzzz = estimated error.
|| | | \
MS Name/IP address Stratum Poll Reach LastRx Last sample
===============================================================================
^+ time.cloudflare.com 3 7 373 113 -2899us[-2899us] +/- 31ms
^* time4.google.com 1 6 377 114 +2791us[+2617us] +/- 24ms
^+ time4.facebook.com 1 7 377 115 -3115us[-3289us] +/- 31ms
^+ 40.119.6.228 3 6 377 47 +1798us[+1798us] +/- 66ms
$ chronyc tracking
Reference ID : D8EF230C (time4.google.com)
Stratum : 2
Ref time (UTC) : Sun Jun 29 23:25:02 2025
System time : 0.000044235 seconds fast of NTP time
Last offset : +0.000120695 seconds
RMS offset : 0.000237082 seconds
Frequency : 5.118 ppm slow
Residual freq : -0.000 ppm
Skew : 0.110 ppm
Root delay : 0.047045529 seconds
Root dispersion : 0.000417993 seconds
Update interval : 129.5 seconds
Leap status : Normal
Below we will first add a Windows NTP client to the NTP server, then add an Ubuntu client to the server.

On the above Windows 11 computer open services, find the Windows Time service, start it and set it's Startup Type to "Automatic"
Next right click the Windows command prompt to open it with "Run as administrator" permissions and run the following commands to connect to the Open Source NTP server we created on the server with IP address "192.168.6.196"

The following steps will add a Linux computer running Ubuntu 24.04 as a NTP client. The first step is to install chrony.
sosuser@Ubuntu22Desktop:~$ hostname
Ubuntu22Desktop
sosuser@Ubuntu22Desktop:~$ sudo apt update && apt install -y chrony
Hit:1 http://security.ubuntu.com/ubuntu jammy-security InRelease
Hit:2 https://dl.google.com/linux/chrome/deb stable InRelease
Hit:3 http://pe.archive.ubuntu.com/ubuntu jammy InRelease
Hit:4 http://pe.archive.ubuntu.com/ubuntu jammy-updates InRelease
Hit:5 http://pe.archive.ubuntu.com/ubuntu jammy-backports InRelease
Hit:6 https://ppa.launchpadcontent.net/mozillateam/ppa/ubuntu jammy InRelease
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
195 packages can be upgraded. Run 'apt list --upgradable' to see them.
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following packages will be REMOVED:
systemd-timesyncd
The following NEW packages will be installed:
chrony
0 upgraded, 1 newly installed, 1 to remove and 195 not upgraded....
sosuser@Ubuntu22Desktop:~$ sudo cp /etc/chrony/chrony.conf /etc/chrony/chrony.conf_ORIG
sosuser@Ubuntu22Desktop:~$ sudo nano /etc/chrony/chrony.conf
# Welcome to the chrony configuration file. See chrony.conf(5) for more
# information about usable directives.
# Include configuration files found in /etc/chrony/conf.d.
confdir /etc/chrony/conf.d
# This will use (up to):
# - 4 sources from ntp.ubuntu.com which some are ipv6 enabled
# - 2 sources from 2.ubuntu.pool.ntp.org which is ipv6 enabled as well
# - 1 source from [01].ubuntu.pool.ntp.org each (ipv4 only atm)
# This means by default, up to 6 dual-stack and up to 2 additional IPv4-only
# sources will be used.
# At the same time it retains some protection against one of the entries being
# down (compare to just using one of the lines). See (LP: #1754358) for the
# discussion.
#
# About using servers from the NTP Pool Project in general see (LP: #104525).
# Approved by Ubuntu Technical Board on 2011-02-08.
# See http://www.pool.ntp.org/join.html for more information.
#pool ntp.ubuntu.com iburst maxsources 4
#pool 0.ubuntu.pool.ntp.org iburst maxsources 1
#pool 1.ubuntu.pool.ntp.org iburst maxsources 1
#pool 2.ubuntu.pool.ntp.org iburst maxsources 2
server 192.168.6.182
You can leave all the other entries of the chrony.conf with their defaults
Save the /etc/chrony/chrony.conf file and restart chrony
Also make sure that chrony starts after a reboot by enabling the chrony service.
sosuser@Ubuntu22Desktop:~$ sudo systemctl restart chrony
sosuser@Ubuntu22Desktop:~$ sudo systemctl enable chrony
Synchronizing state of chrony.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable chrony
Finally verify that the chrony client on Linux is working and pulling time from the NTP server:
sosuser@Ubuntu22Desktop:~$ sudo chronyc tracking
[sudo] password for sosuser:
Reference ID : C0A806B6 (192.168.6.182)
Stratum : 3
Ref time (UTC) : Mon Jul 07 17:33:07 2025
System time : 0.000015730 seconds fast of NTP time
Last offset : +0.000036781 seconds
RMS offset : 0.000041848 seconds
Frequency : 5.050 ppm slow
Residual freq : +0.005 ppm
Skew : 0.286 ppm
Root delay : 0.047718432 seconds
Root dispersion : 0.000613245 seconds
Update interval : 259.5 seconds
Leap status : Normal
sosuser@Ubuntu22Desktop:~$ sudo chronyc sources -v
.-- Source mode '^' = server, '=' = peer, '#' = local clock.
/ .- Source state '*' = current best, '+' = combined, '-' = not combined,
| / 'x' = may be in error, '~' = too variable, '?' = unusable.
|| .- xxxx [ yyyy ] +/- zzzz
|| Reachability register (octal) -. | xxxx = adjusted offset,
|| Log2(Polling interval) --. | | yyyy = measured offset,
|| \ | | zzzz = estimated error.
|| | | \
MS Name/IP address Stratum Poll Reach LastRx Last sample
===============================================================================
^* 192.168.6.182 2 8 377 236 +23us[ +59us] +/- 24ms
sosuser@Ubuntu22Desktop:~$
Above you should see your internal NTP server listed with a * indicating it's the selected source:
If however you get the below question mark give it sometime and try again, if the question mark is a ! you have a communication problem.
Conclusion
Establishing accurate and reliable time synchronization across all systems is a foundational step in gaining full control of your network infrastructure. Inconsistent or drifting system clocks can lead to a range of issues, from troubleshooting difficulties to critical security vulnerabilities. By implementing open-source NTP servers, organizations gain a high degree of control and flexibility, ensuring consistent timekeeping that supports operational integrity and enhances incident response capabilities.
