Configure NTP server with chrony and security hardening for precise time synchronization

Intermediate 35 min Apr 06, 2026 228 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up a production-grade NTP server using chrony with client access controls, firewall rules, and security hardening. Learn to configure upstream time sources, implement monitoring, and troubleshoot common synchronization issues.

Prerequisites

  • Root or sudo access
  • Network connectivity to upstream NTP servers
  • Basic understanding of Linux networking
  • Firewall management knowledge

What this solves

Precise time synchronization is critical for distributed systems, log correlation, and security protocols. This tutorial shows you how to configure chrony as an NTP server with proper security hardening, client access controls, and monitoring for production environments.

Step-by-step installation

Update system packages

Start by updating your package manager to ensure you get the latest versions of chrony and security tools.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install chrony NTP service

Install chrony, which provides better performance and security than traditional ntpd for most use cases.

sudo apt install -y chrony ntpstat
sudo dnf install -y chrony ntpstat

Stop and remove conflicting NTP services

Remove any existing NTP services that might conflict with chrony's operation.

sudo systemctl stop ntp ntpd systemd-timesyncd
sudo systemctl disable ntp ntpd systemd-timesyncd
sudo apt remove -y ntp ntpdate
sudo systemctl stop ntp ntpd
sudo systemctl disable ntp ntpd
sudo dnf remove -y ntp ntpdate

Configure chrony main settings

Create a secure chrony configuration with reliable upstream servers and proper security settings.

# Use reliable NTP pool servers
pool 0.pool.ntp.org iburst maxsources 4
pool 1.pool.ntp.org iburst maxsources 3
pool 2.pool.ntp.org iburst maxsources 3
pool 3.pool.ntp.org iburst maxsources 3

Add public servers for redundancy

server time.cloudflare.com iburst server time.google.com iburst

Security settings

port 0 cmdport 323 bindcmdaddress 127.0.0.1 bindcmdaddress ::1

Serve time to local network (adjust subnet as needed)

allow 192.168.1.0/24 allow 10.0.0.0/8 allow 172.16.0.0/12

Rate limiting

ratelimit interval 1 burst 16 ratelimit ntp interval 1 burst 8

Logging and monitoring

log tracking measurements statistics logdir /var/log/chrony

Drift file and keys

driftfile /var/lib/chrony/chrony.drift keyfile /etc/chrony/chrony.keys

System clock settings

makestep 1.0 3 rtcsync

Leap second handling

leapsectz right/UTC

Local stratum when disconnected (disable for production)

local stratum 10

Hardware timestamping if supported

hwtimestamp eth0

Create chrony keys file for security

Generate authentication keys for secure command access to chrony daemon.

sudo touch /etc/chrony/chrony.keys
sudo chmod 640 /etc/chrony/chrony.keys
sudo chown root:chrony /etc/chrony/chrony.keys

Add authentication keys to the file:

1 SHA1 HEX:$(openssl rand -hex 20)
2 SHA256 HEX:$(openssl rand -hex 32)

Create chrony log directory

Set up proper logging directory with correct permissions for chrony user.

sudo mkdir -p /var/log/chrony
sudo chown chrony:chrony /var/log/chrony
sudo chmod 755 /var/log/chrony
Note: Never use chmod 777 for log directories. The chrony user only needs write access to its own log directory, and administrators need read access for monitoring.

Configure firewall rules for NTP

Open the NTP port (123/UDP) with rate limiting to prevent abuse while allowing legitimate clients.

sudo ufw allow from 192.168.1.0/24 to any port 123 proto udp
sudo ufw allow from 10.0.0.0/8 to any port 123 proto udp
sudo ufw allow from 172.16.0.0/12 to any port 123 proto udp
sudo ufw limit 123/udp
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" port protocol="udp" port="123" accept'
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" port protocol="udp" port="123" accept'
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="172.16.0.0/12" port protocol="udp" port="123" accept'
sudo firewall-cmd --reload

Configure systemd service limits

Create systemd override to limit resource usage and improve security.

sudo systemctl edit chrony
[Service]

Security hardening

NoNewPrivileges=yes PrivateTmp=yes ProtectHome=yes ProtectSystem=strict ReadWritePaths=/var/lib/chrony /var/log/chrony /run/chrony

Resource limits

LimitNOFILE=1024 LimitNPROC=64

Network restrictions

RestrictAddressFamilies=AF_INET AF_INET6

Restart policy

Restart=always RestartSec=10

Enable and start chrony service

Start chrony and enable it to start automatically on boot.

sudo systemctl daemon-reload
sudo systemctl enable chrony
sudo systemctl start chrony
sudo systemctl status chrony

Configure log rotation

Set up log rotation to prevent chrony logs from consuming excessive disk space.

