• Indonesian
  • English
  • Fix Linux Time Sync: Complete Chrony NTP Guide 2026

    Kecepatan:
    ⏱ 17 min read

    Fixing Linux Time Sync with Chrony and NTP: A Complete Troubleshooting Guide

    Okay, I have to tell you about this before I forget, because it’s one of those moments where everything just clicks. Yesterday a client’s staging server was throwing “certificate expired” errors on every single API call. We renewed the cert, reinstalled it, triple-checked the chain — still broken. And then the engineer helping me casually ran timedatectl, and I watched him go quiet for a second. The server clock was off by eleven minutes. ELEVEN MINUTES. That’s all it was. One forgotten time sync daemon, and a whole stack of services acting insane.

    And here’s the crazy part — this isn’t rare at all. I run into it constantly, and every single time it feels like finding out the house alarm was tripped by a spider on the sensor. The fix is genuinely simple once you know what you’re looking at. So let me show you exactly how I fix Linux time sync issues using chrony and NTP on production servers, step by step, the way I wish someone had shown me years ago. Trust me, you’re gonna want to bookmark this one.

    Difficulty: Beginner
    Last Updated: August 2026
    Tested On: Ubuntu 22.04/24.04 (chrony 4.x), Debian 12, Rocky Linux 9, AlmaLinux 9, CentOS 7 (chrony 3.4), cPanel/WHM on AlmaLinux

    Alright, let’s back up and talk about why this matters, because I want you to feel the weight of it. Think of your server like a bakery. Every station has its own timer — the ovens have one, the proofing box has one, the guy prepping dough has a stopwatch. As long as all the timers roughly agree, the bakery runs fine. But the moment one oven runs three minutes slow, the batches that came out of it get logged at the wrong time, the delivery schedule falls apart, and the owner starts blaming the wrong recipe. That’s your server with a broken clock. Logs get wrong timestamps, cron fires at the wrong moment, TLS certificates look expired, and database replicas refuse to agree with each other. Time sync is the silent foundation underneath all of it.

    Now, why does the clock drift at all? Your server has two clocks: a hardware clock (the RTC, powered by a battery on physical machines) and a system clock managed by the kernel. The hardware clock drifts because it’s just a crystal oscillator — cheap ones wander a few seconds per day. The system clock is derived from it, and on virtual machines it gets even worse because the hypervisor can pause, migrate, or inflate the virtual clock. Without a daemon to periodically reconcile against an authoritative time source, your clock just wanders off over days and weeks.

    That’s what NTP is for — the Network Time Protocol. Your machine asks an authoritative time server (or a pool of them) “hey, what time is it?”, measures the network round-trip, and steers the local clock. There are three implementations you’ll meet in the wild: the classic ntpd, systemd-timesyncd, and chrony. Here’s the takeaway I want you to remember for this whole article: on a modern Linux box, especially a VM, chrony is almost always the right answer. It handles the weird stuff VMs do — suspended clocks, live migration, bursty latency — far more gracefully than the alternatives. This is the tool you want running in production. And if your box lives on a virtualized platform, you might also enjoy our VPS KVM migration guide once you’re done here.

    The Three Candidates: chrony vs ntpd vs systemd-timesyncd

    Let me lay out the differences quickly, because people get stuck here way more than they should.

    Feature chrony ntpd systemd-timesyncd
    Best for VMs, containers, laptops, servers with flaky network Physical enterprise servers, NTP broadcast networks Desktops and workstations where good enough is fine
    Fast initial sync Yes (iburst makes it snappy) Slow-ish, gradual Yes, but coarse
    Handles suspended or frozen VM clock Excellent — detects and corrects Poor Poor
    Can act as full NTP server Yes Yes No
    Configuration Simple, one file More complex Nearly none
    Default on RHEL 8/9, Rocky/Alma 9, Ubuntu 22.04+, Fedora Older RHEL/CentOS, older Ubuntu Ubuntu fallback, most minimal installs

    Quick verdict: if your box is a VM (which most “servers” are these days), run chrony. If it’s a physical box with nothing special going on, chrony still works great. systemd-timesyncd is honestly fine for a laptop you use to browse the web. The moment it’s production, make it chrony. Done talking — let’s fix some clocks.

    Step 1: Diagnose Your Time Sync Before Touching Anything

    I can’t stress this enough: look before you leap. Half the broken time-sync situations I’ve debugged were caused by someone reconfiguring a daemon that wasn’t even the problem. First thing, SSH in and check the lay of the land:

    date
    timedatectl status

    Read the output carefully. The two lines that matter are System clock synchronized and NTP service. Here’s what a healthy box looks like:

                   Local time: Mon 2026-08-03 09:15:22 WIB
               Universal time: Mon 2026-08-03 02:15:22 UTC
                     RTC time: Mon 2026-08-03 02:15:22
                    Time zone: Asia/Jakarta (WIB, +0700)
    System clock synchronized: yes
                  NTP service: active
              RTC in local TZ: no

    A few things to read off this output:

    • System clock synchronized: yes — some daemon is steering the clock, and it’s succeeding.
    • NTP service: active — systemd knows about the NTP configuration.
    • RTC in local TZ: no — the hardware clock is kept in UTC. This is what you want. Never store local time in the RTC on Linux.

    If yours says synchronized: no, or the NTP service is inactive, we’ve found our problem. Next, figure out which daemon (if any) is actually running:

    systemctl list-units --type=service | grep -E "chronyd|ntpd|ntp|systemd-timesyncd"

    Or check each one explicitly:

    systemctl status chronyd
    systemctl status systemd-timesyncd
    systemctl status ntpd

    Fix Linux time sync using chrony and NTP on a production server

    You’ll land in one of three situations:

    • chronyd is active — then the issue is probably in the configuration or the firewall. Jump to Steps 4 and 7.
    • systemd-timesyncd is active — it works, but for production I’d still move to chrony. Proceed to Steps 2 and 3.
    • Nothing is running — this is the most common one on freshly provisioned VPS boxes. Install chrony (Step 3) and we’re golden.

    Every now and then you’ll find TWO daemons fighting each other — chronyd plus systemd-timesyncd both enabled. That’s a recipe for a jittery clock, because they’ll step on each other’s corrections. One time-sync daemon per box. That’s the rule.

    Step 2: Clear Out the Competition

    If systemd-timesyncd is running and you want chrony to take over, stop and disable it. But before you go killing services, let’s be adults about this — back things up and know exactly what you’re changing.

    Security Warning: Back Up Before Proceeding

    Before you stop any service or edit time-sync config on a production server, make sure you have:

    1. A backup of the current config: cp /etc/chrony/chrony.conf /etc/chrony/chrony.conf.bak.$(date +%Y%m%d)
    2. If you’re migrating from ntpd, back up /etc/ntp.conf too.
    3. Know exactly which daemon is active right now (systemctl list-units | grep ntp) so you can roll back if needed.

    Stopping the wrong service without a plan can leave your box with no time sync at all — and during business hours that means wrong cron jobs, broken cert validation, and confusing logs for everyone.

    Now, if systemd-timesyncd is in the way:

    sudo systemctl stop systemd-timesyncd
    sudo systemctl disable systemd-timesyncd

    And if you’re on an older box running the classic ntpd:

    sudo systemctl stop ntpd
    sudo systemctl disable ntpd

    Verify nothing else is holding the port: ss -uap | grep 123 should come back empty. If something’s still listening, hunt it down before moving on.

    Step 3: Install Chrony

    Choose your distro’s flavor:

    Ubuntu / Debian:

    sudo apt update
    sudo apt install chrony -y

    Rocky / AlmaLinux / RHEL 8+:

    sudo dnf install chrony -y

    CentOS 7 / RHEL 7:

    sudo yum install chrony -y

    Then enable it to start on boot and start it now:

    sudo systemctl enable --now chronyd
    sudo systemctl restart chronyd

    Restarting right after a fresh install is low-risk since the config is still the package default. If you’ve already hand-edited the config, back it up first (see the warning box in Step 2) before you restart.

    Step 4: Configure chrony.conf Like a Pro

    The config lives at /etc/chrony/chrony.conf. Here’s the setup I use for production boxes, with the important directives called out:

    # Time sources — pick a nearby pool, use iburst for fast initial sync
    pool 0.id.pool.ntp.org iburst
    pool 1.id.pool.ntp.org iburst
    pool 2.id.pool.ntp.org iburst
    
    # makestep is the hero of this whole file.
    # makestep 1 3 means: if the offset is more than 1 second, step the clock
    # instead of slewing, and only for the first 3 updates.
    # This is what saves VMs whose clock is minutes off after a pause.
    makestep 1 3
    
    # Refuse sources that are too far out of range
    maxdistance 16.0
    
    # Optional: make this box a time source for your LAN
    # allow 10.10.10.0/24
    # local stratum 10
    
    # Logging for audit purposes
    logdir /var/log/chrony
    log measurements statistics tracking

    Let’s zoom in on the two directives that actually matter:

    • pool ... iburst — your time sources. I use the Indonesian pool here since the box sits in Jakarta; if your server is in Europe or the US, plain pool ntp.org iburst is fine and chrony sorts it out. The iburst flag fires a burst of requests at startup so you sync in seconds instead of minutes.
    • makestep 1 3 — without this, chrony only slews the clock (nudges it gradually). If the offset is eleven minutes, like my story above, slewing would take forever. With makestep, if the offset exceeds one second within the first 3 updates, chrony jumps the clock straight to the right time. On a VM just resumed from a snapshot, this is the difference between a healed clock and a confusing one.

    Save the file when you’re done, then restart and confirm it came up clean:

    sudo systemctl restart chronyd
    sudo systemctl status chronyd

    You want to see active (running). If you get any Unknown directive warnings, there’s a typo in the config — fix it, restart again, verify again.

    Step 5: Verify Sync — The Part Everybody Skips

    Here’s the thing nobody tells you: installing chrony is step one, but verifying that it actually works is what separates the people who fix the problem from the people who think they fixed it. Two commands rule this domain: chronyc tracking and chronyc sources -v.

    First, the tracking report:

    chronyc tracking

    Healthy output looks like this:

    Reference ID    : 203.0.113.10 (ntp1.id.pool.ntp.org)
    Stratum         : 2
    Ref time (UTC)  : Mon Aug 03 02:15:22 2026
    System time     : 0.000034 seconds slow of NTP time
    Last offset     : +0.000021 seconds
    RMS offset      : 0.000031 seconds
    Frequency       : -0.415 ppm slow
    Residual freq   : +0.001 ppm
    Skew            : 0.002 ppm
    Root delay      : 0.021012 seconds
    Root dispersion : 0.002456 seconds
    Update interval : 1024.3 seconds
    Leap status     : Normal

    Let’s read it line by line, because this is where people get lost:

    • Reference ID — the source chrony is currently locked onto. In my example the real pool IP is masked, but you should see a real source name here. If it shows 0.0.0.0 or nothing, no source is selected yet.
    • Stratum: 2 — you’re two hops from an atomic clock. Healthy. Stratum above 5-6 usually means poor sources or a bad network path.
    • System time: 0.000034 seconds slow — your clock is within 34 microseconds of the reference. Excellent.
    • Leap status: Normal — no leap second pending. You’ll occasionally see Insert second around June or December when the real world adds leap seconds; don’t panic.

    Now the source list, which is honestly the more useful command:

    chronyc sources -v
      .-- Source mode  '^' = server, '=' = peer, '#' = local clock.
     / .- Source state '*' = current synced, '+' = combined , '-' = not combined,
    |/   '?' = unreachable, 'x' = time may be in error, '~' = time too variable.
    ||                                                 .-+-+-+-+-+-+-+-+-+-+-+-
    ||  MS Name/IP address          Stratum Poll Reach LastRx Last sample
    ==============================================================================
    ^* ntp1.id.pool.ntp.org              2   6   377    35  +111us[ +164us] +/- 11ms
    ^+ ntp2.id.pool.ntp.org              2   6   377    35  +202us[ +254us] +/- 12ms
    ^+ ntp3.id.pool.ntp.org              2   6   377    35  -156us[ -150us] +/- 13ms

    Decoding time:

    • The * in the state column marks the source currently being used, and the ^ in the mode column means it’s a server. If you see NO star anywhere, chrony hasn’t selected a source — keep digging.
    • Reach: 377 — that octal value is your packet recovery score. 377 means all 8 recent polls got replies. Dropping numbers like 177, 37, or 3 mean packets are being lost — almost always firewall or routing.
    • Last sample — recent offset in microseconds. Values in the millisecond range are fine. Consistently half a second or more? Something’s off in the network path.

    Want more detail? chronyc sourcestats gives you long-term statistics per source, and chronyc ntpdata shows raw NTP packet info if you’re feeling brave. For a final sanity check, run timedatectl status again and confirm System clock synchronized: yes.

    That’s the core of it, right there. But we’re not done — a broken timezone or a locked-down firewall can undo all of this, so let’s handle those two landmines too.

    Step 6: Fix the Timezone and the Hardware Clock

    You can have a perfectly synced UTC clock and still see wrong time everywhere if the timezone is off. Set it properly:

    sudo timedatectl set-timezone Asia/Jakarta
    timedatectl status

    Browse available zones with timedatectl list-timezones. While you’re here, make sure the RTC runs in UTC, never local time:

    sudo timedatectl set-local-rtc 0
    sudo hwclock --systohc

    hwclock --systohc writes the current system time into the hardware clock. On physical boxes this keeps the battery-backed RTC sane; on VMs the RTC is virtual and mostly managed by the hypervisor, but running it once after a big correction doesn’t hurt.

    Step 7: Open Up the Firewall for NTP

    If chrony is installed, configured, restarted, and your chronyc sources still shows question marks (state ?), the firewall is almost certainly blocking UDP port 123. It happens on default-deny setups all the time.

    UFW (Ubuntu/Debian):

    sudo ufw allow out 123/udp comment 'ntp client'
    sudo ufw allow 123/udp comment 'ntp server'

    firewalld (Rocky/Alma/CentOS):

    sudo firewall-cmd --add-service=ntp --permanent
    sudo firewall-cmd --reload

    CSF (common on cPanel/WHM boxes):

    # Edit /etc/csf/csf.conf and add port 123 to UDP_IN and UDP_OUT,
    # then apply the changes:
    csf -r

    One nuance: if your server is only a client (the common case), you only strictly need outbound UDP 123. Inbound only matters if you’re also serving time to other machines. I usually open both anyway — it makes the box ready to be a time source later without a second firewall change.

    Step 8: The Troubleshooting Table You’ll Actually Use

    This is the table I wish existed when I was a junior. Bookmark it — 80% of time-sync incidents are in here somewhere.

    Symptom Likely cause Fix
    All sources show state ? UDP 123 blocked, or two daemons fighting Open UDP 123 (Step 7), stop the other daemon (Step 2)
    NTP active but synchronized: no chronyd can’t reach its pool, or bad config Check chrony.conf has a valid pool ... iburst line
    Clock off by minutes or hours VM was suspended or migrated, or makestep missing Add makestep 1 3, or force a step with chronyc makestep
    Slow but steady drift (seconds per day) Cheap hardware RTC, virtualization clock skew Keep RTC in UTC, run hwclock --systohc, leave chronyd running
    Reach count dropping (377 to 177 to 37) Lost NTP packets, congestion, or dying source Check ss -uap | grep 123, add more sources, swap pool
    Fine after fix, wrong again after reboot chronyd not enabled, or timesyncd came back systemctl enable --now chronyd, keep timesyncd disabled
    Clock fine, apps still complain about certs Wrong timezone on the box Set the right timezone (Step 6)

    If you’ve been through all the steps and something still feels weird, go read the chrony log at /var/log/chrony/chronyd.log. Here’s a healthy sequence and what each part tells you:

    2026-08-03 02:14:01Z chronyd version 4.3 starting (+CMDMON +NTP +REFCLOCK +RTC +PRIVDROP +SCFILTER +SIGND +ASYNCDNS +NTS +LEAPSECMOM +SUNWARN)
    2026-08-03 02:14:01Z chronyd using configuration from /etc/chrony/chrony.conf
    2026-08-03 02:14:01Z Listening on UDP port 123
    2026-08-03 02:14:01Z Initial frequency -415.300 ppm
    2026-08-03 02:14:06Z System clock wrong by 42.3 seconds, adjustment started
    2026-08-03 02:14:06Z System clock was stepped by 42.3 seconds
    2026-08-03 02:14:36Z Selected source 203.0.113.10 (ntp1.id.pool.ntp.org)
    2026-08-03 02:15:06Z Source 203.0.113.10 (ntp1.id.pool.ntp.org) is reachable
    2026-08-03 02:15:07Z System clock wrong by 0.0003 seconds, adjustment started
    2026-08-03 02:15:07Z System clock was slewed by 0.0003 seconds
    2026-08-03 02:15:10Z Source 203.0.113.11 (ntp2.id.pool.ntp.org) is reachable

    Walk through it top to bottom — this is the symptom-to-pattern-to-root-cause path:

    • Lines 5-6: System clock wrong by 42.3 seconds followed by System clock was stepped — that’s makestep doing its job. The clock jumped straight to the right time. If you never see a stepped line, makestep isn’t triggering (or wasn’t set) and your clock is being nudged slowly instead.
    • Line 7: Selected source — chrony picked its primary reference.
    • Lines 8 and 10: is reachable — sources are answering. If you see unreachable here, it’s your firewall or network path, not chrony.

    Pro Tips & Warnings From the Trenches

    A few scars worth sharing:

    Tip 1: Never store local time in the hardware clock. RTC in local TZ: no is the goal, always. If you set it to local, a daylight-saving change or a timezone edit turns into a confusing clock catastrophe, and dual-boot setups get even messier.

    Tip 2: On VMs, don’t rely on systemd-timesyncd alone. It syncs once at boot and then just sort of vibes. Chrony is purpose-built for the weird clock behavior virtual machines exhibit — use it.

    Warning: Migrating from ntpd to chrony mid-business-hours can cause the clock to step (jump) by the full offset. For time-sensitive applications — distributed locks, replay protection, any monotonic-time assumptions — that jump can matter. Do the swap during a maintenance window, not at 2 PM on a Tuesday.

    One more field note: if your company routes everything through an internal NTP server instead of the public pool, point chrony at that internal server and make sure nothing between you and it is dropping UDP 123. Nine times out of ten, reach = ? on a corporate network is an endpoint firewall silently killing NTP. And while you’re building out your ops toolkit, our guides on analyzing Linux logs with journalctl and troubleshooting high load on Linux servers pair nicely with this one.

    Q: Why chrony instead of ntpd or systemd-timesyncd?

    Because modern servers are usually VMs, and VMs do weird things to clocks — they pause them, migrate them, inflate them. Chrony detects and corrects for that behavior, syncs fast at boot thanks to iburst, and stays stable on imperfect network links. systemd-timesyncd is fine for a laptop, ntpd is fine for old-school physical broadcast networks, but for a production VM or container, chrony is the balanced pick.

    Q: I added makestep but the clock still won’t step. Why?

    makestep only applies to the first N updates — the 3 in makestep 1 3. If your box has been up for a week and the offset shows up on update number 500, chrony will slew, not step. Restart chronyd to reset the counter, or force it manually with sudo chronyc makestep.

    Q: It says synchronized, but the clock is still a second or two off the NTP server. Normal?

    Yes. Network latency guarantees a small offset — a few milliseconds is healthy. If it’s consistently off by whole seconds, look at your network path to the NTP server; asymmetric routing or a firewall that queues packets is the usual suspect.

    Q: Can I run chrony and ntpd at the same time for redundancy?

    Please don’t. Two daemons both steering the system clock will fight each other and make the offset worse, not better. One daemon, many sources. Configure all your time servers as sources inside the single daemon instead.

    Alright, that’s the whole playbook. Fixing Linux time sync with chrony and NTP is one of those problems where a tiny config change makes a huge difference — and honestly, I love that about it. Your logs will finally make sense, your certs will stop crying, your cron will fire when it should, and your replicas will get along again. We also have a server monitoring basics guide if you want to catch these problems before they bite.

    If you’ve got a favorite time-sync trick or a horror story from a drifting clock, drop it in the comments — I’m always down to learn a better way. And if this saved you an afternoon, share it with the next engineer who’s about to lose theirs. Now go make sure your clocks are straight. Gas!

    Author: Syslog Solutions — NOC & Server Management Team. We handle 500+ servers daily, from shared hosting to enterprise dedicated infrastructure.