Running WordPress on AWS: a hands-on guide for engineers
Most teams hosting WordPress on-premise or with shared hosting hit a wall when traffic spikes or compliance demands stricter infrastructure controls. Running WordPress on AWS gives you fine-grained scaling, better isolation, and the flexibility to optimize costs—but it requires thoughtful architecture rather than lifting and shifting a monolithic server. This guide walks through the decisions and implementations we've used in production for e-commerce and SaaS workloads.
Why move WordPress to AWS?
Before diving into the architecture, the motivation matters. Traditional hosting feels simpler at first: you get a control panel, pre-configured PHP-FPM, and one bill. But as your site grows, you lose visibility into what's consuming resources, you can't scale the database separately from the web tier, and security updates often require coordinating with your host.
AWS lets you own that stack. You'll see what's actually happening, control autoscaling, and decouple components so bottlenecks don't cascade. The tradeoff is operational responsibility—you're no longer paying someone else to manage patches and failover. For teams with DevOps bandwidth or contractors, it's worth it.
Compute: where your WordPress code runs
EC2 for control and traditional setups
If your team is comfortable managing Linux instances and you need direct control over the environment, EC2 is the straightforward choice. Start with a single t3.medium or t3.large instance for most small-to-medium sites, then add an Application Load Balancer and a second instance behind it when you're ready to scale horizontally.
Use an auto-scaling group so new instances spin up under load and terminate when traffic drops. Set up a custom AMI (Amazon Machine Image) with PHP 8.2+, Nginx or Apache, WordPress, and any plugins pre-baked. That way, scaling from 1 to 10 instances takes seconds instead of minutes.
// Example: healthcheck endpoint for your load balancer
// Add to your theme's functions.php or a must-use plugin
add_action( 'init', function() {
if ( isset( $_GET['healthcheck'] ) && $_GET['healthcheck'] === 'true' ) {
http_response_code( 200 );
die( 'OK' );
}
} );
Configure the load balancer to hit /index.php?healthcheck=true every 10 seconds. It's a lightweight probe that confirms PHP is running and the database is reachable without hitting your full WordPress bootstrap.
Lightsail for simplicity
Lightsail is AWS's simplified compute offering—think of it as managed EC2 with a fixed monthly price. It works well for WordPress sites with predictable traffic under 50k monthly unique visitors. Lightsail handles some operational tasks (snapshots, firewall rules) via a web UI, removing some of the DevOps burden. The downside is less fine-grained scaling and fewer integration points with other AWS services.
ECS with containers for advanced deployments
If you're running multiple WordPress instances or need rapid blue-green deployments, containerizing WordPress on ECS (Elastic Container Service) gives you orchestration and ease of updates. You define a Docker image with your WordPress installation, push it to ECR (Elastic Container Registry), and ECS manages running and scaling containers across a cluster.
This approach shines when you have a CI/CD pipeline. A new plugin version or theme update triggers a build, pushes the image to ECR, and ECS gradually replaces old containers with new ones—zero downtime.
// Example: Docker Compose locally (docker-compose.yml snippet)
// This mirrors your production ECS setup
services:
wordpress:
image: wordpress:6.4-php8.2-fpm
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
WORDPRESS_DB_NAME: wordpress
volumes:
- ./wp-content:/var/www/html/wp-content
Even if you don't deploy to ECS, developing and testing locally with Docker keeps your laptop environment matching production, reducing "works on my machine" surprises.
Database: RDS and Aurora for reliability
RDS MySQL as the baseline
Self-managing a database on EC2 is possible but costly in operations. RDS MySQL handles backups, minor version patches, and failover automatically. Start with a db.t3.small or db.t3.medium for most WordPress sites; monitor slow query logs and scale up if queries consistently exceed 1 second.
Key RDS settings for WordPress:
- Parameter group: Set
max_connectionsto match your web tier's pool size (usually 20–50 per app server). Setslow_query_log = 1andlong_query_time = 1to log queries over 1 second. - Backup window: Choose an off-peak window (e.g., 3–4 AM UTC). RDS keeps backups for 7 days by default; increase to 30 if compliance requires it.
- Multi-AZ: For production, enable Multi-AZ so a standby instance in a different availability zone takes over if the primary fails. It doubles cost but removes single points of failure.
Configure your WordPress wp-config.php to use RDS:
define( 'DB_HOST', 'your-db.c9akciq32.us-east-1.rds.amazonaws.com:3306' );
define( 'DB_USER', 'wordpress' );
define( 'DB_PASSWORD', $_ENV['DB_PASSWORD'] ?? '' );
define( 'DB_NAME', 'wordpress' );
Aurora for horizontal scaling and serverless flexibility
Aurora is AWS's managed database engine that mimics MySQL and PostgreSQL but adds automatic scaling, faster failover, and read replicas. For WordPress sites with variable traffic (typical in e-commerce campaigns or travel), Aurora serverless can reduce costs by scaling database compute up and down automatically.
One caveat: Aurora can't warm-start as fast as RDS MySQL when jumping from idle to high load, so you may see brief connection-establishment delays during sudden traffic spikes. It's usually acceptable for WordPress because it has multiple layers of caching, but measure in your environment.
Media: S3 and CloudFront for uploads and CDN
Offloading uploads to S3
Storing media files (images, videos, PDFs) on the same EC2 instance creates two problems: disk fills up quickly, and serving files from a single region is slow for international users. S3 solves both. Use the WP Offload Media plugin (or the free open-source alternative, Humanmade's S3 Uploads) to automatically copy uploads to S3 and rewrite media URLs.
Configuration:
- Create an S3 bucket (e.g.,
my-site-uploads) in the same AWS region as your database and compute for latency. - Create an IAM user with S3
PutObjectandGetObjectpermissions scoped to that bucket only. Store the access key in an environment variable or AWS Secrets Manager, never in code. - Set S3 bucket policies to deny unencrypted uploads and require HTTPS for all reads.
// Example: WP Offload Media configuration
// Add to wp-config.php or a settings constant
define( 'AS3CF_SETTINGS', array(
'provider' => 'aws',
'bucket' => 'my-site-uploads',
'region' => 'us-east-1',
'copy-to-s3' => true,
'remove-local-file' => true, // Delete from EC2 after upload
) );
CloudFront as your content delivery network
S3 alone serves from a single region. CloudFront distributes your media to edge locations worldwide, slashing latency for international visitors. Set up CloudFront with S3 as the origin, enable gzip compression, and configure a cache policy that respects Cache-Control headers.
Update the S3 upload plugin to rewrite URLs to your CloudFront domain (e.g., https://d123abc.cloudfront.net/uploads/2024/01/image.jpg) instead of the S3 URL. This layer of indirection lets you swap or add origins later without breaking URLs across the web.
Set CloudFront's default TTL to 86400 seconds (1 day) for media files. Images and PDFs rarely change, so long cache windows reduce origin load and bandwidth costs.
Cache: ElastiCache for object caching
Why object caching matters for WordPress
WordPress queries the database for every post view, term lookup, and plugin option. Page caching (serving a cached HTML file) helps, but you need object caching to speed up the database itself. ElastiCache running Memcached or Redis sits between your app servers and RDS, storing frequently accessed database results in RAM.
With object caching, repeated queries for the same post metadata or taxonomy terms hit memory (sub-millisecond) instead of disk (10–100 millisecond). For high-traffic sites, this alone can reduce database CPU by 30–50%.
Setting up Redis with WordPress
Launch an ElastiCache Redis cluster (not Memcached; Redis supports more data types and persistence). For most WordPress sites, a single cache.t3.micro node is enough; use replication for production (primary + replica in different AZs).
Install the Redis Object Cache Pro plugin or the free WP Redis to connect WordPress to ElastiCache. Configure in wp-config.php:
define( 'WP_REDIS_HOST', 'your-redis.c9akciq32.ng.0001.use1.cache.amazonaws.com' );
define( 'WP_REDIS_PORT', 6379 );
define( 'WP_REDIS_PASSWORD', $_ENV['REDIS_PASSWORD'] ?? '' );
define( 'WP_REDIS_DATABASE', 0 );
// Use Redis for both object and transient caches
define( 'WP_CACHE', true );
Monitor ElastiCache's CacheHits and CacheMisses metrics. A healthy cache should see 80%+ hit rate. Below that, your cached data is expiring too quickly or your query patterns aren't stable enough to benefit from caching.
Putting it together: a reference architecture
A production WordPress setup on AWS typically looks like:
- Route 53 DNS points to an Application Load Balancer.
- ALB routes traffic to an Auto Scaling Group of EC2 instances (or ECS containers).
- EC2 instances run Nginx + PHP-FPM + WordPress, with WP Offload Media and WP Redis plugins installed.
- WordPress connects to RDS MySQL (Multi-AZ) for data.
- WordPress caches database queries in ElastiCache Redis.
- Media uploads go to S3, served globally via CloudFront.
- CloudWatch monitors CPU, memory, database connections, and Redis hit rates.
Estimated monthly cost for a mid-sized site (10k–50k monthly visitors) with this setup: $200–400 (ALB $15, two t3.medium instances $60, RDS db.t3.medium $100, ElastiCache t3.micro $20, S3 and CloudFront ~$50–100 depending on data volume).
Common mistakes
- Storing uploads on EC2 and expecting them to persist across instance replacements: Always offload to S3 and enable "remove local file" in the upload plugin. EC2 instances are ephemeral in autoscaling groups.
- Neglecting database performance: Slow queries sink your entire stack. Enable RDS slow query logging immediately and review logs weekly. A single unindexed query can reduce throughput by 50%.
- Forgetting to set environment-specific database credentials: Hardcoding prod credentials in code is a breach waiting to happen. Use AWS Secrets Manager or environment variables, always.
- Skipping cache invalidation logic: If you set cache TTLs too high (e.g., 24 hours), content updates won't appear immediately. Use WordPress cache-busting hooks (
clean_post_cache,clean_term_cache) to invalidate on save. - Using Lightsail for uneven traffic: If your site has 1k visitors at 2 AM and 50k at noon, Lightsail's fixed pricing means you're paying for capacity you're not using. Migrate to EC2 + Auto Scaling to right-size costs.
Key takeaways
- Start with EC2 and a load balancer for most WordPress sites; Lightsail if you want simplicity and have predictable traffic; ECS if you need rapid deployments and have CI/CD infrastructure.
- Use RDS MySQL with Multi-AZ for production databases, or Aurora Serverless if traffic is bursty and cost-consciousness is high.
- Always offload media to S3 and serve via CloudFront; this removes the biggest scaling bottleneck and reduces EC2 disk and bandwidth costs.
- Add ElastiCache Redis for object caching to cut database load by 30–50% on typical WordPress sites; monitor hit rates and adjust TTLs based on your content update patterns.
- Monitor RDS slow query logs, ElastiCache metrics, and ALB response times weekly; these three dashboards catch 90% of production issues before users see them.
- Use Infrastructure as Code (Terraform or CloudFormation) to define your stack so you can clone it for staging and repeat deployments predictably.






Comments
Loading comments…