WordPress at a Million Visitors
Yes, WordPress can handle it. The question is whether your architecture can. Here is the stack we run in production, and where sites actually break first.
Talk to an EngineerThe Short Answer
A WordPress site can serve a large audience, but monthly visitor totals do not describe its capacity. The expensive work is often in the request path: a single origin server answering every request personally, media files chained to one machine's disk, a database absorbing traffic that a cache should have answered. The platform gets blamed for decisions the platform never made.

We run this architecture on udx.io, but that is evidence of an operating pattern, not a capacity guarantee for another site. A million cached page views and a burst of uncached checkout requests are different workloads. Load-test the actual plugins, queries, logged-in flows, cache-hit mix, and burst rate before promising a traffic target.
One honest caveat before the diagrams, because it will save some readers the rest of the page: if your traffic is steady and modest, and an hour of downtime costs you little, you do not need any of this. A good managed host is the right answer for most sites, and pretending otherwise is how agencies sell Kubernetes to bakeries. This page is for the sites where the graph goes vertical, the stakes are real, and "we'll upgrade the plan" has stopped working.
What a Month of Traffic Actually Looks Like
Scaling plans tend to imagine a million human visitors arriving with good intentions. The logs tell a stranger story. We analyzed thirty days of CloudFront traffic for this site, 575,092 requests and 13.84 gigabytes served, including substantial automated traffic. A missing or scripted user agent alone does not establish malicious intent; the requested paths, responses, and request pattern provide the useful context. More than 62,000 requests arrived with no user agent, and about half were blocked outright. Another 27,851 came from curl, the bulk of it automated probing: seventeen thousand of those requests hammered paths that do not exist, hunting for exposed backups and login endpoints, and scanners methodically enumerated the user APIs of every WordPress membership plugin on the market against a site that runs none of them.
Then there is the newest constituency: AI crawlers. Bytespider, ByteDance's crawler, made 1,824 requests and pulled 147 megabytes on its own; ClaudeBot followed at 1,329. This category barely existed a few years ago and now shows up in every log review we do, which means your architecture is serving an audience your analytics never shows you.
| Traffic category | 30-day volume | What we do with it |
|---|---|---|
| All requests | 575,092 (13.84 GB) | The baseline: everything below is inside this |
| No user agent | 62,783 | About half blocked outright; the rest watched |
| curl and scripted probes | 27,851 | Mostly scanner noise; 17,000+ hit paths that do not exist |
| Bytespider (ByteDance) | 1,824 requests, 147 MB | Steered with robots.txt, served from edge cache |
| ClaudeBot | 1,329 requests | Same: robots.txt rules, edge cache absorbs it |
| SEO crawlers (Ahrefs, MJ12) | Previously blocked | Unblocked: we want these visiting |
Two design consequences follow. First, a meaningful slice of your capacity will always be spent on non-humans, so the economics matter: a cache hit avoids origin work, while an uncached request reaching PHP occupies a worker a customer could have used. Edge requests still have delivery and security costs. Let the edge do that work. Second, blunt instruments cut both ways. Our own firewall rules turned out to be silently blocking the SEO crawlers we very much wanted visiting, and we use robots.txt to express preferences to cooperating crawlers. It does not enforce access control; unwanted or abusive traffic still needs appropriate authentication, rate limits, or firewall rules.
The Architecture That Holds
Edge first
The organizing principle of this architecture is that most requests should be answered before they ever reach WordPress. A CDN like CloudFront serves anonymous page views, assets, and feeds straight from cache at the edge, which means your origin only meets the traffic that genuinely needs PHP: logged-in users, carts, form submissions, and the occasional cache miss. At scale, the ratio is dramatic, and every request the edge absorbs is capacity you did not have to build.
A stateless application tier
The WordPress containers themselves hold nothing of value: no uploads on disk, no local sessions, nothing that makes one server special. That discipline comes straight from the twelve-factor playbook, and it is what makes horizontal scaling possible at all, because any pod can serve any request, and the cluster can add or remove them without ceremony.
Media off the server
Uploads belong in object storage, not on a web server. Our WP-Stateless plugin moves WordPress media to Google Cloud Storage, and with 469,000+ downloads it has been battle-tested well beyond our own client work. An image CDN serves and resizes from the bucket, while PHP can still receive uploads and create derivatives in temporary storage. The durable copy belongs in the bucket; delivery belongs at the CDN. Chapter 06 traces that media lifecycle, including what a replacement container must be able to discard.
Horizontal autoscaling on managed Kubernetes
We run multi-zone clusters on GKE, deployed from Git with Rabbit. Autoscaling can add pods as demand grows, but metrics, scheduling, image pulls, and readiness checks take time. Keep enough capacity for the initial burst and verify that the database can support the extra workers; adding pods cannot repair a database bottleneck. The operational cadence matters as much as the automation: cluster upgrades roll through gradually, control planes in minutes and large node pools over the better part of an hour, and we schedule them early in the week, never on a Friday.

