[daemon]
[daemon] holds settings for the whole daemon. The table is optional. A change
to any [daemon] key except include and include_cron needs
runwisp restart; runwisp reload rejects it.
The config path, data directory, and listen address are command-line flags, because they are needed before the config file is read.
[daemon]shutdown_timeout = "10s"timezone = "Europe/Bratislava"external_url = "https://runwisp.example.com"check_updates = truetls = "off"tls_cert = ""tls_key = ""metrics_enabled = falsemetrics_listen = ""trusted_proxies = ["127.0.0.1/32"]include = ["conf.d/*.toml"]# include_cron = ["/etc/crontab", "/etc/cron.d/*"]shutdown_timeout
Section titled “shutdown_timeout”type: duration default: "10s"
How long the daemon waits for running tasks and services to exit on shutdown. After that, it SIGKILLs what is left.
- Each unit’s
graceful_stopmust fit inside this. If one is longer, the daemon warns about it by name at boot, and at shutdown the unit may be killed before its own grace window ends. - To fix the warning, raise
shutdown_timeoutor lower that unit’sgraceful_stop.
[daemon]shutdown_timeout = "30s"timezone
Section titled “timezone”type: string default: the host zone (UTC if it can’t be detected)
The IANA zone used to read cron for every task
that doesn’t set its own timezone. An
unknown zone name is rejected at load.
[daemon]timezone = "Europe/Bratislava"external_url
Section titled “external_url”type: string default: unset
The public address of this daemon’s Web UI, for example
https://runwisp.example.com behind a reverse proxy or http://192.168.1.50:9477
on a LAN. Notifications (Slack, Telegram, and others) add a “View run” link of
the form <external_url>/tasks/<task>/<id>.
- Must be
httporhttpswith a host. A trailing slash is removed. - The daemon never connects to this URL.
- When unset, notifications leave out the link.
[daemon]external_url = "https://runwisp.example.com"check_updates
Section titled “check_updates”type: boolean default: true
Checks for a newer release (usually every 6 hours) and shows an amber marker
next to the version in the Web UI and TUI. It never downloads or installs
anything. It also lets the Web UI ask for a quick rating, at most once a
month. Nothing is sent unless you press Submit, and then only your rating,
optional note, and RunWisp version. Set false to keep RunWisp fully
offline.
[daemon]check_updates = falsetls, tls_cert, tls_key
Section titled “tls, tls_cert, tls_key”tls: enum, default "off" · tls_cert: string, default unset ·
tls_key: string, default unset
How the HTTP server does TLS. The bind address comes from --host.
| Setting | Loopback bind (127.0.0.1, default) |
Other bind (0.0.0.0, LAN IP) |
|---|---|---|
tls = "off" |
HTTP | HTTP, with a startup warning |
tls = "auto" |
HTTP | HTTPS, self-signed certificate |
tls_cert + tls_key |
HTTPS, your certificate | HTTPS, your certificate |
[daemon]tls = "auto"- Self-signed (
auto): generated on first boot and stored in<data>/tls/. The startup log prints its fingerprint (Serving HTTPS bind=0.0.0.0 cert=self-signed fingerprint=sha256:…) so you can compare it with what your browser shows. The CLI and TUI pin the certificate on first connect, like SSH. - New certificate: if you delete
<data>/tls/and restart, the CLI refuses to connect until you clear the old pin. - Your own certificate: set
tls_certandtls_keyto a PEM pair (from your CA ormkcert). Set both or neither. This overridestls. - Reverse proxy: leave
tls = "off"when a proxy (nginx, Caddy, Traefik) handles TLS, and settrusted_proxiesso the daemon trustsX-Forwarded-Protoand marks cookiesSecure. - No ACME / Let’s Encrypt. For a publicly trusted certificate, use a reverse proxy or your own pair.
- The
RUNWISP_TLSenv var overridestls.
metrics_enabled
Section titled “metrics_enabled”type: boolean default: false
Turns on the OpenMetrics endpoint at /metrics (for Prometheus, Grafana Agent,
OpenTelemetry). It is off by default because the metrics show task names and the
daemon version. See Metrics for the metric list.
[daemon]metrics_enabled = truemetrics_listen
Section titled “metrics_listen”type: string default: unset
A separate host:port for /metrics, for example "127.0.0.1:9478". Use it to
keep metrics on loopback while the Web UI is public.
- When set,
/metricsis served only on this address. The UI, REST API, and/healthstay on the main listener. - Setting it turns metrics on.
metrics_enabled = falsetogether withmetrics_listenis an error. - This listener is always plain HTTP. Keep it on loopback or a private interface.
[daemon]metrics_listen = "127.0.0.1:9478"trusted_proxies
Section titled “trusted_proxies”type: string[] (CIDR) default: unset
Reverse proxies (nginx, Caddy, Traefik, Cloudflare Tunnel) whose
X-Forwarded-For header RunWisp trusts for the real client IP.
- A bare IP means a single address (
"127.0.0.1"is/32). 0.0.0.0/0and::/0are rejected, because they would let any client fake its IP.- The
RUNWISP_TRUSTED_PROXIESenv var (comma-separated) overrides this key. - See Reverse proxies.
[daemon]trusted_proxies = ["127.0.0.1/32", "10.0.0.0/8"]allow_station_dispatch
Section titled “allow_station_dispatch”type: boolean default: false
Lets a connected control-plane peer start ad-hoc runs: one-shot commands
that are not in your config. Without it, the peer can only watch runs and
trigger tasks defined in runwisp.toml.
- Ad-hoc runs never change your task definitions. They are logged and stored like any other run.
- They run with the daemon’s privileges. Enable this only if you need it.
[daemon]allow_station_dispatch = trueinclude
Section titled “include”type: string[] (glob) default: unset
Extra TOML files to merge into the config, so you can split tasks across files.
[daemon]include = ["conf.d/*.toml", "services/*.toml"][tasks.nightly-backup]cron = "0 3 * * *"run = "/opt/backup.sh"- Globs and relative paths inside a file resolve against that file’s directory.
env_file = "backup.env"inconf.d/backups.tomlmeansconf.d/backup.env. - Tasks, services, compose blocks, notifiers, and routes from all files are merged. The root file loads first, then matches in path order.
- Names must be unique across all files. A duplicate is an error that names both files.
[daemon],[defaults],[storage], and[notify]are allowed only in the root file.- Includes don’t nest: an included file can’t have its own
include. runwisp reloadre-reads the globs, so added, changed, or deleted files are picked up without a restart.
include_cron
Section titled “include_cron”type: string[] (glob) default: unset
Real crontabs to load as tasks. RunWisp reads these files and never writes them. Every job becomes a task with captured output, run history, and a page in the UI.
[daemon]include_cron = [ "/etc/crontab", "/etc/cron.d/*", "/var/spool/cron/crontabs/*", # per-user crontabs (needs a root daemon)]sudo runwisp takeoverfinds the crontabs on the host and writes this key for you. It doesn’t edit aninclude_cronyou already have; it prints what to add.- After
crontab -e, runrunwisp reload. runwisp promote <task>copies a job intorunwisp.tomland comments out the crontab line. See Staging and promoting.
Which files, and who the jobs run as
Section titled “Which files, and who the jobs run as”The path decides the format:
| Path | Format | Jobs run as |
|---|---|---|
/etc/crontab, any cron.d/* |
system crontab | the user in the line’s 6th field |
/var/spool/cron/crontabs/<user> |
per-user spool | <user>, from the file name |
/var/spool/cron/<user> |
per-user spool | <user> (RHEL/SUSE layout) |
| anything else | crontab -l dump |
the daemon’s user |
- Running a job as another user needs a root daemon. Without root, RunWisp refuses that spool file instead of running the jobs as itself. Your own crontab works without root.
- A spool file must be owned by the user it is named after (cron checks the same).
Which files a glob matches
Section titled “Which files a glob matches”A glob picks only the files cron would read:
/etc/crontabandcron.d/*: regular files with names made of letters, digits,-, and_. Sobackup.dpkg-old,job.disabled, andREADMEare skipped.- Spool directories: any name that could be a user (
john.doeis fine), excepttmp.*(the temporary filecrontab -ewrites). - A path you name exactly is always read, whatever its name.
- A symlink is not followed, even though cron may run it. It is reported as a
job that is not running, and
runwisp takeoverstops until you replace it with the file it points to. - Skipped files are reported with the reason.
- A spool directory the daemon can’t read (Debian ships
/var/spool/cron/crontabsas1730 root:crontab) is reported, because cron may be running jobs RunWisp can’t see. Run RunWisp as root or as the owner.
Other rules
Section titled “Other rules”- A job RunWisp can’t reproduce is skipped; the rest of the file still
runs. Each skip is reported as
file:lineat boot and byrunwisp reload,runwisp status, andrunwisp validate.runwisp import cronexplains each one. - An unreadable file is skipped; other files still load. (For example: a spool file for a deleted user, wrong file mode.) It is a config error only when every matched file fails.
- NSS/LDAP/SSSD users are not visible to the default release build
(
CGO_ENABLED=0, reads only/etc/passwd), so their jobs are skipped. Check withgetent passwd <name>. A CGO build, or adding the user to/etc/passwd, fixes it. MAILTO=is reported, not used. RunWisp keeps the output and notifies on failure instead. Asendmailnotifier replaces cron mail and removes the warning. ASHELL=that isn’t an absolute path is reported too.- Missed jobs are not run later. A job missed while the daemon was down is
recorded as missed, but not started.
promotethe task to getcatch_up. - Overlapping runs queue. Cron starts a second copy of a slow job; RunWisp
waits for the first.
promotethe task and setmax_concurrentto change this. - Task names come from the command. If two crontabs give the same name, the
second becomes
backup-<crontab name>, and the rename is reported. - Files anyone could edit are refused. A group- or world-writable crontab, or one with an unrelated owner, fails the whole load. Spool directories are the exception: they are group-writable and sticky by design.
- No
${...}substitution in cron files.0 3 * * * dump.sh ${DB}reaches the shell as written. - A file can’t match both
includeandinclude_cron. That is an error. - anacron is not read.
/etc/anacrontabjobs keep running under anacron.
Otherwise a cron task is a normal task: [defaults] applies, notifications
fire, runwisp run starts it, and reload treats it like any other task.