Fix Linux VM Wrong Time After Reboot

When a Linux VM shows the wrong time after every reboot, trace the clock from the virtual RTC through chrony, identify the selected NTP source, and fix stale RTC values, unsuitable chrony sources, or hypervisor time-sync conflicts.

Published

Updated

Read time 11 min read

Reviewed byDeepak Prasad

Linux VM clock sync guide banner with chrony, RTC, and NTP troubleshooting

After a reboot, your Linux VM can show a time that is hours off even though you fixed it last session. That usually means something in the boot chain overwrote a good clock: the virtual RTC started wrong, chrony stepped to a bad NTP peer, or the hypervisor injected host time while chrony was also adjusting the clock.

This guide walks through how Linux sets time on boot, how to read timedatectl and chronyc, and the fixes that stick. The lab example came from a Rocky Linux host where LDAP client setup and a separate lab script both touched the same machine; the LDAP configuration was unrelated, but a server … prefer line added for the lab client made the two changes appear connected. The same checks apply on Ubuntu, Debian, and other chrony-based distros.

Tested on: Rocky Linux 10.2; chrony 4.8; VirtualBox VM with host-only and NAT interfaces.

For day-to-day NTP tuning, see Chrony force sync in Linux.


Quick answer

Run timedatectl status and chronyc tracking right after a bad reboot. Then check journalctl -u chronyd -b on the RHEL family or journalctl -u chrony -b on Ubuntu and Debian for Selected source and System clock was stepped. If chrony selected a lab host that was given prefer, or that host was serving wrong time through local stratum 10 orphan with no valid upstream, remove or redesign those directives, keep rtcsync in /etc/chrony.conf, set a trusted approximate time once if needed, and reboot to confirm the virtual RTC holds the fix.


How Linux sets time on boot

The sequence matters when you are debugging a jump:

  1. The hypervisor exposes a virtual RTC value to the guest.
  2. The kernel sets the initial system clock from that RTC (UTC when RTC in local TZ: no).
  3. Programs interpret and display the system clock using the configured timezone, normally through /etc/localtime. Changing the timezone does not change the underlying UTC system time.
  4. chronyd (or systemd-timesyncd on some images) polls NTP servers and may step or slew the clock.

systemd-timedated is the D-Bus service behind timedatectl; it is not a separate boot stage that applies the timezone. If step 4 selects a peer that is itself wrong, the VM can move to that peer's time even when the RTC was closer to reality. That is what happened in the lab trace below.


Diagnose right after a bad reboot

Start with the clock state. You want timezone, whether NTP is active, and whether the RTC matches UTC.

bash
timedatectl status

Sample output:

output
Local time: Sun 2026-07-19 11:05:19 IST
           Universal time: Sun 2026-07-19 05:35:19 UTC
                 RTC time: Sun 2026-07-19 05:35:20
                Time zone: Asia/Kolkata (IST, +0530)
System clock synchronized: no
              NTP service: active
          RTC in local TZ: no

RTC in local TZ: no is what you want on Linux: the virtual RTC stores UTC, and the OS applies the timezone for display.

Confirm only the intended time service is running. Both chronyd and systemd-timesyncd should not normally be active together.

bash
# RHEL, Rocky Linux, AlmaLinux, Oracle Linux
systemctl is-active chronyd systemd-timesyncd

# Ubuntu and Debian
systemctl is-active chrony systemd-timesyncd

Commands below use chronyd on the RHEL family. Replace the systemd unit name with chrony on Ubuntu and Debian.

Sample output on a chrony-based RHEL or Rocky system:

output
active
inactive

If both show active, stop and disable the one you do not want before continuing.

Next, see which NTP source chrony considers best and whether it is actually synchronized.

bash
chronyc tracking

Sample output when no upstream NTP is reachable (common in locked-down lab networks):

output
Reference ID    : 00000000 ()
Stratum         : 0
Leap status     : Not synchronised

A healthy client instead shows a real reference (for example time.cloudflare.com) and Leap status: Normal.

