−25%

on annual Windows plans, until 31 Oct. See plans

EQVPS
Get started

How to run a script 24/7 on Windows Server: survives logoff, crashes and reboots

Keep a bot or script running around the clock on a Windows Server 2022 VPS with Task Scheduler alone: start at boot without anyone logged in, restart within a minute after a crash, and never run two copies. We killed and rebooted it to check.

The usual way to run a bot on a Windows VPS is to start it in an RDP session and close the window. It works until an update reboots the server at 3 a.m. and nothing starts it again. Task Scheduler, which is already on every Windows Server, fixes this with no extra software. The setup below:

  • starts the script at boot, before anyone logs in;
  • restarts it within a minute if it crashes or is killed;
  • never runs two copies at once.

We tested all three on our Windows Server 2022 image: killed the process, rebooted the server, and counted instances.

The example script

A stand-in for your bot: it writes a line every 10 seconds, so it's easy to see whether it's alive.

New-Item -ItemType Directory -Force C:\bots | Out-Null
Set-Content C:\bots\heartbeat.ps1 -Encoding ascii -Value @'
while ($true) {
    Add-Content -Path C:\bots\heartbeat.log -Value ("{0:u} pid {1}" -f (Get-Date), $PID)
    Start-Sleep -Seconds 10
}
'@

Register the task

Run in an elevated PowerShell:

$action   = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-NoProfile -ExecutionPolicy Bypass -File C:\bots\heartbeat.ps1' -WorkingDirectory 'C:\bots'
$atBoot   = New-ScheduledTaskTrigger -AtStartup
$everyMin = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 1)
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances IgnoreNew -StartWhenAvailable
Register-ScheduledTask -TaskName 'mybot' -Action $action -Trigger $atBoot, $everyMin -Settings $settings -User 'SYSTEM' -RunLevel Highest
Start-ScheduledTask -TaskName 'mybot'

What each piece does:

  • -AtStartup starts the bot when Windows boots, with no logged-in user.
  • The one-minute repeat re-runs the task every minute. If the bot is alive, nothing happens; if it died, it starts again.
  • -MultipleInstances IgnoreNew is what makes the repeat safe: a run that finds the bot already running is skipped.
  • -ExecutionTimeLimit 0 turns off the default 72-hour limit, after which Task Scheduler would stop the bot.
  • -User 'SYSTEM' runs it without a password or a session. SYSTEM has full rights on the server, so run only your own code this way.

For Python, replace the action:

$action = New-ScheduledTaskAction -Execute 'C:\Program Files\Python312\python.exe' -Argument 'C:\bots\bot.py' -WorkingDirectory 'C:\bots'

What we checked

Crash. We killed the bot's process with Stop-Process -Force. 46 seconds later the minute trigger had started a new one, and there was exactly one copy.

Reboot. We restarted the server and didn't log in. 40 seconds after boot the bot was writing to its log, with no user sessions on the server.

What didn't work. Two things look right and aren't:

  1. "If the task fails, restart every…" We set it, killed the process, and the task sat in Ready for minutes. It reacts to the task failing to start, not to your program dying later.
  2. A wrapper loop (while ($true) { & bot; sleep 5 }). It restarts a crashed bot, but if the wrapper itself dies, its child keeps running, and the next start launches a second bot. We ended up with two copies, which for a Telegram bot means two pollers fighting over the same updates.

Check on it

Get-ScheduledTask -TaskName mybot | Select-Object TaskName, State
Get-ScheduledTaskInfo -TaskName mybot | Select-Object LastRunTime, NextRunTime
Get-Content C:\bots\heartbeat.log -Tail 5

To stop it for maintenance, Disable-ScheduledTask mybot first (otherwise the minute trigger starts it again), then stop the process. Enable-ScheduledTask mybot brings it back.

Logs

The task doesn't capture output. Have the bot write its own log file, as the example does, or redirect output in a small .cmd launcher and point -Execute at that file.

Which plan

A PowerShell or Python bot uses little CPU, but Windows itself takes 1 to 1.3 GB of RAM at idle on our image. Windows-Small (4 vCPU, 4 GB RAM, 50 GB, $11/mo) is the right size for a bot or two. On a NAT plan, outgoing connections (Telegram, exchange APIs, webhooks you call) work as usual. If the bot needs to receive webhooks or other inbound traffic, take Windows Small-IP ($28/mo) with its own IPv4.

Next steps: lock down RDP and, if you'd rather manage the bot over SSH, install OpenSSH. Payment is in USDT, USDC or ETH, and Windows is Server 2022 with your own licence (BYOL). All plans: Windows VPS.

FAQ

Why not just leave the script running in my RDP session?

It works until something ends the session: signing out, an update reboot, or a crash. Closing the RDP window (disconnect) keeps the session alive, but signing out kills everything in it, and after a reboot nobody is logged in to start it again. A scheduled task doesn't depend on a session.

Doesn't Task Scheduler have a 'restart on failure' setting?

It does, but it didn't help in our test. When we killed the script's process, the task simply went to Ready and stayed there. That setting reacts to the task failing to start, not to your program dying later. The repeating trigger in this guide is what actually brings it back.

Can two copies of my bot end up running?

Not with this setup. The task runs the bot itself (no wrapper script) and uses MultipleInstances IgnoreNew, so the once-a-minute trigger does nothing while the bot is alive. A wrapper loop is what creates duplicates: kill the wrapper and its child keeps running while a new wrapper starts another one. We saw exactly that.

Does it work for Python or Node.js?

Yes. Put the interpreter in -Execute and the script in -Argument, with full paths, for example -Execute 'C:\Program Files\Python312\python.exe' -Argument 'C:\bots\bot.py'. Install the interpreter for all users so the SYSTEM account can find it.

Comments

No comments yet. Be the first.

Leave a comment

Comments are moderated before they appear.