Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

radiantstatic's avatar

Laravel Horizon with staging and production sites on Laravel Forge?

Hi,

I am currently running my staging and production website on the same server (as different sites) via Laravel Forge and deploying through Envoyer.

I have been really excited to start working with Job classes via Redis and have gone full steam on using Laravel Horizon. When I first began working with it, I was only working with local and staging environment and had no problems as those are obviously on two very separate instances. However, when I pushed staging to production, I ran into some issues. I saw that the queues between the two sites (staging and production) were getting mixed up. I tried a bunch of different things but the only thing that seemed to allow me to get things to work was to change the supervisor name to supervisor-2 on staging. I've also changed the horizon prefix between the two environments but I feel like I'm doing this incorrectly.

I had originally read that I can change the REDIS_DB and REDIS_CACHE_DB to be different values between the two but anytime I touched those, I'd get an inactive status in Horizon.

  1. What does changing the supervisor name actually do? What are the implications of doing that?
  2. What is the standard method of getting horizon to run on the same server for two different sites?

In forge I also have two different Daemons running for each site to monitor Laravel and re-intialize when a deployment comes from Envoyer. Are these both necessary given that they are on the same server?

The look like this:

php artisan horizon	/home/forge/{my-staging-url}/current
php artisan horizon	/home/forge/{my-production-url}/current

I'm slowly migrating long running processes to Jobs so currently they are not processing anything important, but I am desperate to get this scenario to work nicely.

My current horizon config file looks as follows:

'environments' => [
        'production' => [
            'supervisor-1' => [
                'connection' => 'redis',
                'queue' => ['default'],
                'balance' => 'simple',
                'processes' => 10,
                'tries' => 1,
            ],
        ],
        'development' => [
            'supervisor-2' => [
                'connection' => 'redis',
                'queue' => ['default'],
                'balance' => 'simple',
                'processes' => 10,
                'tries' => 1,
            ],
        ],
        'local' => [
            'supervisor-1' => [
                'connection' => 'redis',
                'queue' => ['default'],
                'balance' => 'simple',
                'processes' => 3,
                'tries' => 1,
            ],
        ],
    ],
0 likes
2 replies
radiantstatic's avatar

Ok, this is where I take a deep breath and (partially answer my own question).

Still don't have my head completely wrapped around 'supervisor-1' vs 'supervisor-2' but here is where I netted out.

I made sure that each environment had their own Horizon prefix as well as their own separate databases within Redis. PS, I'm using basic auth to expose my horizon dashboard via middleware as my sites are completely API driven and don't actually have a front-end attached to Laravel.