List every configured source. In chronyc sources -v, ^* marks the selected NTP server, # can mark a reference clock such as a local source, and ^? means unreachable or not yet usable.

bash
chronyc sources -v

Sample output:

output
^? time.cloudflare.com           0   6     0     -     +0ns[   +0ns] +/-    0ns
^? time3.google.com              0   6     0     -     +0ns[   +0ns] +/-    0ns

All public pools marked ^? while a LAN IP is selected is a strong hint the VM is syncing only to a local peer.

For more detail beyond the source list:

bash
chronyc activity
chronyc sourcestats

chronyc activity shows how many sources are online, offline, or unresolved. chronyc sourcestats shows whether chronyd has accumulated usable measurements and the estimated offset and stability of each source.

The boot log usually states the offset chrony applied and which source it chose.

bash
# RHEL family
journalctl -u chronyd -b --no-pager | grep -iE 'Selected source|step|wrong'

# Ubuntu and Debian
journalctl -u chrony -b --no-pager | grep -iE 'Selected source|step|wrong'

Sample output from the lab incident (trimmed):

output
Jul 19 10:57:18 ldap1.example.com chronyd[2258]: Selected source 192.168.56.109
Jul 19 10:57:18 ldap1.example.com chronyd[2258]: System clock wrong by -31673.864517 seconds
Jul 19 02:09:24 ldap1.example.com chronyd[2258]: System clock was stepped by -31673.864517 seconds

Roughly 31 673 seconds is about 8.8 hours. The RTC had been near 10:57 IST after boot; chrony stepped the clock back to match 192.168.56.109 (a lab LDAP client), which was still on 02:09 IST.

Compare the system clock to the RTC. The Universal time and RTC time lines from timedatectl status above already give an unambiguous UTC-side comparison when RTC in local TZ: no.

You can also compare local-time displays directly:

bash
date '+%Y-%m-%d %H:%M:%S %z'
sudo hwclock --show --utc

Sample output:

output
2026-07-19 11:05:19 +0530
2026-07-19 11:05:20.321044+05:30

Both commands display local time here. The --utc option tells hwclock that the underlying RTC value uses the UTC timescale; it does not change the displayed timezone. The two timestamps should be close when rtcsync is working and you shut down cleanly.


Common causes

Use the Fix column to jump to the section that addresses each cause.

Cause Why it happens Fix
prefer on a lab NTP server prefer gives that source priority over other selectable sources. If the preferred lab server is reachable and chrony considers it valid, it may be selected instead of public sources. It does not make an unreachable source usable. Review chrony directives
local stratum 10 orphan on a lab VM Legitimate for an isolated or redundant local NTP design. In the lab incident, this configuration allowed chrony to provide local time when no valid upstream was available. Because that VM's clock was wrong, clients that accepted it as their usable or preferred source inherited the incorrect time. Review chrony directives
Stale or incorrectly interpreted RTC RTC was not updated, the VM restored an old clock value, or RTC local/UTC interpretation is inconsistent Confirm rtcsync and timezone, set the clock once
Outbound NTP blocked Firewall or network blocks UDP 123 to the internet Diagnose sources, Troubleshooting
VirtualBox or VMware time sync vs chrony Hypervisor and chrony both adjust the clock VirtualBox and VMware guests
timedatectl set-local-rtc 1 Windows dual-boot setups; Linux then treats RTC as local time Confirm rtcsync and timezone

Fix permanently

1. Review chrony directives

On the affected VM, open /etc/chrony.conf (Ubuntu and Debian: /etc/chrony/chrony.conf) and review lab-only server lines, prefer, and local / orphan settings.

The prefer option gives a source priority over other selectable non-preferred sources. It does not make an unreachable or rejected source usable, and it does not bypass chrony's source-validity checks. If a preferred lab server is reachable and considered valid, chrony may keep selecting it even when public pools are also configured.

Bad pattern that caused the lab jump:

output
# BEGIN lab local NTP
server 192.168.56.109 iburst prefer
# END lab local NTP

On the peer VM, this configuration allowed chrony to provide local time when no valid upstream source was available:

