There are two ways out and this covers both: move to a tier that stays up, or pay the allowance to hold this one open. Scope is web services that idle out -- databases sleep too, by different mechanics and with a different answer, and merging the two produces advice that is wrong for both.
Why It Sleeps
Render's documentation is specific: a free web service is spun down after 15 minutes without inbound traffic, and that counts both HTTP requests and WebSocket messages from existing connections. Render puts the spin-up at about a minute; its own dashboard warns of 50 seconds or more.
For a portfolio or an internal tool, that is fine. For anything a stranger might open, a 50-second blank screen is the whole first impression.
Option 1: Move to a Host That Does Not Sleep
This is the answer for most people, and it is usually less work than maintaining a pinger forever. Free tiers that stay up do still exist; each asks for a different compromise.
| Host | Sleeps? | The trade |
|---|---|---|
| Northflank | No — its pricing page says "always-on-compute" | Card verified at signup, not charged |
| Oracle Cloud Always Free | No — a real VPS | You are the sysadmin; card required |
| Cloudflare Workers | No — nothing to idle | 10 ms CPU per request; cannot run a process |
| Serv00 | No — long-running processes allowed over SSH | No card, but the account needs periodic renewal |
| Render | Yes, after 15 min | Easiest deploy of the five |
Two of those trade the sleep problem for a different one. Cloudflare Workers never idles because there is no process to idle -- which also means it cannot run one; a 10 ms CPU ceiling per request rules out anything long-running. Oracle gives you a real machine that never stops, and hands you the sysadmin job with it.
Serv00 deserves a caveat: it allows long-running processes over SSH without a card, which makes it the closest thing to a free VPS on this list, but the account itself needs periodic renewal. You have swapped keeping a service awake for keeping an account alive.
Option 2: Keep It Awake
Worth it when Render is where you want to be, you have exactly one service, and moving costs more than the workaround. Any HTTP request arriving more often than the idle timeout works; against a 15-minute window, every 10 minutes is the standard choice.
| Pinger | Free | Card | Minimum interval |
|---|---|---|---|
| cron-job.org | Yes | No | 1 minute |
| UptimeRobot | Yes | No | 5 minutes |
GitHub Actions schedule | Yes | No | Not recommended — see below |
The UptimeRobot link is an affiliate link. Its free plan is enough for this and we earn nothing if you stay on it, which we expect most readers will. cron-job.org pays us nothing and is listed first because its interval is tighter.
What Else UptimeRobot Does
Worth knowing if you are setting one up anyway, because pinging is the smallest thing it does. It is a monitoring product: the periodic request is the mechanism, the alert is the point. Eight monitor types — HTTP/HTTPS, keyword, ping, TCP port, cron/heartbeat, DNS change, response time, and SSL/domain expiry.
The heartbeat monitor is the interesting one here, and it runs in the opposite direction: instead of you pinging your service, your job pings UptimeRobot, and you are alerted when it stops. That catches the failure this article sets you up for — when the monthly instance hours run out, the service simply stops, with no notification, at whatever hour of the night the allowance happens to expire.
The free plan covers 50 monitors at 5-minute checks, 5 alert contacts, one status page, and two months of log retention. Alerts go by email; Slack, Telegram and Teams start on the paid Solo tier.
One trap worth naming: on the free plan an HTTPS monitor does not check the certificate. SSL and domain expiry monitoring is paid, so "I am monitoring the https URL" does not mean you will hear about a certificate about to lapse.
Why Not GitHub Actions
It is the obvious free scheduler and it fails here in two documented ways. GitHub's own documentation states that in a public repository, scheduled workflows are automatically disabled when no repository activity has occurred in 60 days. A keep-alive has to run forever; a repo you have stopped committing to goes quiet, the workflow is switched off, and nothing tells your service. It starts sleeping again two months later.
The same page notes the schedule event can be delayed under load,
and that "if the load is sufficiently high enough, some queued jobs may be
dropped". A dropped run against a 15-minute timeout is a spin-down.
What Staying Awake Costs
This is the part the "just ping it" advice leaves out, and it stays invisible until services start stopping.
Render grants 750 free instance hours per workspace per calendar month — per workspace, not per service. A 31-day month is 744 hours.
| Hours/month | |
|---|---|
| Free allowance, whole workspace | 750 |
| One service, never sleeping | ~744 |
| Left for everything else | ~6 |
You can keep exactly one free service awake. Keep two and both run out before the month does — and because the allowance is shared across the workspace, the second one takes the first one down with it.
The Case Where It Will Not Even Deploy
One workload hits a wall before the spin-down: a process with no HTTP surface at all. A Telegram bot on long polling, a Discord gateway client, a queue consumer — each talks outward and receives nothing inbound.
Render's free plan covers three service types: web services, Postgres, and Key Value instances. There are no free background workers. A process that opens no port is not a web service, so there is nowhere on the free plan to put it — and deployed as one it is idle from the moment it starts, because long polling produces no inbound request to reset the timer.
Both problems have one answer: open a port you did not otherwise want.
import os, threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Cache-Control", "no-store")
self.end_headers()
self.wfile.write(b"ok")
def do_HEAD(self): # many pingers send HEAD, not GET
self.send_response(200)
self.end_headers()
def log_message(self, *args): # a ping every 10 min is 4,320 lines/month
pass
def start():
port = int(os.environ.get("PORT", 10000)) # the platform injects PORT
srv = ThreadingHTTPServer(("0.0.0.0", port), Handler)
threading.Thread(target=srv.serve_forever, daemon=True).start()
start()
bot.run() # or whatever blocks forever
Three details decide whether it works. start() must run
before the line that blocks forever, or the port never opens and the
deploy is rolled back as unhealthy — which looks like a broken build, not a
misplaced line. Bind 0.0.0.0, not 127.0.0.1, or the
platform's router cannot see it. And silence log_message, or a
ten-minute ping writes about 4,320 identical lines a month into logs you deploy
to read.
Thirty lines of standard library, worth writing yourself. A version with the Node equivalent is at github.com/build996/free-tier-keepalive (MIT, no dependencies).
If your bot platform supports webhooks, switching off long polling removes this entire section: inbound requests are exactly what a webhook produces.
FAQ
Is pinging your own service against the rules?
It is not circumventing a limit — the hours are metered and deducted from your allowance exactly as intended. You are spending your monthly grant continuously rather than intermittently. The constraint that bites is the 750-hour budget, not a policy.
Why not use a shorter interval to be safe?
It changes nothing. The instance is either awake or it is not; pinging every minute keeps it awake exactly as well as every ten minutes, and burns the same hours.
Do databases sleep too?
Yes, and differently enough that the advice here does not transfer. Neon autosuspends its compute and resumes on the next query in seconds; Supabase pauses a whole project after about a week of inactivity, and that is not something an HTTP ping to your website prevents. Applying this article's fix to either would be wrong in one case and useless in the other. Its own write-up.
My deploy says "no open ports detected" — what did I get wrong?
Almost always one of two things: the server started after the
blocking call, so it never ran, or it bound 127.0.0.1 instead of
0.0.0.0. Both look identical from the platform's side.
What happens when the free hours run out?
The service stops until the allowance resets at the start of the next calendar month. Hours do not roll over. This is why the one-service limit matters — running out is silent until something is down.