Skip to content

Instantly share code, notes, and snippets.

@troykelly
Last active January 19, 2025 02:57
Show Gist options
  • Save troykelly/ee74b1d9f08739269824ebdd484e362c to your computer and use it in GitHub Desktop.
Save troykelly/ee74b1d9f08739269824ebdd484e362c to your computer and use it in GitHub Desktop.

README for configure_ntp.sh

Author: Troy Kelly


Table of Contents

  1. Introduction
  2. Script Overview
  3. System Requirements
  4. Quick Installation
  5. Usage
  6. Configuration Details
  7. Troubleshooting
  8. Author and Contributing

Introduction

This repository contains the script "configure_ntp.sh", designed to check and configure Network Time Protocol (NTP) settings on Debian-based systems. The script ensures that the correct NTP packages are installed, updates your NTP configuration if desired, restarts NTP/NTPSec to apply any configuration changes, and (if configured to do so) verifies that the server is correctly synchronising.

Key features:
• Supports specifying multiple servers using "--server".
• Supports specifying multiple pools using "--pool".
• Comments out any existing server/pool lines that were not specified by the user.
• Preserves all other (non-server/pool) configuration, ensuring minimal impact on your existing NTP setup.
• Automatically detects if you are using ntpsec (/etc/ntpsec/ntp.conf) or classical ntp (/etc/ntp.conf).


Script Overview

The script will:

  1. Verify that it is run with root privileges.
  2. Check that the operating system is Debian-based (e.g., Debian or Ubuntu).
  3. Install the required NTP package (deb package named "ntp").
  4. Detect your current NTP configuration file ("/etc/ntp.conf" or "/etc/ntpsec/ntp.conf").
  5. If one or more servers and/or pools are supplied via the command line:
    • Backup your current configuration file.
    • Comment out any lines referencing server/pool hosts that are not in your supplied lists.
    • Add any missing server/pool lines that you specify but are not present.
  6. Restart the NTP (or NTPSec) service if the configuration changed.
  7. Optionally verify synchronisation (only if configuring one or more servers/pools).
  8. If no servers/pools are provided, the script will only ensure that ntp is installed and loaded, and will warn that no configuration updates have occurred.

System Requirements

• A Debian-based system (e.g., Debian, Ubuntu).
• Root or sudo privileges to install packages and modify system configuration.
• Internet connectivity for contacting your selected NTP servers or pools.


Quick Installation

Below are quick methods to install and run "configure_ntp.sh". All examples must be executed as root or with "sudo".

Install via Direct Download

  1. Download the script directly:
    wget https://gist.githubusercontent.com/troykelly/ee74b1d9f08739269824ebdd484e362c/raw/configure_ntp.sh -O configure_ntp.sh
    chmod +x configure_ntp.sh
  2. Run the script (including any additional parameters if you have specific servers/pools):
    sudo ./configure_ntp.sh --server 0.pool.ntp.org --pool 1.pool.ntp.org

Install via curl Piping

This method downloads the script and executes it immediately:

curl -sS https://gist.githubusercontent.com/troykelly/ee74b1d9f08739269824ebdd484e362c/raw/configure_ntp.sh | sudo bash -s -- --server 0.pool.ntp.org

Note: Always review scripts before piping them directly to bash in a production environment.


Usage

Once installed and run with the appropriate parameters, NTP will attempt to maintain system time accuracy. The script:

• Installs and ensures NTP or NTPSec is running.
• Modifies your config to exclusively use the servers and/or pools you specify (if any).
• Verifies that at least one server/pool you specified is synchronising (if you specify any).

For built-in help (usage message), run:

sudo bash configure_ntp.sh --help

