Set up Network Time Security (NTS) with chrony to provide cryptographically authenticated and encrypted time synchronization, protecting against time-based attacks and ensuring secure clock synchronization across your infrastructure.
Prerequisites
- Root or sudo access
- Network connectivity on ports 123/udp and 4460/tcp
- Basic understanding of time synchronization concepts
What this solves
Network Time Security (NTS) provides cryptographically authenticated and encrypted time synchronization, protecting your servers from time-based attacks and manipulation. Traditional NTP lacks authentication, making it vulnerable to spoofing attacks that can disrupt security protocols, certificate validation, and distributed systems that rely on synchronized clocks.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you have the latest versions available.
sudo apt update && sudo apt upgrade -y
Install chrony with NTS support
Install chrony which includes NTS support in modern versions. We also install gnutls-utils for certificate management.
sudo apt install -y chrony gnutls-bin
Stop chrony service
Stop the chrony service before making configuration changes to prevent conflicts.
sudo systemctl stop chrony
Create NTS keys directory
Create a directory for NTS keys and certificates with proper permissions for the chrony user.
sudo mkdir -p /var/lib/chrony/nts
sudo chown chrony:chrony /var/lib/chrony/nts
sudo chmod 750 /var/lib/chrony/nts
Configure chrony NTS client
Configure chrony to use NTS-enabled time servers. This configuration uses CloudFlare's NTS servers as the primary source.
# NTS-enabled time servers
server time.cloudflare.com iburst nts
server nts.netnod.se iburst nts
server ptbtime1.ptb.de iburst nts
server nts.time.nl iburst nts
Fallback to standard NTP if NTS fails
pool 2.pool.ntp.org iburst
Record the rate at which the system clock gains/loses time
driftfile /var/lib/chrony/drift
Allow the system clock to be stepped in the first three updates
makestep 1.0 3
Enable kernel synchronization of the real-time clock (RTC)
rtcsync
NTS key and certificate storage
ntsservercert /var/lib/chrony/nts/server.crt
ntsserverkey /var/lib/chrony/nts/server.key
ntsdumpdir /var/lib/chrony/nts
Enhanced logging
log tracking measurements statistics
logdir /var/log/chrony
Security settings
user chrony
lock_all
Allow only specific subnets to query time (if acting as server)
allow 192.168.0.0/16
allow 10.0.0.0/8
allow 172.16.0.0/12
Generate NTS server certificates
If you want to provide NTS service to other systems, generate certificates for the NTS server component.
sudo openssl req -x509 -newkey rsa:4096 -keyout /var/lib/chrony/nts/server.key -out /var/lib/chrony/nts/server.crt -days 365 -nodes -subj "/C=US/ST=State/L=City/O=Organization/CN=$(hostname -f)"
sudo chown chrony:chrony /var/lib/chrony/nts/server.key /var/lib/chrony/nts/server.crt
sudo chmod 600 /var/lib/chrony/nts/server.key
sudo chmod 644 /var/lib/chrony/nts/server.crt
Configure NTS server settings
Add NTS server configuration to allow other systems to sync with authenticated time. This enables port 4460 for NTS-KE (Key Exchange).
# Add these lines to existing configuration for NTS server functionality
ntsport 4460
ntstrustedcerts /var/lib/chrony/nts
ntsprocesses 4
ntsrotate 86400
Configure firewall for NTS
Open the necessary ports for NTS operation. Port 123 is for NTP, and port 4460 is for NTS-KE.
sudo ufw allow 123/udp comment 'NTP'
sudo ufw allow 4460/tcp comment 'NTS-KE'
sudo ufw reload
Create chrony log directory
Ensure the log directory exists and has correct permissions for chrony to write log files.
sudo mkdir -p /var/log/chrony
sudo chown chrony:chrony /var/log/chrony
sudo chmod 755 /var/log/chrony
Start and enable chrony
Start the chrony service and enable it to start automatically on boot.
sudo systemctl enable --now chrony
sudo systemctl status chrony
Configure NTS client authentication
Verify NTS authentication
Check that NTS authentication is working by examining the chrony sources and their authentication status.
sudo chronyc sources -v
sudo chronyc authdata
Configure NTS certificate validation
Set up certificate validation parameters for enhanced security. This ensures certificates are properly verified.
# Add certificate validation settings
nocerttimecheck 1
ntscachedir /var/lib/chrony/nts
ntstimeout 10
Restart chrony with new configuration
Restart chrony to apply the certificate validation settings.
sudo systemctl restart chrony
Set up NTS server with certificate management
Configure automatic certificate renewal
Create a script to automatically renew NTS certificates before they expire.
#!/bin/bash
NTS Certificate Renewal Script
CERT_DIR="/var/lib/chrony/nts"
CERT_FILE="$CERT_DIR/server.crt"
KEY_FILE="$CERT_DIR/server.key"
HOSTNAME=$(hostname -f)
Check if certificate expires within 30 days
if openssl x509 -checkend 2592000 -noout -in "$CERT_FILE" >/dev/null 2>&1; then
echo "Certificate is still valid for more than 30 days"
exit 0
fi
echo "Certificate expires within 30 days, renewing..."
Generate new certificate
openssl req -x509 -newkey rsa:4096 -keyout "$KEY_FILE.new" -out "$CERT_FILE.new" -days 365 -nodes -subj "/C=US/ST=State/L=City/O=Organization/CN=$HOSTNAME"
Set proper permissions
chown chrony:chrony "$KEY_FILE.new" "$CERT_FILE.new"
chmod 600 "$KEY_FILE.new"
chmod 644 "$CERT_FILE.new"
Replace old certificates
mv "$KEY_FILE.new" "$KEY_FILE"
mv "$CERT_FILE.new" "$CERT_FILE"
Restart chrony to use new certificate
systemctl restart chrony
echo "Certificate renewed successfully"
Make renewal script executable
Set proper permissions on the certificate renewal script and test it.
sudo chmod +x /usr/local/bin/renew-nts-cert.sh
sudo /usr/local/bin/renew-nts-cert.sh
Schedule automatic certificate renewal
Create a cron job to run the certificate renewal script weekly.
sudo crontab -e
Add this line to run the renewal check every Sunday at 2 AM:
0 2 0 /usr/local/bin/renew-nts-cert.sh >> /var/log/chrony/cert-renewal.log 2>&1
Monitor and troubleshoot NTS connections
Create NTS monitoring script
Create a monitoring script to check NTS authentication status and log any issues.
#!/bin/bash
NTS Monitoring Script
LOGFILE="/var/log/chrony/nts-monitor.log"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$TIMESTAMP] Starting NTS monitoring check" >> "$LOGFILE"
Check chrony status
if ! systemctl is-active --quiet chrony; then
echo "[$TIMESTAMP] ERROR: chrony service is not running" >> "$LOGFILE"
exit 1
fi
Check NTS authentication
NTS_SOURCES=$(chronyc sources -v | grep -c 'nts')
if [ "$NTS_SOURCES" -eq 0 ]; then
echo "[$TIMESTAMP] WARNING: No NTS sources found" >> "$LOGFILE"
else
echo "[$TIMESTAMP] INFO: Found $NTS_SOURCES NTS sources" >> "$LOGFILE"
fi
Check authentication data
AUTH_DATA=$(chronyc authdata 2>/dev/null)
if [ $? -eq 0 ]; then
echo "[$TIMESTAMP] INFO: NTS authentication data available" >> "$LOGFILE"
else
echo "[$TIMESTAMP] WARNING: Could not retrieve NTS authentication data" >> "$LOGFILE"
fi
Check time synchronization
TRACKING=$(chronyc tracking)
STRATUM=$(echo "$TRACKING" | grep 'Stratum' | awk '{print $3}')
if [ "$STRATUM" -le 4 ]; then
echo "[$TIMESTAMP] INFO: Time synchronized (stratum $STRATUM)" >> "$LOGFILE"
else
echo "[$TIMESTAMP] WARNING: Poor time synchronization (stratum $STRATUM)" >> "$LOGFILE"
fi
echo "[$TIMESTAMP] NTS monitoring check completed" >> "$LOGFILE"
Set up NTS monitoring cron job
Make the monitoring script executable and schedule it to run every 15 minutes.
sudo chmod +x /usr/local/bin/nts-monitor.sh
sudo crontab -e
Add this line for monitoring every 15 minutes:
/15 * /usr/local/bin/nts-monitor.sh
Configure log rotation for NTS logs
Set up log rotation to prevent NTS log files from growing too large.
/var/log/chrony/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 644 chrony chrony
postrotate
systemctl reload chrony > /dev/null 2>&1 || true
endrotate
}
Verify your setup
Confirm that NTS is working correctly and time synchronization is secure.
sudo chronyc sources -v
sudo chronyc tracking
sudo chronyc authdata
sudo chronyc ntpdata
sudo systemctl status chrony
Check for NTS-specific information in the sources output:
sudo chronyc sources -v | grep -i nts
View recent log entries to ensure NTS authentication is working:
sudo tail -f /var/log/chrony/tracking.log
sudo journalctl -u chrony -f
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| No NTS sources visible | NTS servers unreachable or certificates invalid | Check firewall rules and verify NTS server availability with telnet time.cloudflare.com 4460 |
| Certificate validation errors | System clock too far off or CA certificates outdated | Manually sync time first with sudo ntpdate -s time.nist.gov then restart chrony |
| chrony fails to start | Permission errors on NTS directory | Fix ownership with sudo chown -R chrony:chrony /var/lib/chrony/nts |
| NTS authentication timeout | Firewall blocking port 4460 | Verify port 4460/tcp is open and accessible |
| High stratum number | NTS servers not responding | Add fallback NTP servers and check network connectivity |
Next steps
- Configure NTP monitoring with Grafana dashboards and Prometheus alerting
- Implement Kubernetes network policies for pod-to-pod security and traffic isolation
- Set up NTS time synchronization monitoring with Prometheus alerts
- Implement NTS certificate automation with Let's Encrypt
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Configuration
CHRONY_USER="chrony"
NTS_DIR="/var/lib/chrony/nts"
LOG_DIR="/var/log/chrony"
CERT_DAYS=365
# Function to print colored output
print_status() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Cleanup function for rollback
cleanup() {
if [ $? -ne 0 ]; then
print_error "Installation failed. Rolling back changes..."
systemctl stop chrony 2>/dev/null || true
systemctl disable chrony 2>/dev/null || true
rm -f /etc/chrony/chrony.conf.backup 2>/dev/null || true
rm -f /etc/chrony.conf.backup 2>/dev/null || true
fi
}
trap cleanup ERR
# Usage function
usage() {
echo "Usage: $0 [OPTIONS]"
echo "Install and configure Network Time Security (NTS) with chrony"
echo ""
echo "Options:"
echo " --server-mode Configure as NTS server (default: client only)"
echo " --hostname FQDN Override hostname for certificate generation"
echo " --help Show this help message"
exit 1
}
# Parse arguments
SERVER_MODE=false
HOSTNAME=$(hostname -f)
while [[ $# -gt 0 ]]; do
case $1 in
--server-mode)
SERVER_MODE=true
shift
;;
--hostname)
HOSTNAME="$2"
shift 2
;;
--help)
usage
;;
*)
print_error "Unknown option: $1"
usage
;;
esac
done
# Check if running as root
if [[ $EUID -ne 0 ]]; then
print_error "This script must be run as root or with sudo"
exit 1
fi
# Detect distribution and package manager
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
CHRONY_CONF="/etc/chrony/chrony.conf"
GNUTLS_PKG="gnutls-bin"
FIREWALL_CMD="ufw"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
CHRONY_CONF="/etc/chrony.conf"
GNUTLS_PKG="gnutls-utils"
FIREWALL_CMD="firewall-cmd"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
CHRONY_CONF="/etc/chrony.conf"
GNUTLS_PKG="gnutls-utils"
FIREWALL_CMD="firewall-cmd"
;;
*)
print_error "Unsupported distribution: $ID"
exit 1
;;
esac
else
print_error "Cannot detect distribution. /etc/os-release not found."
exit 1
fi
print_status "Installing NTS-enabled chrony on $PRETTY_NAME"
# Step 1: Update system packages
echo "[1/8] Updating system packages..."
$PKG_UPDATE
# Step 2: Install chrony and dependencies
echo "[2/8] Installing chrony with NTS support..."
$PKG_INSTALL chrony $GNUTLS_PKG openssl
# Step 3: Stop chrony service
echo "[3/8] Stopping chrony service..."
systemctl stop chrony
# Step 4: Create NTS directories
echo "[4/8] Creating NTS directories..."
mkdir -p "$NTS_DIR"
mkdir -p "$LOG_DIR"
chown $CHRONY_USER:$CHRONY_USER "$NTS_DIR"
chown $CHRONY_USER:$CHRONY_USER "$LOG_DIR"
chmod 750 "$NTS_DIR"
chmod 755 "$LOG_DIR"
# Step 5: Backup existing configuration
echo "[5/8] Configuring chrony..."
if [ -f "$CHRONY_CONF" ]; then
cp "$CHRONY_CONF" "${CHRONY_CONF}.backup"
fi
# Create new chrony configuration
cat > "$CHRONY_CONF" << EOF
# NTS-enabled time servers
server time.cloudflare.com iburst nts
server nts.netnod.se iburst nts
server ptbtime1.ptb.de iburst nts
server nts.time.nl iburst nts
# Fallback to standard NTP if NTS fails
pool 2.pool.ntp.org iburst
# Record the rate at which the system clock gains/loses time
driftfile /var/lib/chrony/drift
# Allow the system clock to be stepped in the first three updates
makestep 1.0 3
# Enable kernel synchronization of the real-time clock (RTC)
rtcsync
# NTS key and certificate storage
ntsdumpdir $NTS_DIR
# Enhanced logging
log tracking measurements statistics
logdir $LOG_DIR
# Security settings
user $CHRONY_USER
lock_all
EOF
# Add server configuration if requested
if [ "$SERVER_MODE" = true ]; then
echo "[6/8] Generating NTS server certificates..."
openssl req -x509 -newkey rsa:4096 -keyout "$NTS_DIR/server.key" -out "$NTS_DIR/server.crt" \
-days $CERT_DAYS -nodes -subj "/C=US/ST=State/L=City/O=Organization/CN=$HOSTNAME"
chown $CHRONY_USER:$CHRONY_USER "$NTS_DIR/server.key" "$NTS_DIR/server.crt"
chmod 600 "$NTS_DIR/server.key"
chmod 644 "$NTS_DIR/server.crt"
# Add server configuration to chrony.conf
cat >> "$CHRONY_CONF" << EOF
# NTS server configuration
ntsservercert $NTS_DIR/server.crt
ntsserverkey $NTS_DIR/server.key
ntsport 4460
ntstrustedcerts $NTS_DIR
ntsprocesses 4
ntsrotate 86400
# Allow specific subnets to query time
allow 192.168.0.0/16
allow 10.0.0.0/8
allow 172.16.0.0/12
EOF
else
echo "[6/8] Skipping server certificate generation (client mode)..."
fi
# Step 7: Configure firewall
echo "[7/8] Configuring firewall..."
if [ "$FIREWALL_CMD" = "ufw" ]; then
if command -v ufw >/dev/null 2>&1; then
ufw allow 123/udp comment 'NTP' || true
if [ "$SERVER_MODE" = true ]; then
ufw allow 4460/tcp comment 'NTS-KE' || true
fi
ufw reload || true
fi
elif [ "$FIREWALL_CMD" = "firewall-cmd" ]; then
if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld; then
firewall-cmd --permanent --add-port=123/udp || true
if [ "$SERVER_MODE" = true ]; then
firewall-cmd --permanent --add-port=4460/tcp || true
fi
firewall-cmd --reload || true
fi
fi
# Step 8: Start and enable chrony
echo "[8/8] Starting chrony service..."
systemctl enable chrony
systemctl start chrony
# Wait a moment for service to start
sleep 3
# Verification
print_status "Verifying NTS configuration..."
if systemctl is-active --quiet chrony; then
print_status "✓ Chrony service is running"
else
print_error "✗ Chrony service failed to start"
exit 1
fi
# Check NTS sources
sleep 5
if chrony sources 2>/dev/null | grep -q "^[\*\+].*NTS"; then
print_status "✓ NTS sources are active"
elif chrony sources 2>/dev/null | grep -q "NTS"; then
print_warning "NTS sources configured but not yet synchronized (this may take a few minutes)"
else
print_warning "NTS sources not detected in output"
fi
# Display status
print_status "NTS installation completed successfully!"
print_status "Configuration file: $CHRONY_CONF"
print_status "NTS directory: $NTS_DIR"
print_status "Log directory: $LOG_DIR"
if [ "$SERVER_MODE" = true ]; then
print_status "Server mode enabled - NTS-KE available on port 4460"
fi
print_status "Use 'chronyc sources -v' to check time sources"
print_status "Use 'chronyc authdata' to verify NTS authentication"
Review the script before running. Execute with: bash install.sh