I gave my side project a sleep schedule on Krova Cloud to save money — power off at 1 AM, wake at 7. The surprise side effect: zero attack surface while asleep. Here's the setup, the cron, and the trade-offs.
At 3 a.m., my production server is off. Not down — off. On purpose.
It started as a money thing. The project has human hours: traffic ~8:00 to midnight, dead until morning. The old VPS ran 24/7 and I paid for eight idle hours every night. Also, 3 a.m. on a VPS is when the botnet zoo shows up — brute-force loops, scanner sweeps, auth-log horror anthologies.
Then it clicked: this project has a sleep schedule. Servers are allowed to have sleep schedules.
The setup
The project now lives on a Cube on Krova Cloud — a Firecracker microVM with its own kernel and no public IP (private NAT'd network, managed TLS ingress, only explicitly opened ports reachable). The sleep schedule is the dumbest automation I own:
# crontab -e
0 1 * * * krova cubes power-off web-1 # lights out: compute billing stops, disk preserved
0 7 * * * krova cubes wake web-1 # boots back up in seconds
Those are real CLI verbs (@krovacloud/cli), not pseudocode. Semantics per the docs:
- running → billed per minute for compute + disk
- stopped → billed for disk storage only
- data survives both directions; wake starts the Cube from its saved disk
So sleeping 8 hours a day removes ~a third of compute time from the bill, and the stopped hours cost pocket change. On my 2 vCPU / 4 GB / 40 GB Cube that's the difference between "hosting" and "rounding error." (krova pricing prints the per-resource hourly rates if you want to do your own math.)
The surprise: the 3 a.m. noise died
First week I checked the logs out of habit. Nothing. No brute force, no scanners. Of course:
- no SSH daemon listening
- no web server process
- nothing in memory, no kernel of mine executing
- nothing to scan, because there's no public address anyway
You cannot exploit a process that isn't running. The nighttime attack surface isn't small; it's zero. Awake, the Cube still isn't a scannable box — no public IP, default-deny inbound — so the daytime surface stays tiny too.
Verify the states yourself:
krova get web-1 --json | jq -r .state # "stopped" at 1:05 AM
krova cubes wake web-1
krova ssh web-1 -- uptime # back in seconds, disk intact
What sleep forced on my architecture (all good things)
- All state on disk. In-memory state dies at 1 a.m., so sessions in Redis-on-disk or DB rows, not globals. Sleep cured my lazy state management.
- Boot must be boring. Everything starts via systemd units; if wake + boot doesn't produce a working app with zero human input, that's a bug I fix in daylight.
- Health check = first request. I curl the domain after wake in the same cron pipeline; if boot ever fails, I know before users do.
0 7 * * * krova cubes wake web-1 && sleep 15 && curl -sf https://app.example.com/healthz || curl -sS -X POST "$ALERT_URL" -d 'text=web-1 failed to wake'
Objections, fairly handled
- Wake isn't instant. First morning request waits a few seconds for boot. Correct choice for side projects / internal tools / staging with quiet hours. Wrong choice for 24/7 global SaaS — don't sleep those.
- Sleep ≠ security while awake. It shrinks the window; it doesn't replace the walls. The daytime Cube still needs its real properties: no public IP, own kernel, explicit ports only. Krova gives you those; sleep is just schedule math on top.
- Timezones. If your users are global, your "quiet hours" may not exist. Look at actual traffic before copying my cron.
The reframe
We treat "always on" like a moral quality. For a lot of workloads it's just an expensive, attackable assumption. I added sleep to save money; the quietest security upgrade I've ever shipped was the side effect.
Give it a bed with no address. Let it sleep.
Does anyone else run real workloads on a sleep schedule? Or does powering off "production" at night terrify you? Which camp are you in, and what do your quiet hours look like? Comments open.
Top comments (0)