1. Why Move a Script Off Your Computer

Most useful Python scripts are small. One checks a price every hour. One emails a sales summary every morning. One backs up a database every night. They work well on your laptop until the day your laptop is closed at the wrong time.

A scheduled script needs three things your own computer cannot promise. It needs to be switched on at the scheduled time. It needs an internet connection. It needs someone to notice when it fails. A cloud scheduler covers all three.

This guide explains cron schedules, compares five ways to run Python on a timer and shows how to write scripts that behave well when nobody is watching.

2. Cron Schedules in Five Minutes

Almost every scheduler uses cron syntax. A cron schedule has five fields separated by spaces. Each field narrows down when the job runs.

minute  hour  day-of-month  month  day-of-week
  0      7         *          *         *
  • Minute runs from 0 to 59.
  • Hour runs from 0 to 23.
  • Day of the month runs from 1 to 31.
  • Month runs from 1 to 12.
  • Day of the week runs from 0 to 6, and Sunday is 0.

A star means "every." A number means "only this one." A slash means "every so many." These examples cover most real jobs.

ScheduleMeaning
*/5 * * * *Every 5 minutes
0 * * * *Every hour, on the hour
0 7 * * *Every day at 7:00
30 8 * * 1-5Weekdays at 8:30
0 0 * * 0Every Sunday at midnight
0 3 1 * *The 1st of every month at 3:00

Check which time zone your scheduler uses. Most cloud schedulers, including Cloudilio, use UTC. A job set for 0 7 * * * runs at 7:00 UTC, which is 2:00 or 3:00 in the morning in New York, depending on daylight saving time.

3. Five Ways to Run a Script on a Schedule

GitHub Actions

GitHub Actions can run a workflow on a cron schedule from your repository. It is free within your plan's minutes and needs no server. GitHub documents that scheduled runs can start late during busy periods, and that schedules in inactive public repositories may be switched off. Your script also starts from a clean machine each time, so it cannot keep files between runs. It suits light jobs that can tolerate some delay.

AWS Lambda with EventBridge

Lambda runs a Python function, and an EventBridge schedule triggers it. You pay only for the seconds your code runs. A single run can last at most 15 minutes. You also work with IAM roles, deployment packages and the AWS console. It is a strong option if your company already uses AWS.

Google Cloud Functions or Cloud Run with Cloud Scheduler

Google's version of the same idea. Cloud Scheduler calls your function or container on a cron schedule. Setup involves a Google Cloud project, service accounts and permissions. It suits teams already on Google Cloud.

Cron on a VPS

A small Linux server runs cron out of the box. Add a line with crontab -e, and your script runs forever. You control everything, and you also maintain everything. Cron sends no alert when a job fails unless you set up email or monitoring yourself.

0 7 * * * cd /home/me/reports && .venv/bin/python daily_report.py >> report.log 2>&1

A Python host with built-in scheduled tasks

Some Python hosts include a scheduler next to your app. Cloudilio is one. Your scheduled task runs with your app's code, packages, secrets and data folder, so a nightly job can read the same database your web app uses. You get a run history with the output of every run, a time limit for each task and a Run now button for testing.

4. Comparison Table

Feature GitHub Actions AWS Lambda VPS cron Cloudilio
Setup effort Low High Medium Low
Longest run Hours 15 minutes No limit Up to 1 hour
Keeps files between runs No No Yes Yes, in /app/data
Runs on time Can be delayed Yes Yes Yes
Output of past runs Yes In CloudWatch If you log it Yes, per run
Shares data with a web app No Through a database Yes Yes

5. Write Scripts That Run Well Unattended

A script that runs while you watch can still fail at 3 a.m. These five habits prevent most of those failures.

Exit with an error code when something fails

Schedulers decide success from the exit code. Zero means success. Anything else means failure. An uncaught exception already exits with code 1. If you catch errors to log them, exit with a non-zero code afterwards so the failure still shows.

import logging
import sys

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")

def main() -> None:
    logging.info("Starting the nightly import")
    # ...your work here
    logging.info("Imported 1,204 rows")

if __name__ == "__main__":
    try:
        main()
    except Exception:
        logging.exception("The nightly import failed")
        sys.exit(1)

Print what happened

Log a line at the start, a line with the result and a line for every error. When a run fails next month, those lines will tell you why in seconds.

Make each run safe to repeat

Sometimes a job runs twice or stops halfway. Design for it. Use "insert or update" instead of plain inserts, and record what you already processed. A repeated run should change nothing.

Set timeouts on network calls

A request with no timeout can hang until the scheduler kills it. Always pass a timeout.

