If you are putting a WordPress site on AWS, this guide helps you choose the database behind it. You have three realistic options, and the choice is expensive to reverse later, because that database holds every post, order, comment, and user account the site has ever had. For most WordPress sites the answer is RDS MySQL (Relational Database Service) with Multi-AZ, which keeps a standby copy in a second data centre and switches over on its own. Aurora, Amazon’s MySQL-compatible database engine, earns the move once you need several read replicas (extra copies that answer read-only queries) or more write throughput than ordinary disk storage gives you. By the end you will know which one fits your traffic, and how each behaves when WP-Admin gets busy.
Stay with RDS MySQL and Multi-AZ when your writes are steady, an object cache absorbs most of your reads, and one read replica is enough. Move to Aurora MySQL when you need a third reader, readers in another region, or fast clones of the database for staging. Aurora also helps when you need more write IOPS (input/output operations per second, the rate at which storage does work) than gp3, the general-purpose SSD behind RDS, delivers at a sensible price. Choose Aurora Serverless v2 when traffic is genuinely bursty, with long quiet stretches broken by short, intense spikes. It sizes itself in ACUs (Aurora Capacity Units, half-gigabyte steps of memory with matching CPU), and it takes a moment to scale up on the first request after a quiet spell.
Three options at a glance
The decision rows at a glance:
| Dimension | RDS MySQL Multi-AZ | Aurora MySQL | Aurora Serverless v2 |
|---|---|---|---|
| Write workload pattern | steady, predictable | steady-to-bursty, high IOPS | bursty, idle periods OK |
| Failover behaviour | 30–90s, DNS endpoint swap | 30–120s, reader promotes to writer | 30–120s, same as Aurora |
| Read scale | 1 read replica, manual app split | up to 15 readers, reader endpoint round-robin | reader optional, ACU-scaled |
| Cost shape | instance + provisioned IOPS | instance + per-I/O + storage | per-ACU-second |
| Backup posture | automated snapshots + binlog PITR | continuous backup + 1s PITR | continuous backup + 1s PITR |
| Point-in-time recovery | yes, to 5-minute granularity | yes, to 1-second granularity | yes, to 1-second granularity |
| Plugin compatibility | full MySQL 8 surface | full MySQL 8 surface, no LOAD DATA INFILE | same as Aurora, plus ACU pause/resume edges |
| Stack Harbor lift advice | safe default — start here | move when read scale or clones drive it | for editor-driven sites with long quiet hours |
When RDS MySQL is right
RDS MySQL with Multi-AZ is the default choice for the WordPress data tier and stays the right choice longer than most teams realise. Three signals point at it:
Your read pressure is absorbed by Redis. When your object cache hit rate is above 95 % and the writer DB CPU never crosses 50 %, adding a reader is solving a problem you do not have. RDS handles this case at the lowest operational complexity — one writer endpoint, one standby that does no work until the writer fails.
Your write pattern is predictable. WordPress writes scale with editor activity (wp_posts inserts, wp_postmeta updates, wp_options autoload changes), with order or form submissions, and with the wp_users/wp_usermeta writes from login. Most production WordPress sites generate sustained writes well under what db.r6g.large handles. RDS gp3 provisioned at 3000 IOPS easily covers a six-figure-pageviews-per-day WordPress with active commerce.
You want IOPS cost predictable. RDS gp3 has a flat IOPS price tied to provisioned storage. Aurora charges per-I/O operation. For a WordPress where the bulk of traffic is reads served from cache and the database absorbs maybe 50–200 writes/sec on average, RDS is materially cheaper than Aurora at the same instance size because Aurora’s per-I/O charges accumulate even on cache misses.
The trap of staying with RDS too long is the third reader replica. RDS supports a single Multi-AZ standby and can have read replicas, but each one is an independent instance with its own storage and its own replication stream from the writer. Beyond two readers, the operational overhead — replication lag tuning, separate backup posture, separate parameter groups — exceeds what Aurora gives you for the same money.
When Aurora wins
The signal that you have outgrown RDS is the third reader. Aurora’s shared-storage model means readers do not each maintain a copy — they all read from the same underlying volume, replicated six ways across three AZs. Adding a reader is a 5-minute provisioning step, not a 30-minute snapshot-and-replicate dance. Reader endpoint round-robins requests across all readers, so you do not have to teach the application about reader N+1.
Beyond read scale, Aurora wins on three operational features WordPress teams actually use:
- Fast clones for staging. An Aurora clone is a copy-on-write fork of the cluster’s storage. It provisions in minutes regardless of database size — a 200 GB production DB clones to staging in roughly the same time as a 20 GB one. RDS snapshot-and-restore scales linearly with size.
- Backtrack (Aurora MySQL only). Rewind the cluster to any point in the last 72 hours in seconds. Useful for accidental admin operations — a mass post deletion, a bad SQL UPDATE — without restoring a snapshot or going through PITR.
- 1-second PITR granularity. Aurora’s continuous backup means point-in-time recovery is by-the-second, not by-the-5-minutes. For e-commerce WordPress sites where every order matters, this can be the difference between losing five orders and losing one.
The price you pay for these features is Aurora’s per-I/O charge. Every read that does not hit the buffer pool, every write, every replication operation incurs an I/O charge. For caches-hitting-95-% WordPress, this is fine — the database does relatively few I/Os and per-I/O Aurora costs are absorbed by the savings of consolidated readers. For a search-heavy or admin-heavy WordPress where the cache is constantly being bypassed, Aurora’s I/O charges can balloon. Aurora I/O-Optimized (the storage class that caps I/O charges in exchange for a higher base storage rate) inverts that math — choose it when your monthly I/O bill consistently exceeds about 25 % of the base instance cost.
Aurora carries a per-IOPS premium that becomes material above roughly 50 % cache-miss read traffic; below that, Aurora’s per-I/O model is almost always cheaper than RDS’s provisioned-IOPS model because you only pay for I/Os you actually do.
When Aurora Serverless v2 is right
Aurora Serverless v2 is Aurora with ACU-based scaling instead of instance classes. The cluster scales between a minimum and maximum ACU (Aurora Capacity Unit, roughly 2 GiB memory + proportional CPU) in seconds, based on load. It is not the same product as v1 — v2 keeps the cluster always running (no pause-resume), avoids the long cold start v1 was punished for, and supports replicas. The minimum ACU is 0.5 (i.e. you can never go to zero, you pay for 0.5 ACU even when idle).
Serverless v2 fits when:
- Traffic is genuinely bursty. A B2B WordPress that handles 20 RPS during business hours and 0.5 RPS at night, with one news event a quarter that spikes to 800 RPS. Provisioned Aurora at the steady-state size is wasted on nights and undersized on news days; Serverless v2 scales to match.
- You run many WordPress sites with different traffic shapes. A multi-tenant agency stack where ten sites share a cluster and any one can spike. Serverless v2 scales the whole cluster up when any tenant spikes, and back down when the spike passes — no over-provisioning.
- You want the operational model of Aurora without picking an instance class. Some teams prefer not to make the
db.r6g.largevsdb.r6g.xlargecall. Serverless v2 sidesteps it.
Where Serverless v2 burns you on WP-Admin spikes: the editor opens a Gutenberg edit screen, which fires a series of REST calls (/wp-json/wp/v2/posts/<id>, /wp-json/wp/v2/users/me, /wp-json/wp/v2/taxonomies, custom endpoints from ACF, custom endpoints from third-party plugins). On a cold cluster — one that has been at 0.5 ACU for an hour — the first edit screen request hits an under-resourced writer, queues up behind connection initialization, takes 4–8 seconds to complete, and the editor sees a spinner. The cluster scales up within seconds, but the first request is slow. For sites with frequent editor activity, set the minimum ACU high enough that the cluster is always warm — typically 2 to 4 ACU. That trades some of the cost savings for predictable latency on the first edit of a quiet period.
Serverless v2 is not the right choice for a WordPress with steady editor activity all day or an e-commerce site where checkout latency is the gating metric. For those workloads, provisioned Aurora at a fixed instance class gives more predictable latency.
Migration paths from existing managed/shared hosts
The path from cPanel-shared, Dreamhost, WP Engine, Kinsta, or a self-managed VPS to any of these three is the same shape: dump the existing database with wp db export, sanitize the dump (drop _session tables, drop autoloaded options that contain hostnames, drop transients), import into the new RDS/Aurora cluster, then update wp-config.php. The differences are at the edges:
Migrating to RDS MySQL. Use mysqldump --single-transaction --routines --triggers --hex-blob or AWS DMS for any database over about 5 GB. RDS supports the full MySQL 8 syntax — no compatibility surprises.
Migrating to Aurora MySQL. Same workflow as RDS, with one caveat: Aurora does not support LOAD DATA INFILE on local files. If your dump uses LOAD DATA LOCAL INFILE for fast import, switch to mysqldump --extended-insert output or use DMS, which handles the conversion internally. Aurora’s aurora_load_from_s3 extension is the fastest path for very large dumps.
Migrating to Aurora Serverless v2. Same as Aurora MySQL. Set the minimum ACU to 4 during the migration so the import is fast, then drop it to the target floor (often 1 or 2 ACU) after the cutover.
In all three cases, do not drop the source database for a week. Run a small read query from the new WordPress against the old database to confirm no missing data — wp db query "SELECT COUNT(*) FROM wp_posts" on both sides. Test the editor: open an existing post, save it without changes, confirm the autosave row appears. Test commerce: place a test order, refund it, confirm the order row and the refund row both write correctly.
Performance ceiling
Each option has a different scaling wall:
- RDS MySQL hits its ceiling at the writer’s CPU. There is no horizontal write scaling — you scale the writer up (
db.r6g.large→db.r6g.xlarge→db.r6g.2xlarge→ up todb.r6g.16xlarge). The single Multi-AZ standby does not absorb any read traffic. A read replica can take some read load but only via application-level routing. - Aurora MySQL has the same writer ceiling, but adds up to 15 readers that share a single endpoint. Read scale is effectively horizontal until something specific (heavy
SELECT FOR UPDATE, replication lag on hot rows) starts to bite. - Aurora Serverless v2 scales the writer up to 128 ACU, which is comparable to a
db.r6g.16xlarge. Above that, you need provisioned Aurora.
The connection-limit ceiling is shared. WordPress holds one DB connection per PHP-FPM worker per node. On a six-node ASG at 30 workers each, that is 180 connections. db.r6g.large defaults to 1000 connections, db.r6g.xlarge to 2000. The connection limit is rarely the binding constraint, but it is the one that triggers user-visible 500s when it is — there is no graceful degradation, just MySQL: Too many connections errors. RDS Proxy in front of the writer pools connections and absorbs PHP-FPM pool spikes; it adds 1-2ms of latency and is the right precaution above six application nodes.
Replication lag is the other ceiling worth naming. On RDS read replicas, replication is asynchronous and can lag from milliseconds to seconds under write pressure. Reading user data from a lagging replica means the user just saved their post and a refresh shows the old version. WordPress is not write-after-read-tolerant without code that handles this, so most production teams either keep all reads on the writer (and use cache to absorb load) or route reads to readers only for clearly read-only paths (the public site) and admin paths through the writer. Aurora’s storage replication is much faster (sub-100ms typical) but the same principle applies — if your code expects read-your-writes consistency, read from the writer or wait.
Cost example
Without publishing dollar amounts, the qualitative ranges look like this:
- For a WordPress with 100 RPS sustained, 95 % cache hit rate, two ASG nodes, and a modest media library, RDS MySQL Multi-AZ on
db.r6g.largeis roughly 40–60 % cheaper than the equivalent Aurora MySQL configuration over a year. The savings come from gp3’s flat IOPS cost vs Aurora’s per-I/O charge, plus you do not pay for a second writer-grade instance. - For the same WordPress with three or more read replicas, the equation inverts: Aurora is cheaper because adding a reader is just an instance, not a full storage replica with its own I/O bill.
- For a WordPress with bursty traffic — 5 RPS at night, 300 RPS at peak — Aurora Serverless v2 with min ACU 2 / max ACU 16 is typically 30–50 % cheaper than provisioned Aurora sized for peak, and roughly cost-neutral with RDS Multi-AZ sized for peak.
Stack Harbor scopes pricing to the actual drivers — request volume, write-to-read ratio, admin-vs-anonymous mix, media library size, target RPO/RTO — and quotes a fixed monthly figure after a discovery review. We deliberately do not publish per-instance prices because three of the cost drivers above are decided by your traffic mix, not by AWS list price, and a number on a marketing page would be misleading either upward or downward.
See also
- Running WordPress in production on AWS: from EC2+RDS to autoscaling cluster
- Redis session store on ElastiCache — the pattern that survives failover
- ALB sticky sessions: when you actually need them
- AWS Management solution
- managed enterprise WordPress hosting — managed platform when running the IaC stack is not the right trade-off
- Managed Operations — operate the data tier under a Stack Harbor SLA