Engineering & Performance

Scaling a Laravel Application: From 100 to 100k Users

·12 min read·By Abimael Espinoza

Laravel scales — but not by accident. Every order of magnitude (100 → 1k → 10k → 100k users) introduces a different bottleneck. Here's what I optimize at each stage, in order.

0–100 users: don't optimize, observe

At this stage your only job is observability. Add Telescope (dev), Sentry (errors), and an APM (NewRelic / Scout / DataDog). You can't fix what you can't see. Premature optimization is the actual root of all evil.

100–1k users: fix the obvious

N+1 queries

By far the #1 Laravel performance issue. Every collection iteration that triggers a related query is a death-by-thousand-cuts. Use eager loading aggressively:

// Bad: N+1
$posts = Post::all();
foreach ($posts as $post) { echo $post->author->name; }

// Good: eager loaded
$posts = Post::with('author')->get();

Database indexing

Slow query log on, threshold 200ms. For every query above the threshold, add a composite index matching the WHERE + ORDER BY columns. A single missing index can be the difference between 10ms and 10 seconds.

1k–10k users: caching and queues

Redis cache layer

  • Cache expensive queries: leaderboards, dashboards, aggregate counts.
  • Use cache tags for granular invalidation.
  • Set TTLs based on staleness tolerance — most data tolerates 30–300s.

Move work to queues

Anything that doesn't have to happen during the request: emails, webhooks, image processing, third-party API calls. Use Horizon to monitor queue depth and worker health.

OPcache + JIT

Enable OPcache in production with realistic memory (256MB+). PHP 8 JIT helps CPU-bound work; for typical request/response Laravel apps, the gain is 5–15%.

10k–100k users: horizontal scaling

Stateless application servers

  • Move sessions to Redis or DB (`SESSION_DRIVER=redis`).
  • Move file uploads to S3 (`FILESYSTEM_DISK=s3`).
  • Use a shared cache (Redis cluster or ElastiCache).

Database scaling

  • Read replicas: route read queries to replicas via Laravel's database `read`/`write` config.
  • Connection pooling (PgBouncer for Postgres) to handle connection storm.
  • Move heavy reporting to a separate replica or warehouse (BigQuery, Snowflake).

CDN and edge caching

Put Cloudflare or CloudFront in front. Cache static assets aggressively. Use stale-while-revalidate for semi-dynamic pages. Even logged-in pages can have edge-cacheable fragments.

Beyond 100k: architectural surgery

  • Split the monolith into bounded services where team boundaries hurt — not for fashion.
  • Move read-heavy workloads to projection tables (CQRS-lite).
  • Consider Octane (Swoole / RoadRunner) for persistent worker processes — 2–5x throughput on appropriate workloads.
  • Sharded databases for tenants beyond what a single primary can serve.

What I rarely recommend

  • Rewriting in Go/Rust because 'PHP doesn't scale' — Laravel scales fine to millions of users with the right architecture (see Laravel Forge, Statamic, etc).
  • Microservices before 20+ engineers — adds operational overhead with no benefit.
  • Caching everything — invalidation will become your full-time job.

Need a hand?

Hiring or modernizing PHP? Let's talk.

16+ years building, scaling, and rescuing PHP applications. Direct contact, no marketplace, US time zones from LATAM.

Related reading