Configuration Details

  1. Installing Packages
    The script installs the "ntp" package if it is not already present. If you are using "ntpsec", ensure that "ntpsec" is already installed and configured on your system; the script will detect and manage its config file as needed.

  2. Servers and Pools
    • Use the "--server" option for explicitly-defined servers, e.g.:
    "--server time.example.com"
    • Use the "--pool" option for pool-based references, e.g.:
    "--pool 0.pool.ntp.org"
    • If you specify servers but not pools, the script comments out all pool lines.
    • If you specify pools but not servers, the script comments out all server lines.
    • If you specify both pools and servers, the script only preserves those (pool or server) lines you provided.

  3. No Provided Parameters
    If you run the script with no "--server" or "--pool" parameters, it will install the NTP package if necessary, but perform no changes to any config file, and warn that no configuration was updated.

  4. Service Detection and Restart
    The script attempts to detect whether you are using "ntp" ("ntp.service") or "ntpsec" ("ntpsec.service") by checking whether "/etc/ntpsec/ntp.conf" or "/etc/ntp.conf" exists. After changing the config file, it restarts the detected service to ensure changes take effect.

  5. Synchronisation Check
    For each provided server or pool, the script will attempt to verify, via "ntpq -p -n", that the system is receiving synchronisation from at least one of your listed nodes. DNS-based aliases and CNAMEs are handled via IP resolution.


Troubleshooting

• Script Must Be Run as Root
If you see “[ERROR] This script must be run as root” or a permissions error, prepend "sudo" or switch to the root user.

• Non-Debian System
If running on a system without "/etc/debian_version", the script will exit with an error indicating that it is Debian-only.

• Unreachable Servers or Pools
If your servers/pools are unreachable, synchronisation check may fail. Confirm your network configuration and firewall rules.

• Checking Logs and Sync
To investigate issues in detail, review "ntpq -p -n" output and examine logs found in "/var/log/syslog" or "/var/log/ntpsec" (depending on your distribution).


Author and Contributing

• Script Author: Troy Kelly
• Email: [email protected]


Stay synchronised and keep accurate time! If you have any questions, contact [email protected].

