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.
| Schedule | Meaning |
|---|---|
| */5 * * * * | Every 5 minutes |
| 0 * * * * | Every hour, on the hour |
| 0 7 * * * | Every day at 7:00 |
| 30 8 * * 1-5 | Weekdays at 8:30 |
| 0 0 * * 0 | Every 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.
- Put your scripts in a project. Add a
requirements.txtthat lists the packages your scripts import. If the project is only scripts, also add a smallmain.pythat keeps running, such as a loop that sleeps for an hour. - Create and publish the app. Upload a zip of the folder or link GitHub. Cloudilio installs your packages.
- Add your secrets. Add API keys on the app's Secrets page. Tasks receive them as environment variables, just like the app does.
- 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. - 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 • CloudilioCloudilio 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.