Repairs Windows time synchronization on standalone (workgroup) Windows 10 and 11 machines.
If your clock keeps drifting and Windows says "An error occurred while Windows was synchronizing with time.windows.com", this fixes the actual cause instead of forcing a one time resync.
Open PowerShell and run:
irm https://rafarrw.github.io/Windows-Time-Fix/run.ps1 | iexProject page: https://rafarrw.github.io/Windows-Time-Fix/
The short launcher downloads the current script directly from this repository,
so Windows-Time-Fix.ps1 remains the single source of truth.
Start with [1] Repair time synchronization (Recommended). It creates a backup and fixes the service, time sources, synchronization settings, and automatic recovery. This is the right choice for most computers.
If the recommended repair finishes but the problem remains, open [A] Advanced options. Use the large clock correction when the clock is wrong by many hours, or rebuild the Windows Time service when its registration appears corrupted. These advanced repairs include everything performed by option 1.
You can also diagnose the current state or exit without making changes. Each operation runs in the same visible PowerShell window. When it finishes, press any key to return to the main menu.
It only asks for administrator rights after you choose a repair option. Windows still shows the normal UAC confirmation, but the elevated worker stays hidden and sends its progress back to the original window.
To only diagnose without changing system settings or requesting elevation:
& ([scriptblock]::Create((irm https://raw.githubusercontent.com/rafarrw/Windows-Time-Fix/main/Windows-Time-Fix.ps1))) -VerifyOnlyRun that first if you want to see what is wrong before changing anything.
Search for this problem and you get the same handful of suggestions repeated in every video and forum thread:
- Click "Sync now" in Settings
- Run
w32tm /resync - Change the server to
pool.ntp.orgin the Date and Time control panel - Restart the Windows Time service
- Disable your firewall
These either do nothing or fix it until the next reboot. They fail for the same reason: they treat a stopped or misconfigured service as if it were a network problem. Most of the advice never checks whether UDP port 123 actually works, never looks at the Event Log, and never touches the settings that are genuinely wrong.
This script diagnoses first, then changes the specific settings that keep Windows from synchronizing, and verifies the result afterwards.
On a machine that is not joined to a domain, five things stack up:
-
The W32Time service is set to Manual with start triggers. One of those triggers is
DOMAIN JOINED, which never fires on a workgroup machine. The service ends up stopped, so nothing synchronizes. -
There is only one NTP peer,
time.windows.com. A single DNS failure at boot (common with Wi-Fi or VPN that comes up late) logs Event 134 and puts the client into exponential backoff:ResolvePeerBackoffMinutes15 timesResolvePeerBackoffMaxTimes7, which can reach roughly 16 hours between attempts. It may eventually recover, but can appear stuck for most of a day. -
SpecialPollIntervalcan be huge, commonly 16384 seconds and up to 604800 (one full week), depending on the Windows version and existing configuration. -
MaxPosPhaseCorrectionandMaxNegPhaseCorrectionare capped at 54000 seconds (15 hours). A clock that is further off than that, typically after a dead CMOS battery, gets refused instead of corrected. The script can temporarily lift this limit for one resync when explicitly requested. -
There is no safety net for machines whose network only comes up after boot.
The symptom in the Event Log is a recurring Event 36 ("the time service has not synchronized the system time for the last N seconds") along with Source: Free-running System Clock and Last Successful Sync Time: unspecified in w32tm /query /status.
| Setting | Before (typical) | After |
|---|---|---|
| Service start type | Manual, trigger started | Automatic (Delayed Start) |
| Service start triggers | DOMAIN JOINED and custom |
Removed |
| NTP peers | 1 | 4 redundant, unreachable ones demoted |
SpecialPollInterval |
often 16384 or 604800 s | 3600 s |
ResolvePeerBackoffMinutes |
15 | 2 |
ResolvePeerBackoffMaxTimes |
7 (up to 16 h) | 3 |
MaxPos/NegPhaseCorrection |
typically 54000 s | unchanged by default; temporarily lifted with -AllowLargeTimeCorrection, then restored |
| NTP connectivity | not verified | every peer tested before any system change |
| Safety net | none | scheduled task on boot, every 6 h, and on network connect |
Default NTP servers are time.cloudflare.com, time.google.com, pool.ntp.org and time.windows.com. Each one is tested before being written, and any that do not answer are moved to the end of the list as fallbacks.
Running from a downloaded file:
powershell -ExecutionPolicy Bypass -File .\Windows-Time-Fix.ps1| Parameter | What it does |
|---|---|
-Apply |
Apply the recommended repair without showing the interactive menu. |
-VerifyOnly |
Diagnose without changing system settings or requesting elevation. Some detailed status queries may be unavailable to non-admin users. |
-NtpServers |
Use your own servers, for example -NtpServers ntp1.corp.local,ntp2.corp.local |
-PollSeconds |
Poll interval, default 3600. Must be between 1024 and 32768. |
-Reregister |
Re-create the W32Time service registration. Only use when the service itself is missing or corrupted. |
-AllowDomain |
Required to run on a domain joined machine. Read the warning below first. |
-AllowLargeTimeCorrection |
Temporarily allow one correction of any size, then restore the previous limits. Only use with trusted NTP servers. |
-NoElevate |
Never prompt for elevation. Fail with a clear message instead. Useful for RMM and automation. |
Running without parameters opens the menu. Supplying a parameter runs directly,
which keeps the script suitable for automation. Note that irm <url> | iex
cannot pass parameters; use the scriptblock form shown in Quick start when you
need them.
Do not trust the script log alone. Check the real state:
w32tm /query /statusYou want to see:
Leap Indicator: 0(no warning)
Stratum: 2 (secondary reference - syncd by (S)NTP)
Last Successful Sync Time: <a recent timestamp>
Source: time.cloudflare.com,0x9
If it still says Source: Free-running System Clock or Leap Indicator: 3(not synchronized), it did not work. Open an issue with the output of -VerifyOnly.
Before changing anything, the script exports the current configuration to C:\ProgramData\Windows-Time-Fix-backup-<timestamp>.reg and records the rollback command in the detailed log. To undo:
reg import "C:\ProgramData\Windows-Time-Fix-backup-<timestamp>.reg"
schtasks /delete /tn "Windows-Time-Fix - Resync" /fRestart Windows afterwards so the Service Control Manager reloads the restored start type and service triggers.
A full technical log of every run is kept at C:\ProgramData\Windows-Time-Fix.log. The normal repair view only shows the main stages and important results; -VerifyOnly shows the detailed diagnostics on screen.
Domain joined machines. They should synchronize from their domain controller using Type=NT5DS. Applying this configuration there breaks the Active Directory time hierarchy. The script detects this and refuses to run unless you pass -AllowDomain.
When outbound UDP 123 is blocked. The script tests every server before changing anything and aborts if none respond, because in that case the problem is a firewall or router and no registry setting can fix it.
Virtual machines that take time from the hypervisor (Hyper-V integration services, VMware Tools) are already synchronized by the host. Fix it there, not in the guest.
A dead CMOS battery. -AllowLargeTimeCorrection can recover the clock once, but it deliberately does not leave unlimited corrections enabled. Replace the battery if the clock resets again after power loss.
- Windows 10 or Windows 11
- Windows PowerShell 5.1 (ships with Windows) or PowerShell 7
- Administrator rights to apply changes.
-VerifyOnlyruns without elevation, although Windows can deny some detailedw32tmstatus queries to non-admin users.
The script self elevates through a hidden worker and relays its progress to the
original PowerShell window. It re-downloads itself for that worker because
irm | iex leaves no file on disk to re-launch. If you fork this, edit
$ScriptUrl near the top of the script to point at your own raw URL. While it
still contains REPLACE-ME, self elevation is skipped on purpose and the script
simply asks the user to open an elevated prompt.
MIT. See LICENSE.