Laravel Scheduler on Multiple Servers (Laravel 13.35) | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://msaied.com) - [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [About](https://msaied.com#about)

           [  Contact](https://msaied.com#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://msaied.com)
- [ProjectsCase studies](https://msaied.com/projects)
- [ArticlesEngineering notes](https://msaied.com/articles)
- [CertificatesCredentials](https://msaied.com/certificates)
- [AboutHow I work](https://msaied.com#about)
- [ContactGet in touch](https://msaied.com#contact)

  [Start a conversation](https://msaied.com#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://msaied.com)
2. /
3. [Articles](https://msaied.com/articles)
4. /
5. [Laravel](https://msaied.com/articles?category=laravel)
6. /
7. Running Laravel's Scheduler on Multiple Servers with alwaysOnOneServer() and hasBeenInterruptedSince()

   [Laravel](https://msaied.com/articles?category=laravel) [Tips &amp; Tricks](https://msaied.com/articles?category=tips-tricks) 

 Running Laravel's Scheduler on Multiple Servers with alwaysOnOneServer() and hasBeenInterruptedSince()
=======================================================================================================

 Laravel 13.35 adds Schedule::alwaysOnOneServer() to prevent duplicate task runs across servers, and Schedule::hasBeenInterruptedSince() so long-running commands can stop cleanly during a deploy.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://msaied.com#person) Published 8 Oct 2026 · Updated 12 Oct 2026 · 4 min read

ShareCopy linkCopied

 ![Running Laravel's Scheduler on Multiple Servers with alwaysOnOneServer() and hasBeenInterruptedSince()](https://cdn.msaied.com/769/2ba8b59dbd15bdf2ec2f7846da0f62d4.png) 

  On this page +1. [The Problem: Duplicate Scheduled Tasks Across Servers](#the-problem-duplicate-scheduled-tasks-across-servers)
2. [Schedule::alwaysOnOneServer()](#schedulealwaysononeserver)
3. [Name Your Closure Tasks](#name-your-closure-tasks)
4. [Shared Cache Is Required](#shared-cache-is-required)
5. [Schedule::hasBeenInterruptedSince()](#schedulehasbeeninterruptedsince)
6. [Key Takeaways](#key-takeaways)

 The Problem: Duplicate Scheduled Tasks Across Servers
-----------------------------------------------------

When you run `schedule:run` on more than one server, every scheduled task executes once per server. For a task that clears a local cache that is fine, but for a task that sends a daily report email it means duplicate emails on every deploy.

Laravel 13.35 ships two additions that address this directly:

- **`Schedule::alwaysOnOneServer()`** — applies the existing `onOneServer()` behaviour to every task in the schedule automatically.
- **`Schedule::hasBeenInterruptedSince()`** — lets a long-running command check whether `schedule:interrupt` was called and stop itself cleanly.

---

Schedule::alwaysOnOneServer()
-----------------------------

Laravel has had `onOneServer()` per task for a long time. The first server to start a task takes a cache lock; the others skip that run. The catch is that every new task needs the call, and a missed one only surfaces when duplicates appear in production.

A group workaround existed:

```php
Schedule::onOneServer()->group(function () {
    Schedule::command('reports:send')->daily();
    Schedule::command('invoices:generate')->monthly();
});

```

Laravel 13.35 (contributed by Jack Bayliss) adds a cleaner alternative. Call it once in your service provider and it covers every task, including ones defined later in `routes/console.php`:

```php
// app/Providers/AppServiceProvider.php
use Illuminate\Support\Facades\Schedule;

public function boot(): void
{
    Schedule::alwaysOnOneServer();
}

```

It is off by default. Pass `false` to disable it again: `Schedule::alwaysOnOneServer(false)`.

### Name Your Closure Tasks

`onOneServer()` builds its lock key from the task name. Artisan commands have one automatically; closure tasks only have one if you call `->name()`. `alwaysOnOneServer()` silently skips unnamed closures — they still run on every server — so audit your closure tasks:

```php
// Runs on every server even with alwaysOnOneServer()
Schedule::call(fn () => Report::sendDaily())->daily();

// Runs on one server
Schedule::call(fn () => Report::sendDaily())
    ->name('send-daily-report')
    ->daily();

```

### Shared Cache Is Required

The lock lives in the cache, so every server must share the same store (Redis, Memcached, DynamoDB, or the database). The `file` and `array` drivers are local to each server and will not work. If your default `CACHE_STORE` is local, point the scheduler at a shared store:

```php
public function boot(): void
{
    Schedule::alwaysOnOneServer();
    Schedule::useCache('redis'); // named store from config/cache.php
}

```

`useCache()` affects only the scheduler's locks (`onOneServer()` and `withoutOverlapping()`). The rest of your application keeps its default store.

---

Schedule::hasBeenInterruptedSince()
-----------------------------------

Before 13.35, `schedule:interrupt` set a flag that only `schedule:run` checked. A command started with `runInBackground()` ran in its own process and never saw the flag, so it would either finish on old code or be killed mid-run.

Contributed by cyppe, the new approach stores the *time* of the interrupt (mirroring how `queue:restart` works). A long-running command can now compare that time against its own start time and exit gracefully between batches:

```php
use Illuminate\Console\Command;
use Illuminate\Console\Scheduling\Schedule;

class SyncInventory extends Command
{
    protected $signature = 'inventory:sync';

    public function handle(Schedule $schedule): int
    {
        $startedAt = now();

        foreach (Product::lazyById(500) as $product) {
            if ($schedule->hasBeenInterruptedSince($startedAt)) {
                $this->info('Interrupted by a deploy, stopping.');
                return self::SUCCESS;
            }
            $this->syncProduct($product);
        }

        return self::SUCCESS;
    }
}

```

`hasBeenInterruptedSince()` returns `true` only for an interrupt that occurred *at or after* the time you pass in, so a stale interrupt from a previous deploy does not stop a fresh run.

> **Note:** The interrupt time is stored in the *default* cache store, not the store passed to `useCache()`. On a multi-server setup the default store must be shared. Calling `Schedule::withoutInterruptionPolling()` disables the cache lookups and makes `hasBeenInterruptedSince()` always return `false`.

A typical deploy script:

```bash
php artisan schedule:interrupt
# switch release, run migrations…
php artisan queue:restart

```

---

Key Takeaways
-------------

- `Schedule::alwaysOnOneServer()` in `AppServiceProvider::boot()` replaces per-task `onOneServer()` calls.
- Unnamed closure tasks are silently skipped — always add `->name()` to closures.
- All servers must share a cache store; use `Schedule::useCache('redis')` if your default store is local.
- `hasBeenInterruptedSince($startedAt)` lets background commands exit cleanly on deploy.
- The interrupt timestamp lives in the default cache store, which must also be shared.

---

*Source: [Running Laravel's Scheduler on Multiple Servers — Laravel News](https://laravel-news.com/laravel-scheduler-multiple-servers)*

- [Laravel](https://msaied.com/articles?search=Laravel)
- [Scheduler](https://msaied.com/articles?search=Scheduler)
- [Laravel 13.35](https://msaied.com/articles?search=Laravel%2013.35)
- [Multiple Servers](https://msaied.com/articles?search=Multiple%20Servers)
- [Task Scheduling](https://msaied.com/articles?search=Task%20Scheduling)
- [Deploy](https://msaied.com/articles?search=Deploy)

 Frequently asked questions 
---------------------------

  What is the difference between onOneServer() and alwaysOnOneServer() in Laravel?onOneServer() must be chained onto each individual scheduled task. alwaysOnOneServer(), introduced in Laravel 13.35, is called once on the Schedule facade and automatically applies the single-server lock to every task in the schedule, including tasks defined after the call.

   Why do unnamed closure tasks still run on every server even when alwaysOnOneServer() is enabled?The lock key used by onOneServer() is derived from the task's name. Closure tasks have no name unless you call -&gt;name() on them. Because throwing an exception for every unnamed closure would be too disruptive, alwaysOnOneServer() silently skips them, so they continue to run on all servers. Add -&gt;name('your-task-name') to any closure task you want covered.

   How does hasBeenInterruptedSince() differ from the old schedule:interrupt behaviour?Previously, schedule:interrupt stored a simple flag that only schedule:run checked, so background processes started with runInBackground() ignored it. In Laravel 13.35 the interrupt stores a timestamp instead. A long-running command can call $schedule-&gt;hasBeenInterruptedSince($startedAt) in its own loop and exit cleanly when a deploy interrupt is detected, without being forcibly killed.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

[Mohamed Said](https://msaied.com#person)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

[About](https://msaied.com#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articlePostgreSQL JSONB in Laravel: Indexing, Querying, and Casting Without the Mess](https://msaied.com/articles/postgresql-jsonb-in-laravel-indexing-querying-and-casting-without-the-mess) [Next articleEloquent at Scale: Chunked Iteration, Lazy Collections, and Cursor Pagination](https://msaied.com/articles/eloquent-at-scale-chunked-iteration-lazy-collections-and-cursor-pagination)  

   On this page
-------------

1. [The Problem: Duplicate Scheduled Tasks Across Servers](#the-problem-duplicate-scheduled-tasks-across-servers)
2. [Schedule::alwaysOnOneServer()](#schedulealwaysononeserver)
3. [Name Your Closure Tasks](#name-your-closure-tasks)
4. [Shared Cache Is Required](#shared-cache-is-required)
5. [Schedule::hasBeenInterruptedSince()](#schedulehasbeeninterruptedsince)
6. [Key Takeaways](#key-takeaways)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://msaied.com#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/757/f990951a1a0e14b4fece312bce644c29.png)  · 4 min read### Read/Write Splitting and Sticky Reads in Laravel: A Production Guide

9 Oct 2026 ](https://msaied.com/articles/readwrite-splitting-and-sticky-reads-in-laravel-a-production-guide-1) [ ![](https://cdn.msaied.com/756/69efdf9ac9ec1e1f61378055f1cd6e92.png)  · 4 min read### Eloquent at Scale: Chunked Iteration, Lazy Collections, and Cursor Pagination

9 Oct 2026 ](https://msaied.com/articles/eloquent-at-scale-chunked-iteration-lazy-collections-and-cursor-pagination) [ ![](https://cdn.msaied.com/768/6b65210b14d1b96e0fcbe3020ad587a1.png) Laravel · 4 min read### Running Laravel's Scheduler on Multiple Servers with alwaysOnOneServer() and hasBeenInterruptedSince()

8 Oct 2026 ](https://msaied.com/articles/running-laravels-scheduler-on-multiple-servers-with-alwaysononeserver-and-hasbeeninterruptedsince-5) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://msaied.com#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://msaied.com)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://msaied.com/sitemap.xml)
