Migrate from crontab
Convert a crontab file to a SteadyCron manifest — map each entry to a job, and gain retries, alerting, and an audit log.
A crontab fires and forgets. There are no retries, no timeouts, no alert if the script fails silently, and no record of what ran or what it returned. This guide maps a typical crontab to a SteadyCron manifest and explains what you gain.
A typical crontab
# /etc/cron.d/myapp
MAILTO=""
# Weekly digest — every Monday at 09:00 Berlin time
0 9 * * 1 www-data /var/www/myapp/bin/send-digest.sh
# Nightly DB backup — every day at 02:00
0 2 * * * www-data /var/www/myapp/bin/backup.sh >> /var/log/backup.log 2>&1
# 15-minute health sweep
*/15 * * * * www-data /var/www/myapp/bin/health-check.sh
The equivalent SteadyCron manifest
namespace: myapp
channels:
- id: team-email
kind: email
email: oncall@myapp.com
jobs:
# HTTP job: SteadyCron calls the endpoint directly.
# Retries on failure; alerts via the channel if it keeps failing.
- id: weekly-digest
name: Weekly digest email
kind: http
method: POST
url: https://api.myapp.com/jobs/send-digest
schedule: "0 9 * * 1"
timezone: Europe/Berlin
timeout: 120
retries: 3
channel: team-email
# Heartbeat: the backup script pings SteadyCron after it completes.
# SteadyCron alerts if the ping goes missing or arrives late.
- id: nightly-backup
name: Nightly DB backup
kind: heartbeat
schedule: "0 2 * * *"
grace: 1800
channel: team-email
# Heartbeat: the health check script pings after each run.
- id: health-sweep
name: 15-minute health sweep
kind: heartbeat
schedule: "*/15 * * * *"
grace: 120
channel: team-email
Mapping rules
| Crontab concept | SteadyCron equivalent |
|---|---|
| Schedule expression | schedule — same 5-field cron syntax |
MAILTO="" (suppress mail) | Remove the channel field (no alerts) |
Timezone via TZ= env var | timezone field per job |
| Script that hits an endpoint | kind: http job |
| Script you control (runs on your server) | kind: heartbeat — script pings SteadyCron on success |
| Redirect to log file | SteadyCron stores full request/response logs; no redirect needed |
2>&1 (capture stderr) | SteadyCron captures both status and body from HTTP responses |
HTTP job vs heartbeat: which to use?
Use kind: http when your cron logic is behind an HTTP endpoint you own. SteadyCron
calls it on schedule, handles retries, records the response, and alerts on failure. Your
crontab entry and the shell script become unnecessary — the endpoint is the job.
Use kind: heartbeat when:
- The script runs directly on a server (shell, Python, PHP, etc.)
- You can’t or don’t want to expose an HTTP endpoint
- The script already exists and you just want monitoring
For heartbeats, add a single curl call to the SteadyCron ping URL at the end of your
script:
#!/bin/bash
set -euo pipefail
# ... your backup logic ...
# Signal success to SteadyCron
curl -fsS https://ping.steadycron.com/{your-token}
See Ping from any language for snippets in Python, Ruby, PHP, Node.js, and more.
What you gain
| crontab | SteadyCron | |
|---|---|---|
| Alerts on failure | No | Yes — email, Slack, Discord, Telegram, webhook |
| Alerts on missed run | No | Yes — with configurable grace period |
| Retries | No | Yes — configurable per job |
| Timeout enforcement | No | Yes — per job |
| Execution log | No (log rotation) | Yes — full request/response history |
| Version controlled | Not easily | Yes — manifest lives in your repo |
| Per-job timezone | Via TZ= workarounds | Native timezone field |
| PR review for changes | No | Yes — steadycron plan in CI |
Next steps
- Apply the manifest:
steadycron sync manifests/myapp.yaml - For heartbeat jobs: add the ping URL to your scripts
- Set up CI to review changes: CI/CD setup
- For existing dashboard jobs: Migrate to IaC