The database behind a shield
With the edge absorbing reads and object storage handling media, the database is left with the work only it can do: writes and genuinely dynamic queries. Persistent object caching keeps the repetitive queries out entirely, which is the difference between a database that hums through a traffic spike and one that becomes the evening's incident channel. Good telemetry tells you which one you have before your customers do.
What Breaks First
The encouraging thing about WordPress scaling failures is how predictable they are. After enough launches, you stop being surprised and start keeping a list. Ours, roughly in the order sites hit them:
Uploads on local disk is almost always first. The moment a second server exists, media written to one is missing from the other, and the support tickets begin. This is why stateless media is the first move in the architecture, not a refinement for later. Its smaller sibling, PHP sessions on disk, produces the same split-brain in miniature; shared sessions need an explicitly configured store with suitable expiration, concurrency, and failure behavior. An evictable object cache is not automatically a safe session store; avoid server-side sessions when the application does not need them.
Cache stampedes arrive next: a popular page expires and a thousand concurrent requests all volunteer to regenerate it at once, briefly turning your cache into a denial-of-service tool aimed at your own database. Request coalescing, expiration policies, and bounded regeneration can reduce the burst, but the behavior must be tested with the cache and application you actually use. WP-Cron compounds the problem at the worst moments, because traffic triggers checks for due scheduled tasks by default. A separate scheduler makes that trigger predictable, but jobs still need concurrency controls, resource limits, and monitoring.
And then there is the plugin you forgot about: one modest-looking plugin writing to the options table on every page view will happily hold a database lock through your biggest traffic hour of the year. The lesson is to load-test the site you actually run, production plugins and all, rather than the clean install that exists nowhere. If this list reads uncomfortably like a diagnosis of your own site, that is precisely what our enterprise WordPress work exists for.
A Cache Lesson We Paid For
A war story from this very site, offered as evidence that the cache humbles everyone eventually. Our frontend is React running against WordPress as a headless CMS, and it fetches each page a second time with a query flag to retrieve its layout data as JSON. Our CloudFront cache policy includes the json query parameter, so the HTML and ?json response occupy distinct cache entries. Query strings affect a CloudFront cache key only as configured in that policy. We shipped a layout fix, invalidated the page URL, confirmed the HTML was fresh, and watched the page continue to render wrong anyway, because the invalidation had purged the page but not its JSON twin, and the application kept hydrating from the stale copy. Nothing violated the rules we had given the CDN. The rules simply were not the ones we believed we had written.
The takeaway is worth generalizing: enumerate your cache keys before you need them, know every variant your application requests, invalidate all affected variants with appropriately scoped paths, and verify with the exact requests your code makes rather than the ones a human makes in a browser. At a million visitors, the cache is not an optimization layered onto your architecture. It is the architecture, and its configuration deserves the same rigor as code.
Array
The pieces of this architecture, and where we write about running them.
WordPress media on Google Cloud Storage. 469,000+ downloads.
GitHub-to-Kubernetes GitOps deployment automation.
Media management and protection at enterprise scale.
The same architecture, with public-sector compliance.
Managed cloud-native WordPress hosting by UDX.
How we think about automation, monitoring, and delivery.
What the attack traffic in the table above actually becomes, and the plan for it.
When a Data Change Is a Production Deployment
The data-plane side of running a database-backed platform safely.
"
Part of the UDX handbook
Chapter 03 of 10 / Application layer
How to run WordPress on Linux, properly
This page is the Kubernetes chapter of a ten-chapter handbook on running WordPress on Linux, written from our own operating record; start at the handbook for the layer map, the incident record and the other nine chapters.
Talk to an Expert
Connect with our diverse group of UDX experts that can help you implement successful DevOps practices within your organization.