text
allow 192.168.56.0/24
local stratum 10 orphan

local stratum 10 orphan is not inherently wrong. It is used for intentionally isolated networks and for coordinating multiple local time servers. The orphan option helps redundant local servers agree when no external source is selectable; chrony 4.8 ignores orphan sources while another source remains selectable, and orphan mode has a default unsynchronized activation delay. Remove local or orphan configurations unless the machine is intentionally acting as a time server for an isolated or redundant NTP network.

Keep a standard client configuration when public or internal NTP is available. Retain your distribution's default pool, or point at verified internal servers:

text
# Retain your distribution's default pool, or use a verified internal source
pool 2.rocky.pool.ntp.org iburst
rtcsync
makestep 1.0 3

On Ubuntu and Debian, use the pool already defined in /etc/chrony/chrony.conf or under /etc/chrony/sources.d/ rather than copying a Rocky-specific pool name. Keep your distribution's default NTP pool or configure verified internal servers. If you choose Google Public NTP, use only Google's recommended servers instead of combining them with non-smeared NTP sources; Google advises against mixing its leap-smeared service with ordinary NTP around leap seconds.

Restart chrony after edits:

bash
# RHEL family
sudo systemctl restart chronyd

# Ubuntu and Debian
sudo systemctl restart chrony

2. Set the clock once when NTP is not yet working

If the system clock is still wrong after fixing chrony sources, set a trusted approximate time manually as a bootstrap step. timedatectl set-time updates both the system clock and the RTC, so a separate hwclock --systohc is normally redundant immediately afterward.

bash
sudo timedatectl set-ntp false
sudo timedatectl set-time '2026-07-19 11:30:00'
sudo timedatectl set-ntp true
# RHEL family
sudo systemctl restart chronyd
# Ubuntu and Debian
sudo systemctl restart chrony

The timestamp is interpreted in the currently configured local timezone. Replace the example with a time you trust from the hypervisor console, host clock, or hardware management interface.

When NTP sources are reachable again, ask chrony for fresh measurements before stepping:

bash
sudo chronyc burst 4/4
sleep 10
sudo chronyc makestep

burst 4/4 asks chronyd to collect fresh measurements over a short period. Allow the burst to complete before running makestep, which immediately applies the correction chronyd has calculated. makestep does not make an unreachable server reachable or obtain a fresh measurement by itself.

Sample output from makestep:

output
200 OK

Then confirm synchronization:

bash
chronyc tracking

Sample output on a healthy client:

output
Reference ID    : A29FC87B (time.cloudflare.com)
Stratum         : 4
System time     : 0.000012345 seconds fast of NTP time
Leap status     : Normal

For a one-shot sync with the daemon stopped, use chronyd -q instead, as described in Chrony force sync in Linux.

3. Confirm rtcsync and timezone

Ensure rtcsync is present in chrony.conf (shown above). It lets the kernel copy accurate system time to the RTC about every 11 minutes while chrony is running.

Verify timezone and RTC mode:

bash
timedatectl show | grep -E 'Timezone|LocalRTC|NTP'

Sample output:

output
Timezone=Asia/Kolkata
LocalRTC=no
NTP=yes

If LocalRTC=yes and you do not deliberately require a local-time RTC, return it to UTC with sudo timedatectl set-local-rtc 0. On a Windows dual-boot system, configure both operating systems to use the same RTC convention before changing this setting.

4. VirtualBox and VMware guests

On VirtualBox, Guest Additions can push host time into the guest on a schedule. That conflicts with chrony when both try to control the clock.

If Guest Additions is installed, decide whether VirtualBox or chrony will be the primary time authority. When chrony should control time, disable Guest Additions host-time synchronization on the host while the VM is powered off:

bash
VBoxManage setextradata "VM name" "VBoxInternal/Devices/VMMDev/0/Config/GetHostTimeDisabled" 1

Installing Guest Additions alone does not prevent the conflict; you must choose one authority and disable the other mechanism when needed.

On VMware, install open-vm-tools and review VMware documentation for time sync versus NTP on that platform.

