Back to posts
Post

Running Sub‑Minute Tasks with systemd Timers: AccuracySec and OnUnitActiveSec

Replace cron with systemd timers for millisecond‑precise scheduling and jitter control using AccuracySec and OnUnitActiveSec.

SistemsystemdcrontimersAccuracySecOnUnitActiveSec

I moved away from cron for tasks that need to run every few seconds or require tight timing because cron’s minute granularity adds unwanted jitter. In my environment, systemd timers give me sub‑minute precision and let me control how much the scheduler can drift. This is especially useful for health checks, log shippers, or any job where starting exactly on schedule matters more than approximate execution.

Why systemd timers beat cron for high‑frequency jobs

Cron assumes you want to run something at a specific minute, hour, or day. If you tell it to run every 30 seconds, you end up with two entries or a wrapper script that sleeps — both add complexity and unpredictable start times. systemd timers, on the other hand, can trigger units as frequently as once per second with configurable accuracy. I use them for sidecar processes in containers and for host‑level checks where missing a window by even a few hundred milliseconds can skew metrics.

Setting up a timer with OnUnitActiveSec and AccuracySec

The two keys that control timing behavior are OnUnitActiveSec and AccuracySec. OnUnitActiveSec defines the interval measured from the last activation of the unit, not from a fixed clock time. This prevents drift when a job runs longer than its interval. AccuracySec tells systemd how late the timer may fire; setting it to 1ms gives near‑real‑time behavior, while 1s allows coalescing for power savings.

Here’s a typical unit file I deploy for a log forwarder that must run every 10 seconds:

# /etc/systemd/system/log-forwarder.service
[Unit]
Description=Forward logs to central collector

[Service]
Type=oneshot
ExecStart=/usr/local/bin/log-forwarder.sh
# /etc/systemd/system/log-forwarder.timer
[Unit]
Description=Run log-forwarder every 10s with high accuracy

[Timer]
OnUnitActiveSec=10s
AccuracySec=1ms
Persistent=true

[Install]
WantedBy=timers.target

After placing these files, I reload and enable the timer:

sudo systemctl daemon-reload
sudo systemctl enable --now log-forwarder.timer

I verify it’s active with systemctl list-timers --all and check the next trigger time.

Controlling jitter and preventing overlap

Because OnUnitActiveSec measures from the last activation, if my log-forwarder.sh takes 8 seconds to run, the next start will be 2 seconds after it finishes — not 10 seconds after the previous start. This keeps the schedule stable even when workloads vary. If I wanted fixed‑interval behavior regardless of runtime, I’d use OnUnitSec instead, but that risks overlap.

To avoid overlap entirely, I rely on the service’s Type=oneshot and ensure the script exits cleanly. If overlap is acceptable, I’d switch to Type=simple and consider RefuseManualStart=no plus RestartPreventExitStatus= to manage restarts.

AccuracySec trade‑offs: precision vs. power

Setting AccuracySec=1ms wakes the scheduler frequently, which can increase CPU wakeups on idle systems. For battery‑powered devices or low‑power VMs, I raise this to 50ms or 100ms. The timer will still fire within that window, and systemd can group multiple timers to reduce wakeups. I monitor /proc/timer_list or use systemd-analyze blame to see if timer wakeups are showing up unexpectedly.

Debugging timer misfires

If a timer doesn’t fire when expected, I check:

  • Whether the service unit is misconfigured (run systemctl status log-forwarder.service manually)
  • If the timer is active (systemctl status log-forwarder.timer)
  • Journal entries for the timer (journalctl -u log-forwarder.timer)
  • Any AccuracySec clamping (systemd may raise very low values on some kernels)

A useful trick is to add ExecStartPre=/bin/echo 'Timer fired at: $(date +%s.%N)' to the service to see actual start times with nanosecond precision.

When to stick with cron

I still use cron for daily backups, weekly reports, or any job where minute-level timing is sufficient and simplicity wins. But for anything under a minute, or where I need to control jitter rigorously, systemd timers are my default now. As I mentioned before in my post about securing cron with Restic’s --password-file, reducing reliance on cron’s environment inheritance also improves security — timers run in a clean service context by default.

Summary

systemd timers with OnUnitActiveSec and AccuracySec give me precise, jitter-controlled scheduling that cron simply can’t match at sub‑minute intervals. By measuring intervals from the last activation and tuning accuracy, I get repeatable behavior for time-sensitive workloads without overcomplicating the setup.


Cover image: Unknown · CC0 (Openverse / kamu malı) · https://www.rawpixel.com/image/5924954/photo-image-background-public-domain-technology