Proxmox PVE sync VM time after hibernate

I have a few Windows VMs running on Proxmox PVE, i need only from time to time. In the meantime, i hibernate them. After waking them up, they have the time from when I hibernated them and it’s not updated. I have to restart the windows time service or force a sync to get the correct time. Guest tools are installed and the QEMU agent is enabled. I found no option in the Proxmox PVE to sync time after hibernation, but this workaround with a hook script works for me:

  1. Create a hook script on the Proxmox host, for example at /var/lib/vz/snippets/synctime.sh:
#!/bin/bash

# qm set <id> --hookscript local:snippets/synctime.sh
# Shutdown VM and then start it again to apply the hook script
# Test manually by running the script with the vmid and phase as arguments, for example:
# ./synctime.sh <vmid> post-start

# First argument is the vmid
vmid="$1"

# Second argument is the phase
phase="$2"

# Check if the two arguments are provided when testing the script
if [[ -z "$vmid" || -z "$phase" ]]; then
    echo "Usage: $0 <vmid> <phase>"
    echo "Example: $0 100 post-start"
    exit 1
fi

echo "Running hook script for VM ID $vmid during phase $phase"

if [[ "$phase" == "post-start" ]]; then
    # waiting for vm guest service to start
    started="false"
    loopstart=$EPOCHSECONDS
    while [[ "$started" == "false" ]]; do
        qm guest cmd $vmid ping && started="true"
        if [[ "$started" == "false" ]]; then
            sleep 2
        fi
        if (( EPOCHSECONDS-loopstart > 60 )); then
            echo "timeout for vm guest service start"
            break
        fi
    done

    # sync VM time after resume or start
    if [[ "$started" == "true" ]]; then
        time=$(date +%s%N)
        socket=/var/run/qemu-server/$vmid.qga
        cmd="{\"execute\":\"guest-set-time\", \"arguments\":{\"time\":$time}}"
        echo "$cmd" | socat - UNIX-CONNECT:"$socket"
        echo "Time synchronization for VM ID $vmid completed."
    else
        echo "vm guest service not running"
        exit 1
    fi
fi
  1. Make the script executable:
chmod +x /var/lib/vz/snippets/synctime.sh
  1. Test the script to ensure it works correctly. Run ./synctime.sh <vmid> post-start and replace <vmid> with the ID of your VM. You should see output indicating that the time synchronization for the specified VM ID has been completed.
root@pve: ./synctime.sh 101 post-start
Running hook script for VM ID 101 during phase post-start
Time synchronization for VM ID 101 completed.
  1. Enable the hook script for your VM. Replace 101 with your VM ID:
qm set 101 --hookscript local:snippets/synctime.sh
  1. Shutdown and Start the VM to apply the changes in the VM configuration.
  2. Now, every time you start the VM, the hook script will run and synchronize the time after hibernation. In the Task "VM x - Resume" you should see the output from the hook script indicating that the time synchronization has been completed:
Running hook script for VM ID 101 during phase pre-start
Resuming suspended VM
[...]]
Resumed VM, removing state
Running hook script for VM ID 101 during phase post-start
Time synchronization for VM ID 101 completed.
TASK OK

You can also check the log from the console with journalctl -fu qmeventd to see the output from the hook script when the VM starts.

References: