Engineering & Performance
Scaling a Laravel Application: From 100 to 100k Users
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
Designing REST APIs in PHP/Laravel That Don't Become Legacy
Patterns for designing PHP and Laravel REST APIs that age well — versioning, OpenAPI, idempotency, error contracts, and backward compatibility.
PHP Security in 2026: OWASP Top 10 Applied to Real PHP Code
OWASP Top 10 vulnerabilities mapped to real PHP and Laravel patterns — with concrete code examples of the bug and the fix.