When you move a Laravel application from shared hosting to a VPS, queues and Laravel Horizon are often what change the most. On shared hosting, cron usually runs a single queue worker in the background. On a VPS, you suddenly have to decide how many workers to run, how much CPU and RAM they need, how big Redis should be, and what happens during traffic peaks. If you guess, you either overpay for an oversized server or end up with stuck jobs, slow checkouts and frustrated users. In this article, we will walk through a practical way to size a VPS specifically for Laravel Horizon and queues. We will translate business requirements like “send all emails within 2 minutes” or “generate reports within 5 minutes” into concrete numbers: vCPU, RAM, Redis memory and worker counts. The goal is to give you a repeatable method that works for small projects, growing SaaS products and busy e‑commerce sites running on dchost.com infrastructure.
İçindekiler
- 1 How Laravel Horizon, Queues and Redis Use Your VPS Resources
- 2 Step 1: Describe Your Queue Workload in Numbers
- 3 Step 2: Translate Workload Into Worker Counts and vCPU
- 4 Step 3: Estimating RAM for Workers, Redis and the OS
- 5 Step 4: Designing Horizon Worker Configurations
- 6 Step 5: Example VPS Sizing Scenarios for Laravel Horizon
- 7 Step 6: Monitor, Iterate and Know When to Resize
- 8 Bringing It All Together (and How dchost.com Fits In)
How Laravel Horizon, Queues and Redis Use Your VPS Resources
Before we talk numbers, it helps to understand how each component consumes resources on a VPS.
What Horizon Actually Does
Laravel Horizon is a dashboard and supervisor for queue workers. It does three main things:
- Starts and monitors your queue workers (how many processes, which queues, priorities).
- Keeps metrics about jobs (throughput, failures, runtime percentiles) in Redis.
- Provides a web UI to see job status and manage your workers.
Horizon itself is light; it is your queue workers that really consume CPU and RAM.
How Queue Workers Use CPU and RAM
Each worker is basically a long‑running PHP process. It:
- Bootstraps Laravel once, then repeatedly pulls jobs from the queue.
- Spends CPU time executing your job logic (sending email, resizing images, hitting APIs).
- Uses RAM to hold the Laravel framework, your code, loaded services and any in‑memory data it processes.
Roughly speaking:
- I/O‑bound jobs (calling external APIs, sending email) spend a lot of time waiting; they do not fully saturate the CPU.
- CPU‑bound jobs (PDF generation, image processing, encryption heavy tasks) keep the CPU cores busy.
This distinction is important because it determines how many workers you can safely run per vCPU.
What Redis Stores for Laravel Queues
When you use Redis as your queue driver, Redis stores:
- Pending jobs on each queue.
- Reserved and delayed jobs.
- Horizon monitoring data (throughput counters, failed job metadata, tags).
- Anything else you use Redis for: cache, sessions, rate limiting, etc.
Because Redis keeps everything in memory, the amount of RAM you assign to Redis directly limits how many jobs and how much Horizon history you can keep. In our Laravel production tune‑up guide for PHP‑FPM, OPcache, Octane and Redis, we explain why tuning Redis memory limits is critical; the same logic applies here.
Step 1: Describe Your Queue Workload in Numbers
Most sizing mistakes happen because we start from hardware (“maybe 4 vCPU and 8 GB RAM?”) instead of workload. Start by quantifying what your queues must handle.
Identify Job Types and SLAs
Make a simple table of your key job types:
- Transactional emails: order confirmation, password reset, notifications.
- Media processing: image resize, video thumbnail generation.
- Reporting / exports: daily reports, CSV exports, invoices.
- Billing tasks: subscription renewals, invoice generation.
- Real‑time UX jobs: broadcasting events, WebSocket pushes, quick cache updates.
For each job type, specify:
- Volume: jobs per minute / hour during normal and peak times.
- Latency target (SLA): how quickly it must finish from the time it is queued (e.g. 30 seconds, 2 minutes, 5 minutes).
- Priority: can it wait when the system is under load?
Example for an e‑commerce site:
- Transactional emails: 20/minute normal, 80/minute during campaigns; SLA 1–2 minutes.
- Image resize: 2/minute normal, 10/minute peak; SLA 5–10 minutes.
- Reports: 10/hour; SLA 15–30 minutes.
Measure or Estimate Job Runtime
Next, estimate how long a single job takes to run when executed by one worker. You can:
- Add simple timing logs around
handle()usingmicrotime(true). - Use Horizon’s job runtime metrics after a short test run.
For a starting point, you might see something like:
- Email job: 150–250 ms (depends heavily on SMTP or API performance).
- Image resize: 1–3 seconds (CPU intensive, depends on resolution and library).
- Report generation: 3–10 seconds (database + CPU work).
This per‑job runtime is the key to computing throughput and workers later.
Compute Required Throughput
For each job type, compute how many jobs per second you must complete to satisfy your SLA:
required_throughput = peak_jobs_in_SLA_window / SLA_seconds
Example for emails:
- Peak: 80 emails per minute.
- SLA: 120 seconds (2 minutes).
- In 120 seconds you may accumulate up to 160 jobs (80/min * 2 min).
- So you need to process 160 jobs in 120 seconds → 1.33 jobs/second.
Keep these numbers; we will match them against worker capacity in the next step.
Step 2: Translate Workload Into Worker Counts and vCPU
Now we use those job runtimes to estimate how many workers and how much CPU you need.
Throughput Per Worker
If a job takes t seconds on average, then a single worker can execute approximately:
worker_throughput ≈ 1 / t jobs per second
Examples:
- Email job: 0.2 s → 5 jobs/second per worker.
- Image resize: 2 s → 0.5 jobs/second per worker.
- Report: 5 s → 0.2 jobs/second per worker.
This assumes the worker is constantly busy, which is reasonable during peaks.
Workers Required to Meet SLA
To meet your SLA, you need:
workers_needed = required_throughput / worker_throughput
Using our email example:
- Required throughput: 1.33 jobs/second.
- Worker throughput: 5 jobs/second.
- Workers needed: 1.33 / 5 = 0.266 → round up to 1 worker.
For image resize jobs at peak 10/min (0.167 jobs/second) and 2 s per job:
- Worker throughput: 0.5 jobs/second.
- Workers needed: 0.167 / 0.5 = 0.334 → 1 worker is still enough.
The point is not to get an exact number, but a ballpark that you can refine with real monitoring.
Mapping Workers to vCPU
Workers translate to CPU usage differently for I/O‑bound and CPU‑bound jobs:
- I/O‑bound jobs: Because they wait on external services, you can often run 2–4 workers per vCPU without saturating the CPU.
- CPU‑bound jobs: They really use CPU cycles, so plan for about 1–1.5 workers per vCPU to keep load stable.
As a conservative starting point for a mixed workload:
- 1.5–2 workers per vCPU is usually safe on a modern VPS with PHP 8.x and OPcache enabled.
If your calculations say you need 8 workers for various queues, and you want to stay at 2 workers/vCPU, then:
vCPU_needed ≈ 8 / 2 = 4 vCPU
Remember the rest of the stack (PHP‑FPM for web traffic, MySQL/PostgreSQL, Nginx/Apache) also uses CPU. If your Laravel app handles both web and queue traffic on the same VPS, add at least one extra vCPU as a buffer.
We dive deeper into overall VPS sizing (CPU vs RAM vs NVMe) for PHP apps in our article how to choose VPS specs for WooCommerce, Laravel and Node.js; the same principles apply when Horizon is part of the picture.
Step 3: Estimating RAM for Workers, Redis and the OS
Once CPU and worker counts are roughly clear, you can size RAM. RAM is usually consumed by:
- The operating system and background services.
- Web stack (Nginx/Apache, PHP‑FPM, database server).
- Queue workers managed by Horizon.
- Redis and any other caches.
Baseline RAM for OS and Web Stack
On a lean Linux VPS (e.g. Ubuntu or AlmaLinux) running Nginx, PHP‑FPM and a small database, you should budget:
- 0.8–1.0 GB RAM for the OS and base services.
- 0.5–1.0 GB RAM for PHP‑FPM and the web layer on a low‑traffic app.
- 0.5–1.0 GB RAM for a small MySQL/PostgreSQL instance.
So even before Horizon, a modest Laravel app can easily use 2–3 GB of RAM under load. Our post on managing RAM, swap and the OOM killer on VPS servers explains why you should avoid running right at the edge of total RAM.
RAM Per Queue Worker
A single Laravel queue worker (PHP 8.x, OPcache, typical business code) tends to hover around:
- 80–200 MB of RAM per worker, depending on:
- The number of loaded service providers.
- How much data you process in memory (images, big arrays, etc.).
- Memory leaks in long‑running jobs.
To stay safe, assume:
ram_per_worker ≈ 150–200 MB
Then:
worker_ram_total = worker_count * ram_per_worker
If you plan for 8 workers at 200 MB each:
- 8 * 200 MB = 1.6 GB RAM for workers.
Redis Memory Sizing
Redis memory has two parts:
- Memory for queue data (jobs, delays, reserved jobs).
- Memory for Horizon metrics, cache, sessions, etc.
For many small and medium projects, Redis for queues and Horizon happily lives within 256–512 MB. If you also use Redis for cache and sessions, consider 512–1024 MB to leave room for growth.
A simple starting estimate:
- Queue + Horizon only: 256 MB.
- Queue + Horizon + cache/sessions: 512–1024 MB, depending on traffic and cache TTL.
Always set maxmemory in redis.conf so Redis cannot grow until the kernel kills it. Then pick a maxmemory-policy that fits your use (often allkeys-lru for cache‑heavy workloads, volatile-lru if you rely heavily on non‑expiring keys).
Putting RAM Sizing Together
Combine everything into a rough formula:
total_ram_needed ≈ OS_base + web_stack + database + workers + Redis + 20–30% headroom
Example for a mid‑size app:
- OS_base: 1 GB.
- Web_stack (Nginx + PHP‑FPM): 1 GB.
- Database: 1 GB.
- Workers: 1.6 GB (8 workers * 200 MB).
- Redis: 512 MB.
- Subtotal: 5.1 GB.
- Headroom 30%: ≈ 1.5 GB.
- Total: ≈ 6.5 GB → choose an 8 GB RAM VPS.
This type of back‑of‑envelope math prevents you from under‑sizing and fighting the OOM killer.
Step 4: Designing Horizon Worker Configurations
Now that CPU, RAM and worker counts roughly make sense, you can map them into an actual Horizon configuration.
Separate Queues by Latency Sensitivity
Do not dump every job into a single default queue. Instead, create queues with different SLAs:
high: user‑facing tasks that must be fast (emails, broadcasts, quick cache refresh).medium: normal background tasks.low: heavy, slow jobs like reports and imports.
Configure Horizon to run different worker counts for each queue based on the throughput calculations in Step 2. For example, on a 4 vCPU server:
- high priority: 4 workers.
- medium: 4 workers.
- low: 2 workers.
- Total: 10 workers → ~2.5 workers/vCPU (fine for mostly I/O‑bound jobs).
Setting Horizon Balancing and Max Jobs
Horizon supports different balancing strategies:
- simple: each worker handles all queues with priorities.
- auto: dynamically adjusts worker counts between queues based on load.
For most applications, start with auto balancing so Horizon can move capacity from low‑priority to high‑priority queues under load.
Also set:
maxProcessesor equivalent settings so Horizon does not spawn more processes than your RAM and vCPU budget.maxJobsper worker before restart, to clean up any slow memory leaks in long‑running workers.
PHP‑FPM vs Queue Workers
Remember that both PHP‑FPM (for web requests) and queue workers are PHP processes. If you configure both independently, you might accidentally allow too many PHP processes overall.
For example:
- PHP‑FPM pool:
pm.max_children = 20 - Horizon workers: 10
- Total potential PHP processes: 30
On a 4 vCPU, 8 GB VPS, 30 concurrent PHP processes is usually fine if they do not all run CPU‑heavy tasks at once. But if your app is heavy, consider lowering PHP‑FPM’s max_children or Horizon workers slightly. Our article on PHP‑FPM settings for WordPress and WooCommerce shows how to think about process counts; the same principles can be applied to Laravel.
Step 5: Example VPS Sizing Scenarios for Laravel Horizon
Let’s turn all of this into concrete example plans you can adapt on dchost.com VPS servers.
Scenario A: Small SaaS or Internal Tool
Profile: 10–30 concurrent users, a few hundred jobs per hour, mostly emails and light notifications.
- VPS suggestion: 2 vCPU, 4 GB RAM, fast SSD/NVMe.
- Typical jobs: email notifications, webhooks, simple data syncs.
Queue & worker plan:
- Queues:
high(emails, webhooks),low(reports). - Workers: 3–4 total (2 for high, 1–2 for low).
- Redis memory: 256–384 MB.
Why it works: With mostly I/O‑bound jobs, 3–4 workers on 2 vCPU gives enough parallelism without overloading CPU. 4 GB RAM is adequate for OS, Nginx, PHP‑FPM, a small database and a modest Redis instance, as long as you keep PHP‑FPM pool sizes reasonable.
Scenario B: Growing E‑Commerce with Campaign Peaks
Profile: 50–200 concurrent users during campaigns, thousands of orders per day, heavy bursts of email and some on‑the‑fly image processing.
- VPS suggestion: 4–6 vCPU, 8–12 GB RAM.
- Typical jobs: order emails, newsletter queue push, image thumbnails, invoice generation.
Queue & worker plan:
- Queues:
high(order emails, payment notifications),medium(image processing),low(reports, bulk newsletters). - Workers on 4 vCPU: 10–12 total (4 high, 4 medium, 2–4 low).
- Redis memory: 512–1024 MB (because Horizon metrics and cache will grow with traffic).
Why it works: 10–12 workers on 4 vCPU equals ~2.5–3 workers/vCPU, which is fine because most jobs are I/O‑bound except image processing. Put CPU‑heavy jobs on the medium queue and limit workers there so they do not starve the system. Use Horizon’s auto balancing to move extra capacity to high during peaks.
Scenario C: Heavy Background Processing / Analytics
Profile: A Laravel app that runs heavy reports, data imports, or complex billing calculations. Web traffic is modest, but queue load is high and CPU‑bound.
- VPS suggestion: 8 vCPU, 16 GB RAM (or a dedicated server if queues dominate your workload).
- Typical jobs: large CSV imports/exports, complex invoice runs, advanced reporting, integrations with many APIs.
Queue & worker plan:
- Queues:
high(user‑visible tasks),batch(heavy processing),low(maintenance, slow reports). - Workers: around 12–14 total, but with only 6–8 assigned to the CPU‑bound
batchqueue. - Redis memory: 1–2 GB if Horizon keeps long history and you cache a lot of data.
Why it works: You intentionally keep workers per vCPU close to 1.5–2 when jobs are CPU‑bound, to avoid constant 100% CPU usage. With 16 GB RAM you can allocate more to the database and Redis while still leaving room for Horizon workers and PHP‑FPM. On dchost.com this is a typical size we see for analytics‑heavy Laravel applications before they move to dedicated servers or multi‑VPS architectures.
Step 6: Monitor, Iterate and Know When to Resize
No sizing guide is complete without monitoring. Your first configuration will always need tuning once real traffic hits.
Metrics to Watch for Horizon and Queues
Monitor at least these metrics:
- Queue length over time: Are queues consistently growing (under‑provisioned) or empty (maybe over‑provisioned)?
- Job wait time and runtime: Horizon shows how long jobs wait before being processed and how long they take to run.
- CPU usage: If average CPU is near 80–90% during peaks, reduce workers or upgrade the VPS.
- RAM usage and swap: If your VPS starts swapping, reduce PHP‑FPM / worker counts or move to a larger RAM plan.
- Redis memory and eviction: Check for evicted keys if your
maxmemoryis too low.
Our article on monitoring VPS resource usage with htop, iotop, Netdata and Prometheus gives practical commands and dashboards you can reuse for Laravel Horizon setups.
When to Add More Workers vs When to Upgrade the VPS
You have two main levers:
- Increase worker counts if CPU and RAM are still comfortable (e.g. CPU < 60%, plenty of free RAM) but queues are growing during peaks.
- Upgrade the VPS (more vCPU/RAM) if CPU is consistently high or RAM is close to full even with conservative worker and PHP‑FPM settings.
As you scale, you might eventually split roles: one VPS for web + Horizon dashboard, another for queue workers, another for the database. Our guide on deploying Laravel on a VPS with Nginx, PHP‑FPM, Horizon and zero‑downtime releases shows how to structure those deployments cleanly.
Keep Background Jobs First‑Class in Your Architecture
Queues are not an afterthought; they are core to how your application feels to users. We explored this in detail in our article why background jobs matter so much on a VPS. The same lesson applies here: design your VPS sizing, Horizon configuration and Redis memory limits around your queue workload, not the other way around.
Bringing It All Together (and How dchost.com Fits In)
You do not need to guess when sizing a VPS for Laravel Horizon and queues. Start from your workload: job types, peak volume and latency targets. Measure or estimate per‑job runtimes to compute throughput per worker. From there, decide how many workers you actually need, and map that to a realistic number of vCPUs. Then allocate RAM for the OS, web stack, database, Horizon workers and Redis with at least 20–30% headroom to stay clear of the OOM killer. Finally, design Horizon’s queue groups and worker distributions to prioritise user‑facing jobs while giving heavy batch tasks their own space.
On dchost.com, you can start with a modest VPS plan (for example 2–4 vCPU, 4–8 GB RAM) and adjust as Horizon metrics and system monitoring show real behaviour. Because we also offer dedicated servers and colocation, you have a smooth path forward when your queues grow beyond a single VPS. If you are planning a new Laravel project or want to stabilise an existing Horizon setup, our team can help you translate your job workloads and SLAs into a concrete VPS or server plan that fits both your performance needs and your budget.