5. Reboot test

Reboot the VM, log in, and run:

bash
date
timedatectl status
# RHEL family
journalctl -u chronyd -b --no-pager | grep -iE 'step|Selected source' || echo "No clock step on this boot"
# Ubuntu and Debian
journalctl -u chrony -b --no-pager | grep -iE 'step|Selected source' || echo "No clock step on this boot"

You want no multi-hour step lines and a stable date compared to before reboot.


Troubleshooting

Symptom Likely cause Fix
Clock jumps hours backward on boot prefer on a lab NTP server with stale time, or clients accepting a wrong local server Remove or redesign prefer and local/orphan server roles; sync from public pools or a verified internal stratum peer
chronyc sources shows all ^? Outbound UDP 123 blocked, routing trouble, or server reachability problems Check chronyc activity, chronyc sources -v, and journalctl -u chronyd -b (RHEL family) or journalctl -u chrony -b (Ubuntu/Debian). For packet-level confirmation: sudo tcpdump -ni any udp port 123 and look for requests without replies
Correct until reboot, then wrong Stale RTC, restored VM state, or RTC local/UTC mismatch Keep rtcsync; set a trusted time once with timedatectl set-time; avoid custom shutdown scripts that run hwclock --hctosys at the wrong stage
Time good after suspend, bad after restore Hypervisor injected host time Disable guest time sync or tune chrony; see VM docs
Offset equals timezone (e.g. 5h 30m) RTC stored as local time timedatectl set-local-rtc 0
chronyc reports 506 Cannot talk to daemon chrony service not running RHEL family: sudo systemctl enable --now chronyd. Ubuntu/Debian: sudo systemctl enable --now chrony

References


Summary

Persistent wrong time after reboot usually points to the initial virtual RTC value or to a service that adjusts the clock during or shortly after boot. Read journalctl -u chronyd -b on the RHEL family or journalctl -u chrony -b on Ubuntu and Debian first, review whether prefer gave priority to an unsuitable but selectable lab source, and remove local or orphan configurations unless the host is intentionally serving an isolated NTP network. Keep rtcsync, set a trusted approximate time once when NTP is still unavailable, and confirm with a clean reboot.


Frequently Asked Questions

1. Why does my Linux VM time change after reboot?

On boot the kernel reads the virtual RTC presented by the hypervisor, then chrony or systemd-timesyncd may adjust the system clock from NTP. A large jump usually means chrony stepped to an unsuitable source, the RTC was stale, or a hypervisor time-sync service fought chrony. Check journalctl -u chronyd -b on the RHEL family or journalctl -u chrony -b on Ubuntu and Debian for stepped and Selected source lines.

2. What is the difference between RTC time and system time in Linux?

On a VM the RTC is usually a virtual clock from the hypervisor, not a physical battery-backed chip. System time is what date and timedatectl show. With rtcsync in chrony.conf, the kernel copies accurate system time to the RTC about every 11 minutes so the next boot starts closer to correct time.

3. Should I use chrony prefer with a local lab NTP server?

Only when that server is verified against a trusted upstream. prefer gives that source priority over other selectable sources when chrony still considers it valid. It does not make an unreachable or rejected source usable. Combined with a wrong local server, it can still steer clients toward bad time.

4. How do I stop VirtualBox from fighting chrony on Linux?

If Guest Additions is installed, decide whether VirtualBox or chrony is the primary time authority. When chrony should control time, disable Guest Additions host-time synchronization on the host with VBoxManage setextradata VMname VBoxInternal/Devices/VMMDev/0/Config/GetHostTimeDisabled 1 while the VM is powered off.

5. How do I force chrony to sync immediately after fixing NTP?

Run chronyc burst 4/4, wait about 10 seconds for the burst to finish, then chronyc makestep while chronyd is running, and verify with chronyc tracking and chronyc sources -v. burst asks chronyd to collect fresh measurements; makestep applies a correction chronyd already calculated and does not make an unreachable server reachable. For a one-shot sync with the daemon stopped, use chronyd -q instead.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …