Cloud Reference Architecture

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This sub-section’s WordPress bibliography lists the reference material consulted while preparing these pages.

This page was written against WordPress 7.1.3 (released 2026-10-06; 7.1.4, the security release of 2026-10-28, is the version to run), PHP 8.4, WP-CLI 2.12.0, MariaDB 11.8 and Kubernetes 1.36 and 1.37 (both available on Amazon EKS), and checked on 2026-10-29. It is the first page of the "Cloud Deployment at Scale" part of this section and the map for the other eight. It names no prices: sizes are starting points to measure on your own traffic.

Scaling and High Availability explains what a WordPress cluster needs (shared uploads, identical keys and salts, one cron runner, proxy-aware HTTPS) and proves it on a two-node staging cluster built with Docker. This page and the next eight take the same ideas to a public cloud: which managed service replaces each part of the single server, how the pieces connect, how big to make them, and where the details are. The running example is Lumen Press, the fictitious publisher used throughout this section, moving its production site www.lumenpress.example from one virtual machine to an Amazon EKS cluster in eu-west-1. Shared cloud fundamentals are linked, not repeated: VPC Networking, EKS Clusters, Amazon RDS and the rest of the AWS section.

Two architectures

Single server with a bundled database compared with a tiered cloud architecture

The single server is the architecture that most WordPress sites have and most should keep. One machine runs the web server, PHP, the database, the cron trigger and the files. Everything is local, so everything is fast and simple: there is one place to back up, one log to read, and no network between PHP and MySQL. Its limit is the machine. A failed disk, a bad kernel update or a noisy neighbour takes the whole site down, and the recovery is a restore from a backup that is only as fresh as the last copy (see Backups and Disaster Recovery). Vertical scaling postpones the problem but never removes it.

The tiered architecture separates compute from state. The web tier is a set of identical, disposable pods built from one container image. They hold no data worth keeping, so Kubernetes can add, remove, replace and spread them over zones at any time. Everything that must survive moves to a service that is replicated by its provider: the database, the object cache, the media store, the secrets. A scheduler runs WordPress cron once. This is the stateless web tier design of the "twelve-factor" style, applied to WordPress, which was written for a single server and has to be told, one setting at a time, to stop trusting the local disk.

What the tiered design buys, and what it costs:

Single server Tiered on Kubernetes

Availability

One host; recovery means restore or rebuild

Pods in several zones, a managed database with a standby, a load balancer that removes failed pods

Capacity

The largest machine you will buy

Pods added by an autoscaler; the database remains one writer

Deployments

Edit files or run an update on the server

A new image, a rolling update and a rollback, with schema changes in a Job before the rollout

Plugin and theme changes

From the dashboard

Through the image and a pipeline; the dashboard is read-only

Moving parts

One

A cluster, a registry, a database, a cache, an object store, a CDN, a secret store, a pipeline

What can go wrong

The host

Misconfiguration between the parts, and the cost of operating them

Skills

A systems administrator

Kubernetes, a cloud, infrastructure as code and a delivery pipeline

The honest summary is that the tiered design trades simplicity for availability and capacity. Section "When one server is still right" at the end says when not to make that trade.

The request path

flowchart LR V[Visitor] -->|HTTPS| DNS[Route 53 and CloudFront] DNS -->|uploads and static files| S3[(S3 media bucket)] DNS -->|dynamic pages and cache misses| ALB[Load balancer] ALB -->|HTTP, X-Forwarded-Proto| NG[nginx in a pod] NG -->|FastCGI 9000| FPM[php-fpm in the same pod] FPM -->|object cache| VK[(Valkey)] FPM -->|SQL over TLS| DB[(RDS primary)] DB -.->|synchronous standby| DB2[(RDS standby, other zone)] FPM -->|secrets at start| SM[Secrets Manager]

