Skip to content
FreeBSDDeep Dive Published Updated 7 min readViews unavailable

FreeBSD at and batch Operations: Reliable One-Time Job Scheduling

Schedule and audit one-time FreeBSD jobs with at and batch, predictable environments, atrun timing, load controls, output capture, and safe cleanup.

FreeBSD at and batch are for deferred, one-time work. They are not a replacement for a durable job queue, a retrying workflow engine, or a recurring schedule. at runs a submitted command at a requested time; batch places work in an uppercase queue that atrun starts only when recent load is below a threshold. The scheduler operates through cron and atrun, so job submission alone does not prove that the command will execute on time.

Use these tools for bounded maintenance tasks, one-time reports, delayed cleanup, or a controlled batch operation. If the task must survive host replacement, coordinate across machines, retry safely, or meet a tight start-time SLA, choose a system designed for that requirement.

Verify the execution path first

Before submitting work, verify that cron is running and atrun’s schedule exists:

service cron status
grep -v '^[[:space:]]*#' /etc/cron.d/at
ls -ld /var/at/jobs /var/at/spool

FreeBSD’s atrun manual documents the default system crontab entry in /etc/cron.d/at as a five-minute invocation. The at -t form can represent seconds, but the stock atrun cadence means syntactic precision is not dispatch precision. A host that is asleep, powered off, missing cron, or unable to execute atrun does not meet an exact-on-time guarantee.

The default atrun policy starts all due jobs in lowercase queues at each run. It starts at most one batch job from uppercase queues if the one-minute load average is below the configured limit. The default load limit is 1.5 times the number of active CPUs. Therefore, batch is intentionally subject to load and queue delay; it is not a parallel job manager with fairness guarantees.

Submit a command with a controlled environment

First write a script that can be run directly, logs its own start and finish, checks preconditions, and exits nonzero on failure. Use absolute paths for commands and files because the saved environment may not resemble an interactive shell. Make the script safe to rerun if the job is submitted twice.

An operator can submit a script file with at -f:

at -f /usr/local/sbin/rotate-one-archive 23:30 tomorrow

Or enter a short command from standard input:

printf '%s\n' '/usr/local/sbin/rebuild-index --dry-run' | at 2am tomorrow

The at(1) utility retains the working directory, environment except for TERM, TERMCAP, DISPLAY, and _, and umask from submission time. This makes the job’s behavior depend on where and how it was submitted. Prefer a dedicated script that sets its own PATH, locale, working directory, and temporary directory. Do not rely on shell aliases, an interactive profile, an attached terminal, or an environment variable that might contain a secret.

Use shell quoting deliberately. The printf example passes one command line to at; it does not expand the script command when scheduling. Avoid interpolating untrusted text into a shell command. For complex arguments, put validation and quoting in a script rather than constructing a long shell string.

Use batch for low-priority work

batch submits a job to an uppercase queue. atrun starts no more than one such job per invocation and only if system load is below its threshold. This is useful for work that can wait, such as a large report or offline conversion, but it means a queue can accumulate while the machine is busy.

batch -f /usr/local/sbin/rebuild-search-cache
atq

Do not assume that batch is a resource-isolation mechanism. Once started, the job can consume CPU, memory, disk I/O, and network bandwidth. Add explicit limits or use a scheduler appropriate to the workload if contention matters. A low load average also does not imply low storage latency or available memory.

The -l option changes atrun’s load threshold. Do not edit /etc/cron.d/at casually to achieve faster execution; changing the runner cadence affects every queued job and can increase repeated load checks. If a finer schedule is required, verify the current system crontab behavior and document how the change will be maintained across upgrades.

Inspect, remove, and audit queued jobs

List the current user’s jobs with atq. The queue output includes job identifiers and scheduled times. Remove a job only after verifying that the identifier belongs to the intended task:

atq
atrm 17
atq

The at utility also exposes queue-management options, but prefer the dedicated atq and atrm commands for clarity. Keep a change record when a job affects production data. If you need to inspect the saved command, use the documented display option for the local at implementation; do not edit spool files directly.

By default, standard output and standard error from a job are mailed to its owner when there is output. Delivery depends on the local mail setup. Configure explicit logging in the script because email may be disabled, misrouted, or delayed. An empty mailbox is not proof of success: a command can fail silently or write only to a file.

Example script logging pattern:

#!/bin/sh
set -eu
PATH=/bin:/usr/bin:/sbin:/usr/sbin
export PATH
umask 077
exec >>/var/log/archive-maintenance.log 2>&1
date
/usr/local/sbin/archive-maintenance --verify
date

Choose a log destination with enough capacity and rotation, and include an exit-status record if the script has multiple stages. Avoid printing credentials or private data. If a job is long-running, use a lock or another single-instance mechanism so a delayed prior run cannot overlap a newly submitted copy.

Design retries and idempotency outside at

at is a launcher, not a transaction manager. It does not promise exactly-once execution across power loss, filesystem damage, or manual resubmission. A one-time job can be submitted twice; a host may be down at the scheduled time; and an operator may not know whether a partially completed script committed its side effects.

Design the task with a durable completion marker or transactional semantics. Before changing production records, have the script validate its input, check that the target is in the expected state, and make an operation idempotent where possible. For a non-idempotent task, store a unique run identifier and check it before repeating. Do not “retry” by submitting another copy until logs and effects have been inspected.

External dependencies also affect behavior. If a command needs a mounted filesystem, a VPN, DNS, an API token, or a remote service, test those preconditions in the script and fail visibly when absent. Use a bounded timeout around network operations. A job that ran at the right time but used a stale environment or wrong current directory still failed operationally.

Common failure modes

If atq shows a job that never ran, check host uptime, cron status, atrun’s schedule, account status, spool permissions, logs, and mail delivery. atrun checks whether the owner’s account is available and can refuse to run work for an expired or locked account. This is an important property for user-owned jobs and should be considered in maintenance planning.

If a job ran late, remember the five-minute atrun cadence and, for batch, the load threshold and one-job-per-run rule. Inspect the saved requested time and system timezone. Do not assume that a timestamp in an email or log uses the same zone as the person who submitted the task.

If output is missing, inspect the script’s own log, system mail configuration, and job exit handling. If a job has been removed, do not infer whether it ran from the absence of a queue entry. Preserve logs and command history before cleaning up.

Completion checklist

Before leaving the task unattended, verify that the script runs under the same account and environment, the job appears in atq, its queue and scheduled time are correct, cron and atrun are operational, and output has an explicit destination. After dispatch, confirm side effects independently and remove stale or obsolete jobs.

Record the job owner, purpose, expected effects, cancellation instructions, escalation contact, and what evidence constitutes completion. Use cron for recurring schedules and a durable external scheduler or queue when the task needs retries, dependencies, distributed ownership, or strict execution guarantees.

Related:

Sources:

Comments