So my horizon config looks as follows:

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Horizon Domain
    |--------------------------------------------------------------------------
    |
    | This is the subdomain where Horizon will be accessible from. If this
    | setting is null, Horizon will reside under the same domain as the
    | application. Otherwise, this value will serve as the subdomain.
    |
    */

    'domain' => null,

    /*
    |--------------------------------------------------------------------------
    | Horizon Path
    |--------------------------------------------------------------------------
    |
    | This is the URI path where Horizon will be accessible from. Feel free
    | to change this path to anything you like. Note that the URI will not
    | affect the paths of its internal API that aren't exposed to users.
    |
    */

    'path' => 'horizon',

    /*
    |--------------------------------------------------------------------------
    | Horizon Redis Connection
    |--------------------------------------------------------------------------
    |
    | This is the name of the Redis connection where Horizon will store the
    | meta information required for it to function. It includes the list
    | of supervisors, failed jobs, job metrics, and other information.
    |
    */

    'use' => 'default',

    /*
    |--------------------------------------------------------------------------
    | Horizon Redis Prefix
    |--------------------------------------------------------------------------
    |
    | This prefix will be used when storing all Horizon data in Redis. You
    | may modify the prefix when you are running multiple installations
    | of Horizon on the same server so that they don't have problems.
    |
    */

    'prefix' => env('HORIZON_PREFIX', 'horizon:'),

    /*
    |--------------------------------------------------------------------------
    | Horizon Route Middleware
    |--------------------------------------------------------------------------
    |
    | These middleware will get attached onto each Horizon route, giving you
    | the chance to add your own middleware to this list or change any of
    | the existing middleware. Or, you can simply stick with this list.
    |
    */

    'middleware' => ['web', 'horizonBasicAuth'],

    /*
    |--------------------------------------------------------------------------
    | Queue Wait Time Thresholds
    |--------------------------------------------------------------------------
    |
    | This option allows you to configure when the LongWaitDetected event
    | will be fired. Every connection / queue combination may have its
    | own, unique threshold (in seconds) before this event is fired.
    |
    */

    'waits' => [
        'redis:default' => 60,
    ],

    /*
    |--------------------------------------------------------------------------
    | Job Trimming Times
    |--------------------------------------------------------------------------
    |
    | Here you can configure for how long (in minutes) you desire Horizon to
    | persist the recent and failed jobs. Typically, recent jobs are kept
    | for one hour while all failed jobs are stored for an entire week.
    |
    */

    'trim' => [
        'recent' => 60,
        'completed' => 60,
        'recent_failed' => 10080,
        'failed' => 10080,
        'monitored' => 10080,
    ],

    /*
    |--------------------------------------------------------------------------
    | Fast Termination
    |--------------------------------------------------------------------------
    |
    | When this option is enabled, Horizon's "terminate" command will not
    | wait on all of the workers to terminate unless the --wait option
    | is provided. Fast termination can shorten deployment delay by
    | allowing a new instance of Horizon to start while the last
    | instance will continue to terminate each of its workers.
    |
    */

    'fast_termination' => false,

    /*
    |--------------------------------------------------------------------------
    | Memory Limit (MB)
    |--------------------------------------------------------------------------
    |
    | This value describes the maximum amount of memory the Horizon worker
    | may consume before it is terminated and restarted. You should set
    | this value according to the resources available to your server.
    |
    */

    'memory_limit' => 64,

    /*
    |--------------------------------------------------------------------------
    | Queue Worker Configuration
    |--------------------------------------------------------------------------
    |
    | Here you may define the queue worker settings used by your application
    | in all environments. These supervisors and settings handle all your
    | queued jobs and will be provisioned by Horizon during deployment.
    |
    */

    'environments' => [
        'production' => [
            'supervisor-1' => [
                'connection' => 'redis',
                'queue' => ['default'],
                'balance' => 'simple',
                'processes' => 10,
                'tries' => 1,
            ],
        ],
        'development' => [
            'supervisor-1' => [
                'connection' => 'redis',
                'queue' => ['default'],
                'balance' => 'simple',
                'processes' => 10,
                'tries' => 1,
            ],
        ],
        'local' => [
            'supervisor-1' => [
                'connection' => 'redis',
                'queue' => ['default'],
                'balance' => 'simple',
                'processes' => 3,
                'tries' => 1,
            ],
        ],
    ],
    'basic_auth' => [
        'username' => env('HORIZON_BASIC_AUTH_USERNAME'),
        'password' => env('HORIZON_BASIC_AUTH_PASSWORD')
    ]
];

Now each of my environments have their on .ENV files with the appropriate information relative to horizon and redis, like so:

// Staging
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
HORIZON_PREFIX="horizon-development:"
REDIS_DB=0
REDIS_CACHE_DB=1

// Production
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
HORIZON_PREFIX="horizon-production:"
REDIS_DB=1
REDIS_CACHE_DB=2

This seems to be working nicely but would love to know how I can take advantage of the elusive supervisors. Additionally, I am still using the Daemon's on a per-site basis within Forge.

2 likes
lumiform's avatar

@radiantstatic you could use a set of different redis connections configured in queue.php to use different Redis installs per env. Now you use one Redis install for prod, staging and local, just separating jobs with prefixes. I do not find it secure.

You have same connection used for all envs 'connection' => 'redis', You could do in queues.php different connections

'redis-prod' => [..],
'redis-dev' => [...],
'redis-local' => [...],

then use them in horizon

'production' => [
            'supervisor-1' => [
                'connection' => 'redis-prod',
...
'development' => [
            'supervisor-1' => [
                'connection' => 'redis-dev',
...
'local' => [
            'supervisor-1' => [
                'connection' => 'redis-local',

in this way you can have 3 different IP addresses, 3 separate redis installs on separate servers - that will be secure.

Please or to participate in this conversation.