Following one request for a page that is not in the CDN:

  1. The visitor resolves www.lumenpress.example to CloudFront. The CDN serves cached pages and static files. Requests for /wp-content/uploads/* go to the S3 bucket through the same distribution (see Media: S3 Offload, EFS and CloudFront). Everything else goes to the origin.

  2. The load balancer terminates TLS at the origin, picks a healthy pod and adds X-Forwarded-Proto and X-Forwarded-For. wp-config.php must trust that header, or WordPress loops on redirects; the setting is in Scaling and High Availability and the pod configuration in Deploying on EKS with Helm.

  3. nginx in the pod serves static files itself and hands PHP requests to php-fpm over FastCGI on the pod’s loopback address. Two containers in one pod keeps the pair together and lets them scale as a unit.

  4. PHP runs WordPress. It asks the object cache (Valkey) first, which removes most database reads, and connects to the database only when it must. See Object Cache, Sessions and Cron in a Cluster.

  5. The database is a managed primary with a synchronous standby in another zone; the connection uses TLS. See Managed Database: RDS and Aurora.

  6. The response goes back, and the CDN stores it if the cache rules allow. Pages for logged-in users, previews, the admin and the login page are not stored.

The pod learns its configuration from environment variables that the cluster fills from the secret store when it starts. There are no files to edit on a pod; the filesystem is read-only except for a few scratch directories.

Where each piece of WordPress state goes

On one server, WordPress keeps state in many places and nothing says so. Moving to a cluster is mostly the work of finding every one and deciding where it goes. This table is the checklist for the whole section.

State On one server In the tiered design Page

Code: core, plugins, themes

/var/www/html, changed by the dashboard and by updates

Inside the container image, built in the pipeline. DISALLOW_FILE_MODS makes the dashboard read-only; updates become a new image

Deploying on EKS with Helm, Releases and Upgrades

Configuration: wp-config.php, constants, salts

A file on the server

The same file in the image, reading everything variable from environment variables; the eight keys and salts come from a secret and are identical on every pod

Deploying on EKS with Helm

Database content

MariaDB or MySQL on the same host

A managed database with a standby: RDS or Aurora, reached over TLS by a dedicated user

Managed Database

Media uploads

wp-content/uploads on the local disk

Object storage with an offload plugin and a CDN, or a shared file system mounted on every pod

Media

Sessions and logins

Signed cookies and the session_tokens user meta; no PHP sessions

Unchanged: any pod can check a cookie when the keys and salts are identical. A plugin that uses $_SESSION needs a shared handler, or replacing

Object Cache, Sessions and Cron

Object cache and transients

A per-process cache, or a local Redis

A shared Valkey or Redis through the object-cache drop-in, with a key prefix per site

Object Cache, Sessions and Cron

Page cache

Plugin cache files on disk, or a reverse proxy

At the edge: the CDN, honoring cookies that mean a logged-in user. A per-pod file cache is not shared and is not used

Performance and Caching

Search

MySQL LIKE queries through WP_Query

The same, from the managed database, until it is too slow; then a search service (OpenSearch through a plugin) as a separate tier

Search with OpenSearch

Scheduled events (cron)

WP-Cron on page views, or a system cron entry

DISABLE_WP_CRON on the web pods, and one Kubernetes CronJob that runs wp cron event run --due-now with concurrencyPolicy: Forbid

Object Cache, Sessions and Cron

Schema upgrades

wp core update-db by hand, or on the first dashboard visit after an update

A Job that runs before the rollout, so that the new pods never meet an old schema

Releases and Upgrades

Outgoing email

The server’s mail transport

An email service (SES or another) through SMTP or its API; pods do not run a mail server

Amazon SES

Logs

/var/log/nginx, php-fpm.log and debug.log

Standard output and error of the containers, collected by the cluster to the log service

Monitoring and Observability

Backups

Scripts on the server

Database snapshots and point-in-time recovery, bucket versioning and replication, the image registry and the infrastructure code

Backups and Disaster Recovery

TLS certificates

Let’s Encrypt or a purchased certificate on the server

Certificates managed by the cloud (ACM) on the load balancer and the CDN

Route 53 and Certificates

Two rows deserve a sentence more. Sessions is the one that surprises people from other stacks: WordPress core does not use PHP sessions, so a WordPress cluster needs no sticky routing and no session store, which removes a tier that PHP shops usually have. The exception is a plugin that opens $_SESSION, which also trips the Site Health "No PHP sessions" test; treat it as a defect to replace. Code is the row that changes how the team works: in the tiered design nobody installs a plugin from the dashboard. The plugin is added to the repository, built into an image, tested on staging and rolled out. That is a loss of convenience for editors and a gain in repeatability, rollback and security.

The tiers on three clouds

The architecture does not depend on one provider. Each tier has a managed equivalent on the three large clouds. The walkthroughs in the next pages use AWS; Google Cloud, Azure and Alternatives gives the differing commands for the others.

Tier AWS Google Cloud Azure

Managed Kubernetes

Amazon EKS (Auto Mode, or managed node groups with Karpenter)

GKE (Autopilot or Standard)

AKS

Container registry

Amazon ECR

Artifact Registry

Azure Container Registry

Load balancer and edge routing

Application Load Balancer through the AWS Load Balancer Controller

Cloud Load Balancing

Application Gateway or Front Door

CDN

CloudFront

Cloud CDN

Azure Front Door

Web application firewall

AWS WAF

Cloud Armor

Web Application Firewall on Front Door or Application Gateway

DNS and certificates

Route 53 and ACM

Cloud DNS and Certificate Manager

Azure DNS and managed certificates

Managed database (MySQL or MariaDB)

RDS for MariaDB or MySQL, Aurora MySQL

Cloud SQL for MySQL

Azure Database for MySQL Flexible Server. Azure Database for MariaDB was retired on 2025-09-19

Object cache

ElastiCache for Valkey

Memorystore

Azure Managed Redis

Object storage for media

Amazon S3

Cloud Storage

Blob Storage

Shared file system (if used)

Amazon EFS

Filestore

Azure Files, see Storage Accounts

Secrets

Secrets Manager

Secret Manager

Key Vault

Identity for pods

EKS Pod Identity or IAM roles for service accounts

Workload Identity Federation for GKE

Microsoft Entra Workload ID

Logs and metrics

CloudWatch

Cloud Logging and Monitoring

Azure Monitor

Infrastructure as code

OpenTofu or CloudFormation

OpenTofu or Terraform

OpenTofu or Bicep

The Kubernetes layer has its own section, Kubernetes; the cloud pages above cover the managed services. The WordPress-specific parts that no cloud page covers are the other eight pages here.

Sizing tiers for Lumen Press

Three sizes cover the life of a site. The numbers are starting points. A PHP worker for a typical plugin set uses roughly 40 to 80 MiB; take the real figure from ps under load, then size the pod from pm.max_children, and the pod count from the database’s connection budget (see Managed Database: RDS and Aurora).

Staging and launch Production Prize night (a traffic spike)

Web pods

2, spread over two zones; 0.5 vCPU and 1 GiB each; pm.max_children of 8

3 to 10 with an autoscaler; 1 vCPU and 2 GiB each; pm.max_children of 16

Up to 30 in a pre-scaled window; the same pod size

Database

RDS for MariaDB 11.8, a small burstable class, Single-AZ for staging, Multi-AZ for production

Multi-AZ, a general-purpose class such as db.m7g.large, storage autoscaling

The same primary, or Aurora with readers, plus RDS Proxy if the connection budget is tight

Object cache

One small Valkey node

A replication group (primary and one replica) with TLS and auth

A larger node type; keys expire normally

Media

S3 bucket and CloudFront

The same

The same; check CloudFront cache hit ratio first

Nodes

Two small nodes, or Auto Mode

Auto Mode, or managed nodes with Karpenter over three zones

Nodes follow the pods; set limits on the node pool

Cron

One CronJob

One CronJob, one replica of the scheduler

The same; long jobs move to their own CronJob

When to move up

The site goes live, or downtime costs money

The primary’s CPU or connections near limits, or p95 TTFB degrades

Before the event: rehearse with a load test (see the releases page)

The first lever for a spike is not more pods. A CDN that serves anonymous pages and media absorbs most of the traffic, and a persistent object cache removes most database reads; add pods only for what is left: logged-in editors, search, the REST API, admin-ajax.php and, on a shop, the cart and checkout. Performance and Caching covers measuring it, and Releases, Upgrades and Autoscaling on Kubernetes the load test and the autoscaler.

Measure the single server before choosing a tier. The script below, run on the existing server, collects the figures that matter: versions, database size, media size and file count, the code that has to go into the image, the weight of autoloaded options and the busiest minutes of the access log.

#!/usr/bin/env bash
# Collects the numbers that decide a sizing tier. Run on the single server, as the site's user, from the WordPress root.
set -euo pipefail

echo "== Versions"
wp core version
wp eval 'echo PHP_VERSION, PHP_EOL;'
wp db query 'SELECT VERSION();' --skip-column-names

echo "== Database size (largest tables first)"
wp db size --tables --size_format=mb --format=table | head -n 15

echo "== Media"
du -sh wp-content/uploads
find wp-content/uploads -type f | wc -l

echo "== Code that must go into the image"
wp plugin list --fields=name,status,version --format=table
wp theme list --fields=name,status,version --format=table

echo "== Autoloaded options (large values slow every request)"
wp db query "SELECT ROUND(SUM(LENGTH(option_value)) / 1024) AS autoload_kb FROM $(wp db prefix)options WHERE autoload IN ('yes', 'on', 'auto-on', 'auto');" --skip-column-names

echo "== Scheduled events"
wp cron event list --fields=hook,next_run_relative,recurrence --format=table | head -n 20

echo "== Peak load (requests in the busiest minutes of the web server log)"
awk '{print substr($4, 2, 17)}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -n 3

Read the output before the meeting about cost. A 40 GiB media library points at object storage first; a 2 GiB database with a 10 MiB autoload total points at a cache and a small instance; a long list of cron hooks points at the scheduler. Migrating from a Single Server uses the same inventory.

The names used across these pages

The nine pages use one set of names, so a snippet in one page connects to another.

Item Name

Region and account

eu-west-1, account 123456789012

Site and media hosts

www.lumenpress.example, media.lumenpress.example

EKS clusters

lumenpress-prod, lumenpress-staging; namespace lumenpress

Helm release and chart

release lumenpress, chart directory lumenpress-wordpress

Image

ECR repository lumenpress/wordpress, tag <git-sha>, built from the official wordpress:7.1.4-php8.4-fpm image

Workloads

Deployment lumenpress-web (nginx and php-fpm containers), CronJob lumenpress-cron, Jobs lumenpress-migrate and lumenpress-install

Database

RDS instance lumenpress-prod, database lumenpress, users wp_app and wp_migrate

Secrets

lumenpress/prod/wordpress, lumenpress/prod/valkey

Cache

ElastiCache for Valkey replication group lumenpress-prod

Media

S3 bucket lumenpress-prod-media, or an EFS file system when a directory is needed

The tag wordpress:7.1.4-php8.4-fpm existed on Docker Hub on the check date. Confirm it with docker pull when you build.

The end of the Bitnami images

Many older tutorials start a WordPress cluster from the Bitnami images or the Bitnami Helm chart, or a Lightsail blueprint, because they came with sensible defaults. That path changed:

  • In August 2025 Broadcom moved the public Bitnami container images to a "Bitnami Legacy" repository that receives no updates, and moved the maintained images to a paid catalogue; new OCI Helm charts stopped being published on Docker Hub.

  • Broadcom’s announcement of 2026-05-20 says that on 2026-06-10 the Bitnami containers on Amazon ECR, the Helm charts, the AMIs on AWS Marketplace and the Lightsail blueprints are removed from AWS. Existing EC2 and Lightsail instances keep running but get no updates after 2026-05-20.

  • The source repositories (bitnami/charts, bitnami/containers) are still public, but published images and charts are no longer a safe default for production: nothing guarantees updates.

The consequence for this section: build your own image from the official wordpress image (the Dockerfile is in Deploying on EKS with Helm), use managed services for the database and cache instead of in-cluster charts, and treat a Bitnami or Lightsail WordPress instance as a system to migrate off, not one to patch. The migration is in Migrating from a Single Server.

When one server, or a managed WordPress host, is still right

The tiered architecture is the answer to specific requirements, not a goal. Stay with a single server, or buy a managed WordPress service, when:

  • Traffic is modest and mostly anonymous. A CDN or page cache in front of one well-tuned server serves very large audiences. The Advanced Administration Handbook orders the steps: caching and tuning first, a database on its own server next, offloaded media after that, and several web servers last.

  • A few minutes of downtime is acceptable and the backup and restore are tested. A one-hour recovery objective can be met with snapshots and a rebuild script, with none of the cluster.

  • No team operates Kubernetes. A cluster that nobody understands is less available than a virtual machine that somebody does. Operating it (upgrades every year, node and add-on patching, cost control, on-call) is the real price.

  • The plugins need a writable code directory. A site that depends on installing plugins from the dashboard, or on plugins that write PHP files, fights the immutable-image model.

  • A managed WordPress host fits. A host that runs WordPress, its updates, caching, backups and a CDN removes most of the operations while keeping the site portable. Evaluate it as in Requirements and Hosting; check how it handles staging, WP-CLI access, cron, custom plugins and the exit (an export you can restore elsewhere).

Between the extremes there are middle paths that do not need Kubernetes: a single virtual machine with a managed database and object storage (the first two steps of the tiered design), an EC2 Auto Scaling group behind a load balancer, or containers on ECS with Fargate. Google Cloud, Azure and Alternatives compares them. A managed database is often the best single step: it removes the failure that is hardest to recover from and changes only one constant, DB_HOST.

Reading path

Pitfalls

  • Moving the database and nothing else. A managed database fixes the data tier but not the web tier. Two web servers still need shared media, identical keys and salts, one cron runner and a shared cache.

  • Treating the cluster as a bigger server. Dashboard installs, a writable code directory, local uploads and file-based page caches all break when the pod is replaced.

  • A database that cannot keep up with the pod count. More pods mean more connections; the budget is on Managed Database: RDS and Aurora.

  • Forgetting the egress. Pods in private subnets reach the WordPress.org API, plugin license servers and email services through a NAT gateway or endpoints; a blocked call is a hung request, not an error. WP_HTTP_BLOCK_EXTERNAL and its allow-list are in wp-config.php Reference.

  • Two sources of truth for one setting. A value in a Helm chart, a secret and the options table at once will disagree. Constants in wp-config.php win over the options table; choose one place.

  • Copying a diagram without the cost model. A tier that is not needed is still operated, patched and paid for.