Scheduling
Give a task a cron expression and RunWisp starts
it on that schedule. Every scheduled start becomes a run in the history, with
its exit code and output.
[tasks.heartbeat]cron = "*/5 * * * *"run = "/usr/local/bin/heartbeat"A task without cron runs only when you start it. Scheduled runs use each
parameter’s default value.
Cron syntax
Section titled “Cron syntax”The usual 5 fields: minute, hour, day of month, month, day of week.
| Expression | Runs |
|---|---|
* * * * * |
every minute |
*/5 * * * * |
every 5 minutes |
0 * * * * |
at the top of every hour |
0 2 * * * |
every day at 02:00 |
30 9 * * 1-5 |
at 09:30, Monday to Friday |
0 0 1 * * |
at midnight on the 1st of every month |
@hourly |
at the top of every hour |
@daily / @midnight |
every day at 00:00 |
@weekly |
Sundays at 00:00 |
@monthly |
the 1st at 00:00 |
@yearly / @annually |
January 1st at 00:00 |
@every 1h30m |
every 90 minutes |
Fields take lists (1,15), ranges (1-5), and steps (*/10). In the day of
week field, both 0 and 7 mean Sunday. @every is a fixed interval that
doesn’t line up with the clock.
For more than once a minute, add a seconds field in front. Plain 5-field
expressions run at second :00.
| Expression | Runs |
|---|---|
*/30 * * * * * |
every 30 seconds (:00, :30) |
15 * * * * * |
once a minute, at second :15 |
0 */5 * * * * |
every 5 minutes, at second :00 |
runwisp validate checks every expression without starting anything.
Running in another timezone
Section titled “Running in another timezone”Cron expressions use the host’s timezone unless you set one. Set it for the whole daemon, or per task:
[daemon]timezone = "Europe/Bratislava" # every task without its own timezone
[tasks.nightly-backup]cron = "30 2 * * *"timezone = "Atlantic/Faroe" # this task onlySee [daemon] timezone and
timezone. An unknown zone name (a typo, or a
container image without tzdata) stops the config from loading; RunWisp never
falls back to UTC silently. The TUI start screen and the Web UI connection
indicator show which zone is in use.
Daylight saving time
Section titled “Daylight saving time”A schedule runs once per wall-clock time, even across a clock change:
- Clocks go back: a time like 02:30 happens twice, but the task runs once.
The second one is recorded in the history as
dst_skipped. - Clocks go forward: a time that doesn’t exist that day (02:00 when the clock jumps from 01:59 to 03:00) runs once at 03:00, instead of being skipped until the next day.
@every schedules are not affected. They count real elapsed time, not wall-clock
times, so @every 30m keeps running every 30 minutes through a clock change.
Catching up after downtime
Section titled “Catching up after downtime”When the daemon was down (a reboot, a deploy, a crash), scheduled runs didn’t
happen. catch_up decides how many of them
run when the daemon starts again:
[tasks.metrics-rollup]cron = "*/15 * * * *"catch_up = 1 # default: run the most recent missed oneUse 0 for probes that only care about fresh data, and a higher number for
jobs where each run handles its own slice of time. A value above 1 needs
on_overlap = "queue", or the config is
rejected. RunWisp counts missed runs
from the task’s last run, or from when it first saw the task, so a new install
doesn’t replay old history.
If more runs were missed than catch_up allows, the oldest are dropped and the
daemon logs a warning with the task name and how many were dropped.
Missed-run alerts
Section titled “Missed-run alerts”A missed run counts as a failure by default. On the restart that finds the gap,
RunWisp records one run with end_reason = "missed" and sends a failure alert
naming the task and how many runs were missed. It goes to the bell and to every
channel that gets the task’s failures. This happens whatever catch_up is set
to, and the alert always reports the full count.
For a task that is expected to miss runs (a laptop that’s off at night), take
missed out of its failures:
[tasks.daytime-sync]cron = "*/30 9-17 * * 1-5"failures = ["-missed"] # still recorded, but not a failure and no alertPut the same line in [defaults] to do it for every
task.
Spreading out start times
Section titled “Spreading out start times”When many tasks share a start time (say, 0 3 * * *), they all start at the
same second. jitter lets RunWisp spread them
out:
[tasks.backup-a]cron = "0 3 * * *"jitter = "30m"
[tasks.backup-b]cron = "0 3 * * *"jitter = "30m"RunWisp starts jittered tasks one at a time. If nothing else jittered is
running, a task starts right on time. If several are due together, they take
turns, and none starts later than its jitter. Each task’s latest start time is
fixed when the daemon starts, so the same config always gives the same order.
The run history shows both the scheduled time and the real start.
Pausing a schedule
Section titled “Pausing a schedule”Pause a task to stop its schedule without editing runwisp.toml, for example
while something it depends on is down:
runwisp pause nightly-backuprunwisp resume nightly-backupIn the Web UI, click the schedule next to the task name at the top of its page.
In the TUI, press p on the task. The REST API has POST /api/tasks/{task}/pause and .../resume.
While a task is paused, its scheduled times are skipped. They add no runs and
are not caught up when you resume or when the daemon restarts. You can still run
the task by hand. runwisp status lists paused tasks.
The pause survives runwisp reload and restarts. Pausing needs
manual_trigger on, and a reload that
removes the task’s cron or sets manual_trigger = false clears the pause with
a warning. A task cron still runs (held) can’t
be paused.
After a crash
Section titled “After a crash”On a normal shutdown, every run gets a final status. After a hard crash (power
loss, kill -9) the daemon can’t do that, so on the next start every run still
marked as running is ended as crashed with exit code -2. These runs are not resumed.
Catch-up may then start a fresh run. No run is ever left stuck on “running”.
Crontab jobs
Section titled “Crontab jobs”Jobs read from a crontab with
include_cron follow cron’s rules: a
missed job is recorded but never run later.
Changing the schedule
Section titled “Changing the schedule”RunWisp reads runwisp.toml at startup and on runwisp reload, never on its
own. A task added by a reload gets no catch-up. See
Reload & restart.