You wrote a script that checks a price, watches an API, posts to a webhook or scrapes a page every ten minutes. It works, as long as your computer stays on and the terminal stays open. To run a Python script 24/7, you need two things: a script built to run forever, and a machine that does.
What usually goes wrong
A script that runs fine for an hour on your laptop often dies overnight on a server, for three reasons:
- One exception kills everything. A timeout, a 502 from an API, a malformed JSON, and the whole process exits.
- Nobody sees why.
print()output is buffered when there is no terminal, so the last lines before a crash are lost. - Time drifts. A
while Truewithtime.sleep(600)shifts a little later on every run, by however long the run itself took.
The script below fixes all three.
A script built to run forever
main.py:
import logging
import os
import time
import requests
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
)
log = logging.getLogger("worker")
INTERVAL = int(os.environ.get("INTERVAL_SECONDS", "600"))
WEBHOOK_URL = os.environ["WEBHOOK_URL"]
def run_once():
r = requests.get("https://api.coinbase.com/v2/prices/BTC-EUR/spot", timeout=15)
r.raise_for_status()
price = r.json()["data"]["amount"]
requests.post(WEBHOOK_URL, json={"content": f"BTC is at {price} EUR"}, timeout=15)
log.info("posted price %s", price)
def main():
log.info("worker started, every %s s", INTERVAL)
while True:
started = time.monotonic()
try:
run_once()
except Exception:
log.exception("run failed, retrying next cycle")
elapsed = time.monotonic() - started
time.sleep(max(0, INTERVAL - elapsed))
if __name__ == "__main__":
main() requirements.txt:
requests>=2.32 What makes it reliable:
try/exceptaround one run, not around the program. A failed run is logged with its full traceback and the next one happens on schedule.timeout=on every request. Without it, one hung connection blocks the script forever, silently.loggingwith timestamps. You see when each run happened and what failed.- The interval subtracts the run time, so a slow run does not push every later run back.
- Configuration from environment variables. The webhook URL is a secret, and the interval can change without touching the code.
Scheduled jobs instead of a loop
For “every day at 8:00” rather than “every 10 minutes”, use the schedule package:
import time
import schedule
def daily_report():
...
schedule.every().day.at("08:00").do(daily_report)
schedule.every(15).minutes.do(run_once)
while True:
schedule.run_pending()
time.sleep(1) Times are in the server’s timezone, which is UTC on most hosts. Pass the timezone explicitly, .at("08:00", "Europe/Paris"), to stay on local time across daylight saving changes (it needs pytz in requirements.txt).
One catch: schedule does not catch exceptions. A job that raises stops the whole loop, so wrap each job in a try/except, the way main() wraps run_once above.
Run the Python script 24/7 on a server that never sleeps
Sign up on Bot-Hosting.net and start a new deployment:
- What are you deploying? Application, then Python. Python 3.14 is the default; pick an older version if a dependency needs it.
- Source: Archive with a
.zipof your folder, or GitHub for a repository. - Click Launch deployment. The entry file defaults to
main.py(change it in the Startup tab if needed).
In Environment, add WEBHOOK_URL (tick Secret) and optionally INTERVAL_SECONDS. Saving restarts the deployment: it installs requirements.txt, then runs your script unbuffered, so every log line shows up in the Console as it happens.
2025-06-10 09:00:00,112 INFO worker started, every 600 s
2025-06-10 09:00:00,481 INFO posted price 94231.55 EUR Files, data and output
The disk is persistent: a CSV, a SQLite database or a JSON state file written by the script is still there after a restart. You can browse and download them from the Files tab, and backups cover them. If the data matters, a PostgreSQL or MySQL deployment in the same account is a better home.
If your script also serves a page or an API, listen on 0.0.0.0 and the port in SERVER_PORT, then activate a free HTTPS address in the Domains tab.
Updating the script
Push changes from your editor with the Bot-Hosting CLI:
npm i -g bot-hosting
bh login
bh link
bh deploy Or keep the code on GitHub and enable Auto-pull at restart in the GitHub tab: every restart fetches the branch first. See deploy from GitHub.
When it crashes anyway
The Console recognises the classic failures, a missing module, a syntax error, a missing environment variable, an out-of-memory kill, and explains them in one line. Ask AI reads the traceback and your code and proposes the fix.
Cost
The free plan has 256 MB of RAM, no card and no sleep timer, and is renewed by hand every 4 days with one click. A script like the one above uses a small fraction of that. Paid plans start at 1 GB and renew automatically.