import requests

response = requests.get("https://api.example.com/prices", timeout=15)
response.raise_for_status()

Keep secrets out of the script

Read API keys and passwords from environment variables. Every scheduler listed above can supply them.

6. Schedule a Script on Cloudilio

On Cloudilio, a scheduled task belongs to an app. The app can be a full website, or it can be a folder that holds only your scripts.

  1. Put your scripts in a project. Add a requirements.txt that lists the packages your scripts import. If the project is only scripts, also add a small main.py that keeps running, such as a loop that sleeps for an hour.
  2. Create and publish the app. Upload a zip of the folder or link GitHub. Cloudilio installs your packages.
  3. Add your secrets. Add API keys on the app's Secrets page. Tasks receive them as environment variables, just like the app does.
  4. Create the task. Open the Tasks page and add a scheduled task. Give it a name and a command, such as python daily_report.py. Pick a schedule from the list, or enter your own cron schedule. Set a time limit of up to one hour.
  5. Test it. Press Run now. The run appears in the history with its exit code and full output.

Each run starts fresh with your app's code and packages. Files the task writes in /app/data stay there for the next run and for your web app. Everything else is cleared when the run ends.

7. Three Ready-to-Use Examples

Watch a web page and send an alert

This script checks a product page and posts to a Slack or Discord webhook when the price drops. Run it every 15 minutes.

import json
import os
import re
from pathlib import Path

import requests

STATE = Path(os.environ.get("APP_DATA_DIR", ".")) / "last_price.json"
URL = "https://example.com/product/123"

html = requests.get(URL, timeout=15).text
price = float(re.search(r'"price":\s*"?([\d.]+)', html).group(1))
last = json.loads(STATE.read_text())["price"] if STATE.exists() else None

if last is not None and price < last:
    requests.post(os.environ["WEBHOOK_URL"], json={"content": f"Price dropped to ${price}"}, timeout=15)

STATE.write_text(json.dumps({"price": price}))
print(f"Price is {price}, last was {last}")

The last price is saved in the data folder, so the script remembers it between runs.

Email a daily summary

This script counts yesterday's signups in a SQLite database and emails the number. Run it every morning.

import os
import smtplib
import sqlite3
from email.message import EmailMessage

db = sqlite3.connect(os.path.join(os.environ["APP_DATA_DIR"], "app.db"))
count = db.execute(
    "SELECT COUNT(*) FROM users WHERE created_at >= date('now', '-1 day')"
).fetchone()[0]

msg = EmailMessage()
msg["Subject"] = f"{count} new signups yesterday"
msg["From"] = os.environ["SMTP_FROM"]
msg["To"] = os.environ["REPORT_TO"]
msg.set_content(f"You had {count} new signups in the last 24 hours.")

with smtplib.SMTP_SSL(os.environ["SMTP_HOST"], 465, timeout=30) as smtp:
    smtp.login(os.environ["SMTP_USER"], os.environ["SMTP_PASSWORD"])
    smtp.send_message(msg)
print(f"Sent the report: {count} signups")

Clean up old files

This script deletes uploads older than 30 days. Run it every night.

import os
import time
from pathlib import Path

folder = Path(os.environ["APP_DATA_DIR"]) / "uploads"
cutoff = time.time() - 30 * 24 * 3600
removed = 0
for path in folder.glob("*"):
    if path.is_file() and path.stat().st_mtime < cutoff:
        path.unlink()
        removed += 1
print(f"Removed {removed} old files")

8. Questions People Ask

Can I run a Python script every minute in the cloud?

Some schedulers allow it. GitHub Actions and Cloudilio allow every five minutes at most. If you need work every few seconds, run one long-lived script with a loop and a time.sleep() instead of a scheduled job.

What happens if a run takes longer than the time limit?

The scheduler stops it and marks the run as failed. On Cloudilio, the run history shows that the time limit was reached. Split long jobs into smaller batches, or raise the limit up to one hour.

Can a scheduled task use my web app's database?

On Cloudilio, yes. Tasks run with the same code, secrets and data folder as your app. A nightly job can read and update the same SQLite database your site uses. Keep write-heavy tasks short so they do not block your site for long.

Is GitHub Actions good enough for scheduled scripts?

For light jobs that can run a little late, yes. For jobs that must run on time, keep files between runs or share data with a live app, a dedicated scheduler is more dependable.

Cloudilio Engineering Team
Application Delivery Group • Cloudilio

Cloudilio hosts Python apps. You publish Flask, Django, FastAPI, Streamlit and plain Python scripts without managing a server, and your app's data stays safe between updates.