← All posts

Run a Bot 24/7 as a Windows Service: What Breaks

Running a bot 24/7 as a Windows service taught me the code is the easy half. What breaks is ops: power, clocks, updates and silent restarts.

Run a Bot 24/7 as a Windows Service: What Breaks

Running a bot 24/7 as a Windows service means handing the process to the operating system, so it starts on boot, restarts after a crash, and keeps running when nobody is logged in.

Short version: the code was never the hard part. Every failure I hit over months of uptime was an ops failure, not a logic failure.

For the last few months I've been running a side project called Helios. It's an autonomous system. It wakes up on its own, does its work, and reports back. What it decides isn't the subject of this post. That part is mine, it's boring to everyone else, and it's the part I'm least qualified to be interesting about. What's worth writing down is everything around it.

I build websites for a living. A website fails politely. Someone reloads the page, and you hear about it from a log or a monitor. A system that runs unattended fails in silence, at 3am, and by the time you notice it has been broken for eleven hours. That difference changed how I build.

Why run a bot as a Windows service instead of a terminal window?

Because a terminal window depends on you. A service doesn't.

The first version ran the way every first version runs: a minimised terminal I promised myself I wouldn't close. Then Windows installed an update. Then I rebooted for something unrelated. Then I closed the wrong window.

The fix took an afternoon and has no glamour to it. I made it a real Windows service with NSSM, which wraps any executable as a service. It starts on boot. It restarts after a crash. It doesn't care whether anyone is logged in. Nothing about the application changed. What changed is that its life no longer depends on my attention span.

Here's the rule I'd keep. The moment a process needs to outlive your session, it stops being a script and becomes a service. Treat it like one.

What actually breaks when software runs 24/7?

Not the algorithm. Here's the honest list, in order of how often it happened:

Not one of those is a logic bug. Every one is an ops bug. Software that runs by itself isn't really an application any more. It's a small unattended system, and unattended systems break in boring physical ways: power, disk, clock, network, and the operating system deciding it knows better.

Why should your system report its own restarts?

Because a restart is the earliest warning you get, and by default it leaves no trace.

The change that helped my confidence most was tiny. The system already sent me a short daily summary. I added one line to it: every process restart since the last summary, with timestamps. That's the whole feature.

Before, an overnight restart was invisible. The system came back up, carried on, and looked healthy at breakfast. Now a restart is a line in a message I already read. Twice that line has been the first evidence of a real problem, hours before anything else would have shown it. The second time it was the hardware. Three restarts in one night is not a software pattern. Seeing them stacked told me to go and look at the box instead of the code.

The general rule: your system should report the things that happened to it, not just the things it did. Work output tells you it's running. Restarts, retries, skipped cycles and dropped connections tell you how it's running. The second list is the one that predicts trouble.

How do you make a process safe to kill at any moment?

You assume it will be killed, and you design for that early.

State goes to disk, not to memory, at every point where losing it would be expensive. Startup checks whether the last run finished cleanly. If it didn't, it recovers instead of guessing. Anything that must happen once a day is guarded by a check on whether it already happened today, because a restart at the wrong minute will cheerfully do it twice. Nothing important lives only in a variable.

None of that is clever engineering. It's just accepting that "it crashed" is a normal event with an ordinary recovery path, rather than an exception you'll be awake to babysit.

What does this change for a client's website?

More than I expected, which is why this is on a web developer's blog.

The same instincts apply to a nightly job, a scheduled export, a webhook consumer, a newsletter send. That code is never the interesting part of a build, so it usually gets the least care. No restart story. No visibility. No record of the runs that quietly didn't happen. Then a client mentions in passing that they haven't had the weekly report "for a while", and nobody can say since when.

Now anything I ship that runs unattended gets three things before I call it done. It runs as a supervised service. It reports what happened to it, not only what it produced. And it can be killed at any moment without leaving damage behind. That's maybe an extra half day of work. It's the difference between a system you operate and one that quietly stops.

It's the same reasoning behind the plugin I built to automate my own website chores. Automation you can't see is automation you can't trust. It's also why I fuss over things like database connections in scheduled jobs, which look like a detail until that job is the only thing awake at 4am. There's more of this sort of thing on the blog.

More on Helios when it earns the right to be written about. For now the transferable lesson is this: the hard part of automation isn't the automation. It's everything that happens while you're asleep.

Building something that needs to run whether or not anyone is watching? That's the kind of work I do: sites, services, and the plumbing that keeps them alive.