Scheduled tasks: /etc/cron.d/<app>

Why cron jobs run as webapp-svc, the generated format, and how to confirm a job actually ran.

sudo bosun add <app-name> writes /etc/cron.d/<app-name> automatically — fully commented out, so it has zero effect until you edit it. This doc covers the format, why it must run as webapp-svc, and how to confirm a job actually ran.

Standalone mode (no argus-tty): every webapp-svc below is actually whatever account /etc/bosun.conf names (see standalone.md) — the cron file bosun add generates already has the right name baked in for your host, so copy its own example line rather than the literal webapp-svc shown here.

The file bosun add generates

# /etc/cron.d/myapp — scheduled tasks for 'myapp'.
  #
  # cron.d format has 6 fields, NOT 5 like a personal 'crontab -e':
  #   min hour dom month dow  user  command
  #
  # Run as 'webapp-svc' — the same unprivileged account puma/sidekiq already
  # run as. It's the only account with a read grant on shared/.env, so any
  # scheduled task that needs real app env vars loaded must run as this user
  # via the generated shared/bin/run wrapper, not as root or a developer
  # account.
  #
  # Example (uncomment and edit — runs daily at 02:30):
  #30 2 * * *  webapp-svc  /var/www/myapp/shared/bin/run rake some:scheduled_task >> /var/www/myapp/shared/log/cron.log 2>&1

bosun add never overwrites this file on a re-run — same "leave existing content untouched" rule it applies to shared/.env/deploy.conf — so your edits are never clobbered.

Why 6 fields, and why /etc/cron.d/, not crontab -e

A plain user's crontab -e has 5 fields (schedule + command) and is owned by that user. /etc/cron.d/* files are root-owned and need a 6th field — the user to run the command as — because one file can schedule jobs for multiple users. Using /etc/cron.d/<app> instead of webapp-svc's own crontab is deliberate:

Why webapp-svc, specifically

shared/.env (the app's secrets — DATABASE_URL, SECRET_KEY_BASE, etc.) is root:root 600 with a single named-user ACL read grant for webapp-svc — no group can read it, deliberately (see the main README's "Why a per-app .env file" section). shared/bin/run (generated by bosun add alongside the cron file) sources deploy.conf and shared/.env, then execs your command under the app's RVM-resolved ruby/gemset. A cron job that doesn't run as webapp-svc calling this wrapper either can't read the env vars at all, or has to re-implement that loading itself — always prefer:

<schedule>  webapp-svc  /var/www/<app>/shared/bin/run <command> >> /var/www/<app>/shared/log/cron.log 2>&1

Requirements for the file to actually run

  1. Owned by root, mode 644 — bosun add already sets this.
  2. Clean filename — lowercase letters/digits/-/_ only, no dots, no ~. The app name validation bosun add already enforces (^[a-z][a-z0-9_-]{0,31}$) satisfies this.
  3. No restart/reload needed — cron re-scans /etc/cron.d/ on its own.

Verifying a job actually ran

sudo -u webapp-svc /var/www/<app>/shared/bin/run rake some:scheduled_task  # run once by hand first
  tail -f /var/www/<app>/shared/log/cron.log                                 # the job's own output
  journalctl -u cron -f          # Debian/Ubuntu — follow live
  grep CRON /var/log/syslog      # Debian/Ubuntu — history