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://www.msaied.com) - [Home](https://www.msaied.com)
- [Projects](https://www.msaied.com/projects)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [About](https://www.msaied.com#about)

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

Menu
----

Close 

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

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

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

   [Laravel](https://www.msaied.com/articles?category=laravel) [Tips &amp; Tricks](https://www.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://www.msaied.com#person) Published 8 Oct 2026 · Updated 11 Oct 2026 · 4 min read

ShareCopy linkCopied

 ![Running Laravel's Scheduler on Multiple Servers with alwaysOnOneServer and hasBeenInterruptedSince](https://cdn.msaied.com/764/f0add725c7e924dc0417ce1d05dab9d7.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 fires once per server. Sending a daily report email three times because you have three app nodes is not acceptable. Laravel has long offered `onOneServer()` to solve this on a per-task basis, but forgetting to add it to a new task only becomes obvious when the duplicates appear in production.

**Laravel 13.35** ships two new tools that address this at the schedule level rather than the task level.

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

Contributed by [Jack Bayliss](https://github.com/jackbayliss), `Schedule::alwaysOnOneServer()` applies the single-server constraint to every task in the schedule. Call it once in your `AppServiceProvider`:

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

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

```

The flag is evaluated when the schedule reads its events, so tasks defined later in `routes/console.php` are covered automatically. It is off by default, and you can disable it again with `Schedule::alwaysOnOneServer(false)`.

### Name Your Closure Tasks

The lock key for `onOneServer()` is derived from the task's name. Artisan commands have a name automatically; closure tasks do not unless you call `name()`. With `alwaysOnOneServer()` enabled, unnamed closures are silently skipped and still run on every server:

```php
// Runs on every server — no name, no lock
Schedule::call(fn () => Report::sendDaily())->daily();

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

```

There is no warning when a closure is skipped, so audit your closure tasks for a `name()` call before enabling the flag.

### Shared Cache Is Required

The lock lives in the cache. Every server must share the same cache store — Redis, Memcached, DynamoDB, or the database driver all work. The `file` and `array` stores are local to each server and will not prevent duplicate runs.

If your application's default cache store (`CACHE_STORE`) is local, point the scheduler at a shared store with `Schedule::useCache()`:

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

```

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

> **Note:** If some tasks must run on every server (e.g., clearing a local disk cache), leave `alwaysOnOneServer()` off and call `onOneServer()` only on the tasks that should run once.

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 could finish on old code or be killed mid-run during a deploy.

Contributed by [cyppe](https://github.com/cyppe), the updated `schedule:interrupt` now stores the interrupt *time* (mirroring how `queue:restart` works). A long-running command can check that time between batches and exit cleanly:

```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 an earlier deploy does not stop a fresh run.

A typical deploy script now looks like this:

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

```

> **Important:** The interrupt time is stored in the *default* cache store, not the store passed to `useCache()`. That store must be shared across all servers. Calling `Schedule::withoutInterruptionPolling()` disables polling, and `hasBeenInterruptedSince()` will always return `false`.

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

- `Schedule::alwaysOnOneServer()` prevents duplicate task execution across all servers without per-task boilerplate.
- Closure tasks must have a `name()` call to participate in the single-server lock; unnamed closures are silently skipped.
- All servers must share a cache store (Redis, database, etc.) for locks to work; use `Schedule::useCache()` if the default store is local.
- `hasBeenInterruptedSince($startedAt)` lets background commands exit gracefully between batches when a deploy triggers `schedule:interrupt`.
- The interrupt timestamp is stored 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://www.msaied.com/articles?search=Laravel)
- [Scheduler](https://www.msaied.com/articles?search=Scheduler)
- [Laravel 13.35](https://www.msaied.com/articles?search=Laravel%2013.35)
- [Multiple Servers](https://www.msaied.com/articles?search=Multiple%20Servers)
- [Task Scheduling](https://www.msaied.com/articles?search=Task%20Scheduling)

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

  What is Schedule::alwaysOnOneServer() in Laravel 13.35?It is a method introduced in Laravel 13.35 that applies the single-server constraint to every scheduled task globally, so you no longer need to call onOneServer() on each individual task. Call it once in AppServiceProvider::boot() and all tasks — including those defined later in routes/console.php — will only run on one server per scheduled interval.

   Why do unnamed closure tasks still run on every server even with alwaysOnOneServer() enabled?The lock key used by onOneServer() is derived from the task's name. Artisan command tasks have a name automatically, but closure tasks only have one if you explicitly call -&gt;name('your-name') on them. Without a name, the scheduler cannot build a lock key, so alwaysOnOneServer() silently skips those closures and they run on every server.

   How does Schedule::hasBeenInterruptedSince() differ from the old schedule:interrupt behavior?Previously, schedule:interrupt set a short-lived flag that only schedule:run checked, meaning background commands started with runInBackground() never saw it. In Laravel 13.35, schedule:interrupt stores the exact interrupt timestamp instead. A long-running command can call hasBeenInterruptedSince($startedAt) between batches to detect a deploy-time interrupt and exit cleanly, without being killed mid-process.

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

[Mohamed Said](https://www.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://www.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://www.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://www.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://www.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://www.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://www.msaied.com/articles/eloquent-at-scale-chunked-iteration-lazy-collections-and-cursor-pagination) [ ![](https://cdn.msaied.com/763/f0add725c7e924dc0417ce1d05dab9d7.png) Laravel · 4 min read### Running Laravel's Scheduler on Multiple Servers with alwaysOnOneServer and hasBeenInterruptedSince

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

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

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

 [Discuss your project ↗](https://www.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://www.msaied.com)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.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://www.msaied.com/sitemap.xml)