/var/log/chrony/*.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    create 644 chrony chrony
    postrotate
        systemctl reload chrony > /dev/null 2>&1 || true
    endscript
}

Set up monitoring and alerting

Create a monitoring script to check NTP synchronization status and alert on issues.

#!/bin/bash

NTP monitoring script

MAX_OFFSET=0.1 # Maximum acceptable time offset in seconds LOG_FILE="/var/log/chrony/ntp-monitor.log"

Check chrony sync status

if ! systemctl is-active --quiet chrony; then echo "$(date): CRITICAL - chrony service is not running" >> "$LOG_FILE" exit 2 fi

Get time synchronization status

OFFSET=$(chronyc tracking | grep "System time" | awk '{print $4}' | tr -d ' seconds') STRATUM=$(chronyc tracking | grep "Stratum" | awk '{print $3}') SOURCES=$(chronyc sources | grep -c "^\^\*") if [ "$SOURCES" -eq 0 ]; then echo "$(date): WARNING - No synchronized time sources" >> "$LOG_FILE" fi if [ "$STRATUM" -gt 5 ]; then echo "$(date): WARNING - High stratum level: $STRATUM" >> "$LOG_FILE" fi echo "$(date): OK - Offset: ${OFFSET}s, Stratum: $STRATUM, Sources: $SOURCES" >> "$LOG_FILE"
sudo chmod 755 /usr/local/bin/check-ntp-sync.sh
sudo chown root:root /usr/local/bin/check-ntp-sync.sh

Set up automated monitoring with cron

Schedule regular monitoring checks to detect synchronization issues early.

sudo crontab -e
# Check NTP synchronization every 5 minutes
/5    * /usr/local/bin/check-ntp-sync.sh

Daily NTP statistics report

0 6 * chronyc tracking >> /var/log/chrony/daily-stats.log

Configure client access controls

Set up subnet-based access control

Configure specific network access controls and rate limiting to prevent abuse.

# Allow specific subnets with different rate limits
allow 192.168.1.0/24
allow 10.0.0.0/8
allow 172.16.0.0/12

Deny all other access explicitly

deny all

Rate limiting per client

ratelimit interval 2 burst 16 ratelimit ntp interval 1 burst 8

Client logging

clientloglimit 100000

Configure NTS (Network Time Security)

Enable NTS for encrypted and authenticated time synchronization where supported.

# NTS server configuration
ntsservercert /etc/ssl/certs/ntp-server.crt
ntsserverkey /etc/ssl/private/ntp-server.key
ntsport 4460
ntsprocesses 4

NTS client configuration for upstream servers

server time.cloudflare.com nts

Performance tuning and optimization

Optimize chrony for high-load environments

Configure chrony for better performance under heavy client loads.

# Performance optimizations
schedrr 1
lock_all
noclientlog

Reduce poll intervals for better accuracy

minpoll 4 maxpoll 10

Hardware timestamping if network card supports it

hwtimestamp eth0

hwtimestamp *

Configure kernel-level time synchronization

Enable kernel PLL synchronization for better system clock stability.

# Kernel time synchronization parameters
kernel.time_adj=0
kernel.time_freq=0
kernel.time_maxerror=16000000
kernel.time_esterror=16000000
kernel.time_status=64

Network buffer sizes for NTP

net.core.rmem_max=134217728 net.core.wmem_max=134217728
sudo sysctl -p /etc/sysctl.d/99-ntp-tuning.conf

Verify your setup

Check that chrony is running correctly and serving time to clients.

sudo systemctl status chrony
chronyc tracking
chronyc sources -v
chronyc sourcestats
chronyc clients
ntpstat

Test time synchronization from a client machine:

chronyc -h 203.0.113.10 tracking
ntpdate -q 203.0.113.10

Check firewall rules and network connectivity:

sudo netstat -ulnp | grep :123
sudo ss -ulnp | grep :123
chronyc activity

Common issues

SymptomCauseFix
Clients can't connectFirewall blocking port 123Check firewall rules: sudo ufw status or sudo firewall-cmd --list-all
Large time offsetSystem clock drift or bad upstream sourcesCheck sources: chronyc sources -v and verify connectivity
High stratum levelUpstream servers unreachableTest connectivity: chronyc sourcestats and check network
Permission denied logsIncorrect log directory ownershipFix ownership: sudo chown -R chrony:chrony /var/log/chrony
Service fails to startConfiguration syntax errorCheck config: sudo chronyd -Q for syntax validation
No client connectionsAllow rules too restrictiveReview allow/deny rules in chrony.conf and adjust subnets
Rate limiting too aggressiveBurst limits too lowIncrease burst values in ratelimit configuration
Time jumps frequentlyMakestep threshold too lowAdjust makestep parameters or use smoothing

Next steps

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle private cloud infrastructure for businesses that depend on uptime. From initial setup to ongoing operations.