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 execution across servers, and Schedule::hasBeenInterruptedSince() so long-running commands can stop gracefully during a deploy.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://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/765/c39e5397b2a498c12d18b6f1d5a5c726.png) 

  On this page +1. [The Problem with Running schedule:run on Multiple Servers](#the-problem-with-running-codescheduleruncode-on-multiple-servers)
2. [Schedule::alwaysOnOneServer() — Apply the Lock Globally](#codeschedulealwaysononeservercode-apply-the-lock-globally)
3. [Name Your Closure Tasks](#name-your-closure-tasks)
4. [Shared Cache Is Required](#shared-cache-is-required)
5. [Schedule::hasBeenInterruptedSince() — Graceful Deploy Interruptions](#codeschedulehasbeeninterruptedsincecode-graceful-deploy-interruptions)
6. [Key Takeaways](#key-takeaways)

 The Problem with Running `schedule:run` on Multiple Servers
-----------------------------------------------------------

When you run `php artisan schedule:run` on more than one server, every scheduled task fires once per server. That is fine for something like clearing a local cache, but it is a real problem for a task that sends a daily report email — your users will receive it multiple times.

Laravel has long offered `onOneServer()` to solve this. The first server to claim the task takes a distributed cache lock; the others skip it. The catch is that you have to remember to add `onOneServer()` to every task, and a missed call only surfaces when the duplicates appear in production.

**Laravel 13.35** ships two new tools that make multi-server scheduling significantly easier to manage.

---

`Schedule::alwaysOnOneServer()` — Apply the Lock Globally
---------------------------------------------------------

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

```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. You can disable it again with `Schedule::alwaysOnOneServer(false)`.

### Name Your Closure Tasks

The lock key is derived from the task's name. Artisan commands already have one, but closure tasks only get a name if you call `->name()`. `alwaysOnOneServer()` silently skips unnamed closures — they still run on every server — so make sure to name them:

```php
// Runs on every server — no name, skipped by 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 all servers must share the same store (Redis, Memcached, DynamoDB, or the database driver). If your default `CACHE_STORE` is local to each server (e.g. `file`), point the scheduler at a shared store with `Schedule::useCache()`:

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

```

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

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

---

`Schedule::hasBeenInterruptedSince()` — Graceful Deploy Interruptions
---------------------------------------------------------------------

Contributed by [cyppe](https://github.com/cyppe), this method lets a long-running command check whether `schedule:interrupt` was called after the command started, and stop cleanly 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;
    }
}

```

The method returns `true` only for an interrupt that occurred at or after `$startedAt`, so a stale interrupt from a previous deploy will not stop a fresh run.

A typical deploy script that signals both the scheduler and queue workers:

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

```

> **Important:** The interrupt timestamp is stored in the **default** cache store, not the one 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`.

---

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

- `Schedule::alwaysOnOneServer()` applies the one-server lock globally — no more per-task `onOneServer()` calls.
- Unnamed closure tasks are silently skipped by `alwaysOnOneServer()`; always call `->name()` on them.
- All servers must share the same cache store; use `Schedule::useCache('redis')` if your default store is local.
- `hasBeenInterruptedSince($startedAt)` lets long-running background commands stop gracefully between batches when a deploy calls `schedule:interrupt`.
- The interrupt timestamp uses the default cache store, which must also be shared across servers.
- Both features shipped in **Laravel 13.35** (PRs [\#61789](https://github.com/laravel/framework/pull/61789) and [\#61764](https://github.com/laravel/framework/pull/61764)).

---

*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)
- [Multi-Server](https://msaied.com/articles?search=Multi-Server)
- [Laravel 13.35](https://msaied.com/articles?search=Laravel%2013.35)
- [Task Scheduling](https://msaied.com/articles?search=Task%20Scheduling)

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

  What happens to unnamed closure tasks when Schedule::alwaysOnOneServer() is enabled?Unnamed closure tasks are silently skipped by alwaysOnOneServer() and continue to run on every server. To have a closure task respect the one-server constraint, you must give it a name with -&gt;name('your-task-name') so the scheduler can build a unique cache lock key for it.

   Does Schedule::useCache() affect the cache store used by hasBeenInterruptedSince()?No. The interrupt timestamp written by schedule:interrupt is stored in the application's default cache store, not the store configured via useCache(). On a multi-server setup, the default store must itself be a shared store (such as Redis or the database driver) for hasBeenInterruptedSince() to work correctly across all servers.

   Can I still run certain tasks on every server when alwaysOnOneServer() is active?Not directly — there is no per-task opt-out once alwaysOnOneServer() is enabled. If you have tasks that must run on every server (for example, clearing local disk files), leave alwaysOnOneServer() off and call -&gt;onOneServer() only on the tasks that should run on a single server.

   ![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 with Running schedule:run on Multiple Servers](#the-problem-with-running-codescheduleruncode-on-multiple-servers)
2. [Schedule::alwaysOnOneServer() — Apply the Lock Globally](#codeschedulealwaysononeservercode-apply-the-lock-globally)
3. [Name Your Closure Tasks](#name-your-closure-tasks)
4. [Shared Cache Is Required](#shared-cache-is-required)
5. [Schedule::hasBeenInterruptedSince() — Graceful Deploy Interruptions](#codeschedulehasbeeninterruptedsincecode-graceful-deploy-interruptions)
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/766/d77b7cfbd66d41e2a67e814e344f2694.png) Laravel · 3 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-3) 

  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)
