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 |
Automated import
Instead of writing the manifest by hand, use steadycron import crontab to generate it
automatically from an existing crontab file:
# From a file
steadycron import crontab /etc/cron.d/myjobs -o steadycron.yaml
# From crontab -l (current user's jobs)
crontab -l | steadycron import crontab -o steadycron.yaml
# Preview without writing
steadycron import crontab mycron.txt --dry-run
The importer maps each entry to a job kind automatically:
| Command type | Becomes |
|---|---|
curl/wget/bare https://… URL | http job — URL, method, headers extracted |
| Anything else | heartbeat monitor |
Use --as http or --as heartbeat to force all entries to a specific kind.
Crontab conventions handled: MAILTO= and PATH= env-assignment lines are ignored; a
# comment immediately above an entry becomes the job name; macros like @daily, @weekly,
@monthly are expanded to 5-field cron expressions; system crontab format (6th field is a
username) is auto-detected or forced with --system.
After import, review the manifest, then sync:
steadycron validate steadycron.yaml
steadycron sync steadycron.yaml --namespace prod
Next steps
- Apply the manifest:
steadycron apply --prune 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: From dashboard to code