#!/usr/bin/env bash
#
# configure_ntp.sh
#
# Description:
# Checks and configures NTP settings on a Debian-based system. Installs
# required packages (ntp), updates the system’s active NTP configuration file
# (which may be /etc/ntp.conf or /etc/ntpsec/ntp.conf) based on user-supplied
# --server or --pool arguments, restarts the relevant service if needed, and
# verifies synchronisation. If no servers or pools are specified, the script
# only ensures NTP is installed and active, with no config changes performed.
#
# Usage:
# curl -sS https://example.invalid/configure_ntp.sh | sudo bash
# (Ensure you run as root or with sudo.)
#
# Examples:
# sudo bash configure_ntp.sh --server time.example.org --pool 0.pool.ntp.org
# sudo bash configure_ntp.sh # (No config changes; only ensures ntp installed)
set -Eeuo pipefail
###############################################################################
# Globals
###############################################################################
readonly SCRIPT_NAME="${0##*/}"
# Variables to track the actual config file and service name in use.
# Some systems (ntpsec) use /etc/ntpsec/ntp.conf with the 'ntpsec' service.
# Others use /etc/ntp.conf with the 'ntp' service.
NTP_CONFIG_FILE=""
NTP_SERVICE="ntp"
# Arrays of user-supplied servers/pools
declare -a NTP_SERVERS=()
declare -a NTP_POOLS=()
# Associative array: SERVER_IPS will map a server/pool name to a space-separated
# list of resolved IP addresses (either IPv4 or IPv6).
declare -A SERVER_IPS=()
# Trap function to catch errors and unexpected exits.
trap 'catchError $?' ERR
###############################################################################
# Functions
###############################################################################
# -----------------------------------------------------------------------------
# catchError: Print an error message if the script encounters an unexpected exit.
#
# Args:
# $1 (int): The exit code of the command that triggered this function.
# -----------------------------------------------------------------------------
catchError() {
local exit_code="$1"
echo "[ERROR] The script '${SCRIPT_NAME}' exited with code ${exit_code}."
echo " Check the output above for details."
exit "${exit_code}"
}
# -----------------------------------------------------------------------------
# checkRoot: Verify that the script is run as root (or via sudo).
# -----------------------------------------------------------------------------
checkRoot() {
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
echo "[ERROR] This script must be run as root or with sudo privileges."
exit 1
fi
}
# -----------------------------------------------------------------------------
# checkDebian: Verify we're running on a Debian-based system.
# -----------------------------------------------------------------------------
checkDebian() {
if [[ ! -f "/etc/debian_version" ]]; then
echo "[ERROR] This script is intended for Debian-based systems only."
exit 1
fi
}
# -----------------------------------------------------------------------------
# parseArgs: Parse command line arguments.
# Multiple NTP servers can be provided with repeated --server options.
# Multiple NTP pools can be provided with repeated --pool options.
# -----------------------------------------------------------------------------
parseArgs() {
while [[ "$#" -gt 0 ]]; do
case "$1" in
--server)
if [[ -z "${2:-}" ]]; then
echo "[ERROR] --server requires an argument (e.g. time.example.org)."
exit 1
fi
NTP_SERVERS+=("$2")
shift 2
;;
--pool)
if [[ -z "${2:-}" ]]; then
echo "[ERROR] --pool requires an argument (e.g. 0.pool.ntp.org)."
exit 1
fi
NTP_POOLS+=("$2")
shift 2
;;
-h|--help)
showHelp
exit 0
;;
*)
echo "[ERROR] Unknown option: $1"
echo "Use --help for usage details."
exit 1
;;
esac
done
}
# -----------------------------------------------------------------------------
# showHelp: Display usage information for the script.
# -----------------------------------------------------------------------------
showHelp() {
cat <<EOF
Usage: ${SCRIPT_NAME} [--server <server>] [--pool <pool>] ...
[--server <server2>] [--pool <pool2>] ...
or
${SCRIPT_NAME} --help
Options:
--server <server> Specify an NTP server. Repeat the option to add multiple servers.
--pool <pool> Specify a pool. Repeat the option to add multiple pools.
--help Display this help message.
If no --server or --pool parameters are provided, the script will install
ntp if necessary, detect which config (if any) is in use, but make no changes,
and warn that no config update was performed.
Examples:
sudo ./${SCRIPT_NAME} --server time.example.org --pool 0.pool.ntp.org
sudo ./${SCRIPT_NAME} --pool 1.pool.ntp.org --pool 2.pool.ntp.org
sudo ./${SCRIPT_NAME} --server time.internal.net
sudo ./${SCRIPT_NAME}
EOF
}
# -----------------------------------------------------------------------------
# detectNtpConfFile: Determine which config file path and service name to use:
# - /etc/ntpsec/ntp.conf + "ntpsec" service if found
# - /etc/ntp.conf + "ntp" service if found
# - If none found, default to /etc/ntp.conf with "ntp" service
# -----------------------------------------------------------------------------
detectNtpConfFile() {
if [[ -f "/etc/ntpsec/ntp.conf" ]]; then
NTP_CONFIG_FILE="/etc/ntpsec/ntp.conf"
NTP_SERVICE="ntpsec"
echo "[INFO] Detected ntpsec config at /etc/ntpsec/ntp.conf. Using 'ntpsec' service."
elif [[ -f "/etc/ntp.conf" ]]; then
NTP_CONFIG_FILE="/etc/ntp.conf"
NTP_SERVICE="ntp"
echo "[INFO] Detected ntp config at /etc/ntp.conf. Using 'ntp' service."
else
NTP_CONFIG_FILE="/etc/ntp.conf"
NTP_SERVICE="ntp"
echo "[INFO] No existing NTP config file found. Defaulting to /etc/ntp.conf with 'ntp' service."
fi
}
# -----------------------------------------------------------------------------
# installNtpPackage: Installs the ntp package if not already installed.
# (If the system uses ntpsec, user is expected to have it installed.)
# -----------------------------------------------------------------------------
installNtpPackage() {
echo "[INFO] Checking and installing required packages (ntp)..."
apt-get update -y
DEBIAN_FRONTEND=noninteractive apt-get install -y ntp
echo "[INFO] Package installation completed (or already present)."
}
# -----------------------------------------------------------------------------
# backupNtpConfig: Make a backup of the existing config file if it exists.
# -----------------------------------------------------------------------------
backupNtpConfig() {
if [[ -f "${NTP_CONFIG_FILE}" ]]; then
local timestamp
timestamp="$(date +%Y%m%d%H%M%S)"
cp "${NTP_CONFIG_FILE}" "${NTP_CONFIG_FILE}.bak-${timestamp}"
echo "[INFO] Backed up existing NTP config to ${NTP_CONFIG_FILE}.bak-${timestamp}"
fi
}
# -----------------------------------------------------------------------------
# resolveIpsForHost: Gathers all resolvable IP addresses for a given host
# (server or pool), storing them in SERVER_IPS for IP-based sync determination.
#
# Args:
# $1 (string): Hostname of the NTP server or pool to resolve
# -----------------------------------------------------------------------------
resolveIpsForHost() {
local hostparam="$1"
local -a ipList=()
# Attempt to retrieve all addresses via 'getent ahosts'.
while read -r line || [[ -n "$line" ]]; do
local ip
ip="$(awk '{print $1}' <<< "${line}")"
if [[ "${ip}" =~ ^[0-9a-fA-F:\.]+$ ]]; then
ipList+=("${ip}")
fi
done < <(getent ahosts "${hostparam}" 2>/dev/null || true)
# Remove duplicates
if [[ "${#ipList[@]}" -gt 0 ]]; then
mapfile -t ipList < <(printf '%s\n' "${ipList[@]}" | sort -u)
fi
SERVER_IPS["${hostparam}"]="${ipList[*]}"
}
# -----------------------------------------------------------------------------
# gatherAllIpsForHosts: Resolve IPs for each user-provided server and pool so we
# can handle potential aliases or CNAME records in ntpq output.
# -----------------------------------------------------------------------------
gatherAllIpsForHosts() {
for srv in "${NTP_SERVERS[@]}"; do
resolveIpsForHost "${srv}"
if [[ -n "${SERVER_IPS["${srv}"]:-}" ]]; then
echo "[INFO] '${srv}' resolved to IP(s): ${SERVER_IPS["${srv}"]}"
else
echo "[WARN] Unable to resolve any IP addresses for '${srv}'."
fi
done
for pl in "${NTP_POOLS[@]}"; do
resolveIpsForHost "${pl}"
if [[ -n "${SERVER_IPS["${pl}"]:-}" ]]; then
echo "[INFO] '${pl}' resolved to IP(s): ${SERVER_IPS["${pl}"]}"
else
echo "[WARN] Unable to resolve any IP addresses for '${pl}'."
fi
done
}
# -----------------------------------------------------------------------------
# updateExistingNtpConfig: Takes an existing config file and updates it so:
# 1. "server" lines are preserved only if they match user-supplied servers (if any).
# If no servers are provided, all "server" lines are commented out.
# 2. "pool" lines are preserved only if they match user-supplied pools (if any).
# If no pools are provided, all "pool" lines are commented out.
# 3. User-supplied servers/pools missing from the file are added (with iburst).
# -----------------------------------------------------------------------------
updateExistingNtpConfig() {
echo "[INFO] Updating existing NTP configuration in ${NTP_CONFIG_FILE}..."
local -a newLines=()
local -a foundServers=()
local -a foundPools=()
local line lineType lineHost
while IFS='' read -r line || [[ -n "$line" ]]; do
# We look for lines beginning with either "server" or "pool"
# capturing the type in group1 and the host in group2:
# ^[[:space:]]*(server|pool)[[:space:]]+([^[:space:]]+)
if [[ "${line}" =~ ^[[:space:]]*(server|pool)[[:space:]]+([^[:space:]]+)(.*)$ ]]; then
lineType="${BASH_REMATCH[1]}" # "server" or "pool"
lineHost="${BASH_REMATCH[2]}" # The hostname argument
local keep=0
if [[ "${lineType}" == "server" ]]; then
if [[ "${#NTP_SERVERS[@]}" -gt 0 ]]; then
# If we have user-supplied servers, keep only if it matches
for s in "${NTP_SERVERS[@]}"; do
if [[ "${s}" == "${lineHost}" ]]; then
keep=1
foundServers+=("${s}")
break
fi
done
else
# We have no user-supplied servers => comment out all server lines
keep=0
fi
elif [[ "${lineType}" == "pool" ]]; then
if [[ "${#NTP_POOLS[@]}" -gt 0 ]]; then
# If we have user-supplied pools, keep only if it matches
for p in "${NTP_POOLS[@]}"; do
if [[ "${p}" == "${lineHost}" ]]; then
keep=1
foundPools+=("${p}")
break
fi
done
else
# We have no user-supplied pools => comment out all pool lines
keep=0
fi
fi
if [[ "${keep}" -eq 1 ]]; then
# Keep unmodified
newLines+=("${line}")
else
# Comment out if not already
if [[ "${line}" =~ ^[[:space:]]*# ]]; then
newLines+=("${line}")
else
newLines+=("# ${line}")
fi
fi
else
# Not a server/pool line, keep as-is
newLines+=("${line}")
fi
done < "${NTP_CONFIG_FILE}"
# Add missing servers if not found
for s in "${NTP_SERVERS[@]}"; do
local wasFound=0
for f in "${foundServers[@]}"; do
if [[ "${s}" == "${f}" ]]; then
wasFound=1
break
fi
done
if [[ "${wasFound}" -eq 0 ]]; then
newLines+=("server ${s} iburst")
echo "[INFO] Added missing server '${s}' to configuration."
fi
done
# Add missing pools if not found
for p in "${NTP_POOLS[@]}"; do
local wasFound=0
for f in "${foundPools[@]}"; do
if [[ "${p}" == "${f}" ]]; then
wasFound=1
break
fi
done
if [[ "${wasFound}" -eq 0 ]]; then
newLines+=("pool ${p} iburst")
echo "[INFO] Added missing pool '${p}' to configuration."
fi
done
# Overwrite the file with the updated lines
printf "%s\n" "${newLines[@]}" > "${NTP_CONFIG_FILE}"
echo "[INFO] Existing NTP configuration has been updated successfully."
}
# -----------------------------------------------------------------------------
# configureNtp: Main logic to preserve existing items, but adjust "server"/"pool"
# lines if user-supplied servers/pools were specified. If the file
# doesn't exist at all, create minimal with user-supplied lines.
# -----------------------------------------------------------------------------
configureNtp() {
echo "[INFO] Configuring NTP..."
local totalCount=$(( ${#NTP_SERVERS[@]} + ${#NTP_POOLS[@]} ))
if [[ "${totalCount}" -eq 0 ]]; then
# No user-supplied servers/pools => we do not modify config
echo "[WARN] No --server or --pool arguments supplied. No config changes performed."
return
fi
# We have at least one server/pool; back up existing config (if present),
# then update or create.
backupNtpConfig
if [[ -f "${NTP_CONFIG_FILE}" ]]; then
updateExistingNtpConfig
else
# File doesn't exist at all, create minimal with user-supplied lines
echo "[INFO] ${NTP_CONFIG_FILE} not found. Creating a new file with base items and user-supplied lines."
cat <<EOF > "${NTP_CONFIG_FILE}"
driftfile /var/lib/ntp/ntp.drift
# Restrict default network for security
restrict -4 default kod notrap nomodify nopeer noquery
restrict -6 default kod notrap nomodify nopeer noquery
EOF
for s in "${NTP_SERVERS[@]}"; do
echo "server ${s} iburst" >> "${NTP_CONFIG_FILE}"
echo "[INFO] Added server '${s}'."
done
for p in "${NTP_POOLS[@]}"; do
echo "pool ${p} iburst" >> "${NTP_CONFIG_FILE}"
echo "[INFO] Added pool '${p}'."
done
echo "[INFO] New ${NTP_CONFIG_FILE} created with user-supplied servers/pools."
fi
}
# -----------------------------------------------------------------------------
# restartNtp: Restart the selected ntp/ntpsec service to apply any changes.
# -----------------------------------------------------------------------------
restartNtp() {
echo "[INFO] Restarting ${NTP_SERVICE} service..."
systemctl restart "${NTP_SERVICE}"
echo "[INFO] ${NTP_SERVICE} service restarted."
}
# -----------------------------------------------------------------------------
# checkNtpSync: Check if the system is synchronised with at least one user-provided
# host. We wait several attempts to allow time for sync. If no user hosts exist,
# we skip this entirely.
# -----------------------------------------------------------------------------
checkNtpSync() {
local totalCount=$(( ${#NTP_SERVERS[@]} + ${#NTP_POOLS[@]} ))
if [[ "${totalCount}" -eq 0 ]]; then
# No user-supplied configuration => skip sync check
return
fi
local attempt
local synced=0
echo "[INFO] Verifying that NTP is able to synchronise..."
for attempt in {1..5}; do
sleep 2
local syncLines
# We use ntpq -p -n, parse lines that start with an asterisk/star or plus sign
syncLines="$(ntpq -p -n 2>/dev/null | grep -E '^[\*\+]' || true)"
if [[ -z "${syncLines}" ]]; then
echo "[INFO] Attempt ${attempt}: No sync detected yet, re-checking shortly..."
continue
fi
# We check IP-based matching for all user-supplied servers/pools
while read -r line; do
local lineIp
lineIp="$(awk '{print $1}' <<< "${line}")"
# Strip leading '*' or '+'
lineIp="${lineIp#\*}"
lineIp="${lineIp#\+}"
# Compare with resolved IP lists
for s in "${NTP_SERVERS[@]}"; do
local resolvedIps="${SERVER_IPS["${s}"]:-}"
if [[ -n "${resolvedIps}" ]] && grep -qw "${lineIp}" <<< "${resolvedIps}"; then
synced=1
break 3
fi
done
for p in "${NTP_POOLS[@]}"; do
local resolvedIps="${SERVER_IPS["${p}"]:-}"
if [[ -n "${resolvedIps}" ]] && grep -qw "${lineIp}" <<< "${resolvedIps}"; then
synced=1
break 3
fi
done
done <<< "${syncLines}"
if [[ "${synced}" -eq 1 ]]; then
break
else
echo "[INFO] Attempt ${attempt}: Some sync lines found, but no match to user-specified servers/pools. Re-checking..."
fi
done
if [[ "${synced}" -eq 1 ]]; then
echo "[INFO] NTP sync successful; at least one user-specified server/pool is providing sync."
else
echo "[ERROR] Unable to detect synchronisation from any user-specified server/pool after multiple attempts."
echo " Investigate network connectivity or server availability if this is unexpected."
exit 1
fi
}
# -----------------------------------------------------------------------------
# main: Main script execution flow.
# -----------------------------------------------------------------------------
main() {
parseArgs "$@"
checkRoot
checkDebian
installNtpPackage
detectNtpConfFile
local totalCount=$(( ${#NTP_SERVERS[@]} + ${#NTP_POOLS[@]} ))
if [[ "${totalCount}" -gt 0 ]]; then
gatherAllIpsForHosts
fi
configureNtp
# Only restart the service if we configured it
if [[ "${totalCount}" -gt 0 ]]; then
restartNtp
fi
checkNtpSync
echo "[INFO] Final check: NTP configuration complete (if applicable) and service is active."
echo "[INFO] Script completed successfully."
}
# -----------------------------------------------------------------------------
# Script entry point
# -----------------------------------------------------------------------------
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment