Laravel Artisan commands and task scheduling
Status: Complete. Last reviewed 2026-08-27.
Commands are automation boundaries
Section titled “Commands are automation boundaries”An Artisan command is a console adapter around an application operation. Its signature parses arguments and options, the container injects dependencies into handle(), and its exit status tells a caller whether the operation succeeded. Interactive colors and progress bars are for people; stable exit codes, structured logs, and machine-readable output are for cron, CI, supervisors, and orchestration.
Keep transaction and domain behavior in an application service when HTTP, queue, and console entry points share it. The command owns input validation, confirmation, presentation, cancellation, and process-level outcome. Zero means success. A non-zero status means the caller must not treat the run as successful. If processing 9,990 records and rejecting 10 violates the contract, printing ten warnings and returning zero is dishonest. Define partial-success semantics explicitly, including a summary, durable failure records, and whether rerunning retries only failures.
Arguments and options are untrusted input. Validate identifiers, paths, dates, ranges, and mutually exclusive flags before making changes. Destructive commands should support a preview/dry-run, require explicit scope, and avoid prompting when run non-interactively. Laravel’s console tests can provide input, assert output and exit codes, but the application operation also needs direct tests for effects and reruns.
Long-running and rerunnable work
Section titled “Long-running and rerunnable work”A production command may be interrupted by deployment, host shutdown, a signal, timeout, OOM kill, lost connection, or operator action. Signal handling allows graceful cancellation, not immortality. On termination, stop accepting new units, finish or roll back the current bounded unit, persist a checkpoint, release resources, and return promptly enough for the supervisor’s grace period. A hard kill remains possible, so durable correctness cannot depend only on a shutdown callback.
Process data in bounded chunks or cursor-based pages. Offset pagination over a changing table can skip or duplicate rows; a stable monotonic cursor such as the last processed ID is easier to checkpoint. Keep each transaction small, free large object graphs, and report rate, success/failure counts, checkpoint, memory, and elapsed time. If per-record work is independent or slow, dispatch bounded idempotent jobs and let the command coordinate rather than keeping one process alive for hours.
Rerunnability comes from stable work identity and durable state. Use database uniqueness, conditional transitions, upserts, or provider idempotency keys. A --force flag is not an idempotency mechanism. Store a run record with parameters, release, start/end times, checkpoint, status, and error summary when operations matter operationally.
An Isolatable command uses the default cache’s atomic locks when invoked with --isolated. By default, failure to acquire the lock still exits successfully unless the caller supplies a different isolated exit code. Customize the lock ID when different scopes may run concurrently and set an expiry based on real runtime. As with every lease, expiry after a crash can overlap a still-running paused process; business effects remain idempotent.
What the scheduler guarantees
Section titled “What the scheduler guarantees”Laravel’s scheduler evaluates a code-defined schedule; it does not wake itself. An external cron normally runs php artisan schedule:run every minute, or a managed long-running schedule:work process invokes it. If that trigger is absent, delayed, duplicated, or running old code, due tasks behave accordingly. schedule:list helps inspect the calculated next runs but does not prove the external trigger or timezone is correct.
Define schedules in the application’s configured timezone deliberately. Daylight-saving transitions can cause a local time to occur twice or not at all; Laravel warns that timezone schedules may double-run or skip. Prefer UTC for machine workflows, or make local-time tasks idempotent using a business-date execution key. Sub-minute tasks keep schedule:run alive for the rest of the minute, so deployments should interrupt them with Laravel’s scheduler interrupt command.
withoutOverlapping() takes a cache lock before running a scheduled task. Its expiry prevents a permanent orphan, but an expiry shorter than actual runtime permits overlap; an expiry much longer than runtime delays recovery after a crash. Use a shared cache across hosts. onOneServer() also uses a shared atomic lock so only one scheduler host runs the task, and named tasks are needed where otherwise identical jobs need distinct locks. Neither feature means exactly once: the process can complete an external effect and die before local completion is observed.
runInBackground() applies to scheduled commands and shell commands; it lets later due tasks start without waiting for the child. It creates a new supervision and logging problem and is not a replacement for queues. Scheduled queued jobs are often better for retryable work, but dispatch itself can be duplicated, so the job still needs identity and idempotency.
Maintenance mode suppresses scheduled tasks unless explicitly allowed. Scheduler groups and hooks (before, after, success/failure callbacks, output capture and notification) improve consistency and evidence, but hooks are not durable workflow engines. If a failed follow-up must always happen, model it as persisted work rather than relying solely on an in-process callback.
Production failure analysis
Section titled “Production failure analysis”When a scheduled task “didn’t run,” distinguish: was the external trigger invoked; did Laravel consider the expression due in the effective timezone; was maintenance mode active; did a one-server or overlap lock suppress it; did dispatch succeed; did a queued handler later fail; and was output observable? Record the scheduler host, release, scheduled instant, lock key, task identity, dispatch ID, and final outcome.
When it “ran twice,” inspect duplicate cron entries, multiple hosts without onOneServer(), lock-store partitioning, lease expiry, DST, manual invocation, and retry/redelivery. A business-date uniqueness constraint or idempotency key is the final defense for billing, settlement, or irreversible notifications.
Current and legacy context
Section titled “Current and legacy context”Current: Laravel 13 supports attribute-based command signature/description alongside the conventional properties, isolatable commands, signal traps, schedule groups, sub-minute scheduling, one-server and overlap locks, and scheduler interruption. Common: Laravel 11–12 use the same operational model, although application bootstrapping locations differ. Legacy: older applications define schedules in app/Console/Kernel.php; current fresh applications commonly use routes/console.php or bootstrap configuration.
Interview practice
Section titled “Interview practice”- LARAVEL-ARTISAN-01 — Design an automation-safe command
- LARAVEL-ARTISAN-02 — Make a long command resumable
- LARAVEL-ARTISAN-03 — Explain scheduler triggering and time
- LARAVEL-ARTISAN-04 — Compare overlap and one-server locks
- LARAVEL-ARTISAN-05 — Diagnose a missing or duplicated scheduled run