The UDX handbook / 01
How to run WordPress on Linux, properly.
WordPress is a system you operate.
Here is how to make every layer hold.
The operating idea, in sixty seconds.
Installation is solved. Operation is not. The sites that fail are usually installed correctly; the decisions around them are what break under load.
- Give every layer an owner.Edge, application, cache, data, media, pipeline and evidence.
- Follow the evidence.Our 19 incidents trace failures to the system around WordPress.
- Use the complexity you need.Steady traffic and inexpensive downtime? A managed host may be enough.
a clear responsibility
operating record
image delivery in one incident
an incomplete purge map
Why this handbook now? The questions are changing.
Falling: the install era
Rising: the operating era
Change in US search demand, twelve months to September 2026 vs. the prior twelve. Source: Ahrefs. All bars use the same scale. Developer queries are undercounted; read the direction, not the size.
Seven layers. One system.
One server, one folder, one person who knows how it works. That model holds until the first traffic spike, the second server, or the day that person leaves.
Think in responsibilities instead. Each layer has one job, a clear owner, and evidence that it is doing its job. Linux is the foundation, whether you run one virtual machine or a Kubernetes node pool.
The operating model
Seven responsibilities. One system.
Linux underneath.
01Edge
Chapter 04 ↗A CDN answering anonymous page views, assets and bots before they reach PHP.
Without itEvery crawler and scanner lands on your servers, and your capacity goes to an audience your analytics never shows you.
02Application
Chapter 02 ↗WordPress as identical, disposable containers: no uploads, no sessions, no local state.
Without itOne server becomes special, and the site can never be run twice.
03Cache
Chapter 04 ↗An object cache for repeated queries, a page cache for rendered output, explicit rules for expiry.
Without itThe database absorbs traffic a cache should have answered, and the first popular page to expire starts a stampede.
04Data
Chapter 10 ↗A managed database with automated backups, a replica, and a snapshot before every risky change.
Without itThe night an update goes wrong is the night you learn what your backups actually contain.
05Media
Chapter 06 ↗Uploads in object storage with an image service in front, so the app tier never touches a file.
Without itThe second server cannot see the images the first one received.
06Pipeline
Chapter 07 ↗Changes arrive through source control, built once, tested on staging or a preview, then promoted.
Without itProduction is edited by hand, and nobody can say what is running or how to put it back.
07Evidence
Chapter 05 ↗Edge logs, application logs and metrics somewhere you can query after the fact.
Without itThe only record of an outage is a screenshot from a customer, and the fix is a guess.
Where production sites actually fail.
Real symptoms. Traceable causes. Nineteen incidents from sites we operate, fixes we shipped, and support for WP-Stateless. Client names stay private; the mechanisms do not.
Inside incident 01 / Application
~1,850 errors a weekThe health check passed.
WordPress was not ready.
New containers took traffic after six seconds. PHP needed thirty-five. Every scale-up opened a window for 503s.
29 seconds of false readiness
The evidence903 of the last 1,000 errors were backend connections closed at scale-up.
The correctionProbe the real PHP path and raise the replica floor.
19 incidents. Select a symptom to see the cause and evidence.
01503s whenever the cluster scales upApplication
ObservedBursts of 503 errors, about 1,850 a week, clustered at the minutes the cluster scaled up.
CauseThe load balancer health check watched a helper process that was ready in six seconds, while PHP behind it needed thirty-five. New pods took traffic before they could serve it, and the autoscaler flapped between two and four replicas.
Evidence & resolutionLoad balancer log: 903 of the last 1,000 errors were backend connections closed at scale-up. Fixed by probing the real PHP path and raising the replica floor.
02Logins fail after a CDN changeCache
ObservedLogins failing with 403 after a routine CDN configuration change.
CauseA security token was being served from a cached JSON response, so every visitor received the same expired token.
Evidence & resolutionA diff of the CDN behavior and the headers its cache key kept.
03Event listings stay stale for hoursCache
ObservedEvent listings up to 11.6 hours stale while the CDN purge count reached about 270 a day.
CauseA nightly sync re-saved about 150 events and each save fired its own purge, which overran the 30-second gateway timeout before the last ones ran.
Evidence & resolutionCDN invalidation history: 1,282 purges in five days. Fixed by skipping sync saves and issuing one wildcard purge.
04Content updates stop reaching visitorsCache
ObservedContent changes silently stopped reaching visitors.
CauseTwo plugins bundled different versions of the same cloud authentication library, and the cache purge call failed on an undefined method.
Evidence & resolutionOne line in the PHP error log. Fixed by namespacing the vendored library in our plugin and upgrading the fleet.
05Image URLs change on the first of the monthMedia
ObservedEvery image URL on a site changed to the current month on the first of the month.
CauseThe bucket had been switched to uniform access, so the per-file permission write failed, the file's metadata never saved, and the path was regenerated with today's date on every request.
Evidence & resolutionA plugin issue thread that reached 37 comments before the cause was isolated, and the bucket's access setting. Uniform access becomes permanent after 90 days.
06The image service runs out of memory dailyMedia
ObservedAn image service pod killed for memory once a day.
CauseThe CDN honored a 60-second cache lifetime instead of the application's four hours, so the origin re-optimized 3,160 images an hour for 303 distinct variants.
Evidence & resolutionCDN hit ratio alongside the container memory graph. Fixed with an origin-honoring cache policy and a memory arena cap.
07Images fail while monitoring stays greenMedia
ObservedImages failing for 65 percent of visitors over thirty days, with a p95 response time of 30 seconds.
CauseAn upstream image optimizer bug: one aborted request on a cold cache key wedged that key for every later visitor, and a year of cached HTML kept asking for an image width the site no longer generated.
Evidence & resolutionThirty days of CDN standard logs, 2,032 unique clients, status 000 for viewer disconnects. Nothing in application monitoring showed it.
08Scheduled tasks and newsletters stopEdge
ObservedScheduled tasks stopped and a newsletter queue froze.
CauseA redirect rule at the edge sent the cron ping to the homepage, so it never reached PHP.
Evidence & resolutionEdge logs showing 301 responses for the cron path. Fixed with a bypass header for internal callers.
09A deployed fix still renders incorrectlyCache
ObservedA page fix deployed, invalidated and confirmed fresh, yet the page kept rendering wrong.
CauseThe front end fetches each page a second time with a query flag for its layout data, and that variant is a separate cache object the invalidation never touched.
Evidence & resolutionThe exact request the application makes, not the one a human makes in a browser. Invalidate with wildcards.
10Some visitors get trapped in a redirect loopCache
ObservedA homepage redirect loop for some visitors while every monitor stayed green.
CauseThe CDN cache key ignored query strings but forwarded them to the origin. A crawler hit a junk legacy URL, WordPress answered with a canonical 301, and the CDN stored that redirect under the bare homepage key for a day.
Evidence & resolutionEdge logs: about 8 percent of homepage visitors at the affected edges, two of them looping 378 and 150 times. Fixed by forwarding only whitelisted parameters, then purging everything, because a fixed policy does not evict what is already poisoned.
11Edits stay invisible for up to a dayCache
ObservedEdits and releases invisible to visitors for up to a day.
CauseHTML cached for 24 hours, and the save hooks purged only the edited permalink. The homepage, menus, sitemaps and the pages affected by a deploy were never purged; the release flushed the object cache and nothing else.
Evidence & resolutionA purge coverage map built from the save hooks against the edge lifetimes. Fixed with purges in the release and a short HTML lifetime.
12A PHP upgrade breaks a formApplication
ObservedFatal errors on a form page after a PHP upgrade, 120 occurrences in a week.
CauseAn abandoned add-on used a function removed in PHP 8.
Evidence & resolutionApplication monitoring grouped by error. Fixed with a small shim while the add-on was replaced.
13PHP errors with no user reportsEdge
ObservedHundreds of PHP errors in monitoring with no user reports.
CauseScanners were fetching core bootstrap and sample configuration files directly, and the files answered.
Evidence & resolutionError groups by request path. Closed with a web server rule that returns 404 for those files.
14An attack writes a web shell to cacheSecurity
ObservedMore than 27,000 requests fleet-wide to one REST endpoint in a day, and a web shell written to a cache directory on one site.
CauseA core vulnerability before the patch was available, plus a web server that would execute any PHP file wherever it was written.
Evidence & resolutionEdge logs. The shell returned 404 only because the container filesystem is ephemeral. Fixed with a firewall rule and a web server deny for PHP under uploads and cache.
15PHP tuning has no effect under loadApplication
ObservedOut-of-memory kills and 502s in traffic spikes despite per-site PHP tuning.
CauseThe init script edited the PHP-FPM pool file as the wrong user, the error was swallowed, and every site silently ran the default of 15 workers.
Evidence & resolutionReading the live pool config inside the container, not the repository: 22 deployments affected. Fixed by mounting the config read-only and asserting it at boot.
16A fresh install never startsData
ObservedA fresh install that never came up.
CauseMySQL takes about 100 seconds to initialize on burst-starved nodes, and the liveness probe killed it at 60 to 90 seconds, every time.
Evidence & resolutionPod events: exit 137 at the same age on every attempt. Fixed with a five-minute startup probe.
17The login page fails only in productionApplication
ObservedLogin page returning 502 on production only.
CauseThe production web server had 64k FastCGI buffers while staging had 256k, and chunked responses without a content length also made the CDN skip compression.
Evidence & resolutionA configuration diff between the two environments. Fixed in infrastructure code for both, which is where the drift started.
18Production URLs point at developmentData
ObservedProduction canonical URLs pointing at a development hostname.
CauseA development environment had been pointed at the shared database and WordPress wrote its own hostname into rows.
Evidence & resolutionA database query for the development hostname: 57 rows, 54 bad sitemap entries. Fixed from a backup with a flush and a purge.
19A site appears to alternate between two versionsEvidence
ObservedA site that appeared to flip between two renderings for a week, with the object cache suspected.
CauseOur own observer was wrong: one screenshot suite alternated between staging and production against a single baseline. A controlled cache flush rebuilt cleanly and ruled out poisoning.
Evidence & resolutionFour days lost before the suite was split. Verify the instrument before the system.
Go deeper, one layer at a time.
Go deeper where your system needs it. Each chapter connects an operating decision to the failures it prevents.
The Operating Model
WordPress as a system, not a site: the seven layers and who owns each one.
Start hereContainers
What belongs in the image, what belongs in a volume, and running the same build everywhere.
Read chapterKubernetes
Stateless pods, horizontal scaling, rolling upgrades, and the state you have to move out first.
Read chapterCaching Layers
Object cache, page cache, CDN: what each one actually fixes and how they fail together.
Read chapterSlow Responses and 500s
Diagnosing from the edge inward, with a worked example reconstructed from CDN logs.
Read chapterMedia and Images
Uploads in object storage, image delivery, and the day an image optimizer took a site down.
Read chapterPipelines and Environments
CI/CD for WordPress, staging that matches production, preview environments per branch.
Read chapterSecurity as Operations
What attacks on a business site look like by the numbers, and the first hour after a breach.
Read chapterWordPress as a Content API
Headless WordPress with Next.js and React: when it is the right call and how to host the pair.
Read chapterBackups, Migration and Recovery
What a backup must contain, how to prove a restore works, and moving a site without downtime.
Read chapterNext in the handbook
How much of this do you need?
Keep it simple
Most sites do not need a cluster.
Steady traffic, occasional content changes, and an hour of downtime that costs an apology? A good managed host is probably right.
Keep the useful habits: tested backups, staging, media in object storage, and a CDN.
Take the next step
Operations become a business concern.
- Traffic arrives in spikes you cannot schedule.
- An outage costs revenue or public trust.
- You run several sites and need repeatable releases.
- You need proof of how the system is operated.
- Your host’s answer is always a bigger plan.
The practice behind the handbook.
We run this architecture ourselves.
This site runs WordPress in stateless containers on Google Kubernetes Engine, with a React front end, media in Google Cloud Storage through WP-Stateless, and CloudFront at the edge. It deploys from Git through Rabbit, the same pipeline we use for client sites.
Our source material is the operating record: incidents, pull requests, and the things we would do differently. We publish mechanisms and numbers while protecting client identities and internal systems.
Before you commit.
Can WordPress run on Linux in production without a managed host?
Yes. WordPress runs on Linux everywhere, from a single virtual machine to a Kubernetes cluster. What determines whether it stays up is not the host but whether the layers around it are deliberate: a CDN at the edge, stateless application containers, an object cache, a managed database with backups, media in object storage, a deployment pipeline, and logs you can query.
Why is my WordPress server slow even though traffic is low?
The usual causes are a missing object cache so repeated queries hit the database, WP-Cron firing heavy scheduled jobs on visitor requests, a plugin writing to the options table on every page view, or PHP workers tied up by slow external calls. Diagnose from the edge inward: check what the CDN served, then PHP worker saturation, then slow queries.
Do I need Docker or Kubernetes to run WordPress well?
Not always. Containers make WordPress reproducible and are worth it for any site that is deployed more than occasionally. Kubernetes earns its complexity when traffic spikes, downtime is expensive, you operate several sites, or you need to prove how the site is operated for compliance. For a modest, steady site, a good managed host is the right answer.
Should WordPress uploads be stored on the server?
No. Uploads on local disk are the first thing that breaks when a second server exists, and they make backups and migrations fragile. Store media in object storage such as Google Cloud Storage or S3 and serve it through an image CDN, so the application tier never touches a media file. WP-Stateless does this for Google Cloud.
What is the minimum setup for a production WordPress site on Linux?
A CDN in front, media in object storage, a persistent object cache, automated database backups with a tested restore, a staging environment that matches production, and request logs retained somewhere you can search. Those six cost little and prevent most of the incidents in our operating record.
Apache or nginx for WordPress?
Either serves WordPress well. The web server is rarely where production sites fail. Spend the attention on the edge cache, the object cache and the pipeline instead, and pick the server your team can configure and debug confidently.
Make your WordPress operation dependable.
Tell us where your site runs and what keeps going wrong. Talk with an engineer about the missing layers and the work needed to put them in place.