Deploying WordPress on AWS Using a Three-Tier Architecture

OLUWASEUNOLUWASEUN
8 min read

Imagine you’re tasked with building a WordPress site that won’t just go live but will scale with traffic, maintain high availability, and stay secure—no matter what. Sounds like a dream setup? Well, that’s precisely what we will achieve today by deploying WordPress on AWS using a Three-Tier Architecture.

This architecture splits our infrastructure into three logical layers—Web, Application, and Database—giving us clear separation, better security, and robust scalability. Each part serves a distinct role, and AWS gives us the tools to build this easily. I’ll walk you through each stage, from setting up the VPC to launching WordPress with Auto Scaling and an SSL-enforced connection.

Building the Foundation: VPC and Subnet Creation

First, let’s lay the groundwork for our architecture by creating a Virtual Private Cloud (VPC). I chose the EU-West-2 (London) region for this deployment, but feel free to pick the most appropriate region for your needs. Our VPC is going to have a CIDR block of 172.31.0.0/16, which provides plenty of IP space to work with.

Next, I carved out six public and private subnets spread across multiple availability zones (AZs). This ensures we’re architecting for resilience from the get-go, with each tier of our application being deployed across different AZs for high availability.

  • The public subnets are for resources that need direct access to the internet, like our Application Load Balancer (ALB).

  • The magic happens in the private subnets—our EC2 instances and RDS database will live here, shielded from public access.

Now, to route traffic between these, I set up two route tables:

  1. A public route table linked to an Internet Gateway allows outbound traffic from our public subnets.

  2. A private route table that routes traffic through a NAT Gateway so our private resources can still access the internet (for updates and patches) without exposing themselves directly.


Securing the Infrastructure: Security Groups

No good architecture is complete without solid security boundaries. I created five security groups (SGs) to define the allowed traffic flow for this deployment. Let me break them down for you:

  1. EC2 Instance Connect Endpoint (EICE) Security Group: This SG only allows outbound SSH traffic on port 22 within the VPC. Why? Because we will access our EC2 instances via the EC2 Instance Connect Endpoint, avoiding the need for a bastion host or SSH keys.

  2. ALB Security Group: This group allows inbound HTTP (port 80) and HTTPS (port 443) traffic from anywhere. After all, we want our WordPress site to be accessible from anywhere.

  3. Application Server Security Group: We allow traffic only from the ALB on HTTP and HTTPS ports. We also permit SSH traffic, but only from our EC2 Instance Connect Endpoint SG—this means the EC2 instances are still safe from direct internet exposure.

  4. RDS Security Group: The database needs protection too. This SG only allows MySQL connections (port 3306) from the Application Server SG.

  5. EFS Security Group: Our Elastic File System (EFS) will use this SG to accept NFS traffic from our Application Servers.

With these SGs, we’ve set up a network where each tier can communicate with the one it needs to without opening unnecessary attack vectors.


Setting Up EC2 Instance Connect for Seamless Access

Now comes the fun part—setting up the EC2 Instance Connect Endpoint (EICE). This AWS service lets us SSH into our instances without managing SSH keys or a bastion host. I love using this because it’s secure, easy, and integrates directly with the AWS console.

Once the endpoint was ready, I spun up an EC2 instance with Amazon Linux 2023 in one of our private subnets. I didn’t bother with an SSH key since I’ll access it through EC2 Instance Connect. We were good to go after testing the connection and confirming the instance had outbound internet access!


The Data Tier: RDS Setup for the Database Layer

The next layer in our three-tier architecture is the database. For this, I chose Amazon RDS with MySQL as the engine. The instance type was a humble db.t3.micro—more than enough to start with, but easily scalable if needed.

I set up the database with password authentication and applied the RDS SG to lock down access. Only our Application Server EC2 instances, which we’ll set up shortly, can communicate with the database.


File Storage: EFS Setup

I created an Elastic File System (EFS) to store WordPress files. Why EFS? It’s fully managed and scalable, allowing us to share file storage across multiple instances. This becomes crucial as we scale out our application with Auto Scaling.

To mount the EFS on the application servers, I used the following script:

#!/bin/bash

# Update the package repository
sudo yum update -y

# Create /var/www/html directory
sudo mkdir -p /var/www/html

# Variable for EFS
EFS_DNS_NAME=fs-0010eb9b6121b2db3.efs.eu-west-2.amazonaws.com

# EFS Mount to the /var/www/html
sudo mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport "$EFS_DNS_NAME":/ /var/www/html

This ensures our /var/www/html directory (where WordPress lives) is backed by EFS, providing persistent storage that scales independently of the instances.


Distributing Traffic: Application Load Balancer Setup

At the heart of our web tier sits the Application Load Balancer (ALB). This ALB is the face of our WordPress site, routing traffic from the internet to the EC2 instances running WordPress.

I configured the ALB to listen on both HTTP and HTTPS (because, of course, we want secure traffic). The ALB SG controls what traffic is allowed in, while I created a target group that will later receive the EC2 instances running WordPress.


Deploying WordPress on EC2

Now it’s time to get WordPress up and running! I launched an EC2 instance, assigned it to the private subnet, and used a basic setup script to install Apache, PHP, and MySQL. Here’s the script I used:

#!/bin/bash

# Update the package repository
sudo yum update -y

# Create /var/www/html directory
sudo mkdir -p /var/www/html

# Variable for EFS
EFS_DNS_NAME=fs-0010eb9b6121b2db3.efs.eu-west-2.amazonaws.com

# EFS Mount to the /var/www/html
sudo mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport "$EFS_DNS_NAME":/ /var/www/html

# Install apache2
sudo yum install git httpd -y

# Install apache and php dependencies for the php web app
sudo yum install -y \
php \
php-cli \
php-cgi \
php-curl \
php-mbstring \
php-gd \
php-mysqlnd \
php-gettext \
php-json \
php-xml \
php-fpm \
php-intl \
php-zip \
php-bcmath \
php-ctype \
php-fileinfo \
php-openssl \
php-pdo \
php-soap \
php-tokenizer

# Install Mysql-Client 
sudo wget https://dev.mysql.com/get/mysql80-community-release-el9-1.noarch.rpm 
sudo dnf install mysql80-community-release-el9-1.noarch.rpm -y
sudo rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2023
sudo dnf repolist enabled | grep "mysql.*-community.*"
sudo dnf install -y mysql-community-server 

# Start and enable Apache & Mysql server
sudo systemctl start httpd
sudo systemctl enable httpd
sudo systemctl start mysqld
sudo systemctl enable mysqld

# set /var/www/html directory permissions
sudo usermod -aG apache ec2-user
sudo chown -R ec2-user:apache /var/www
sudo chmod 2775 /var/www && find /var/www -type d -exec sudo chmod 2775 {} \;
sudo find /var/www -type f -exec sudo chmod 0664 {} \;
chown apache:apache -R /var/www/html 

# Download Wordpress files and copy to /var/www/html
wget https://wordpress.org/latest.tar.gz
tar -xzf latest.tar.gz
sudo cp -r wordpress/* /var/www/html/

# create the wp-config.php file
sudo cp /var/www/html/wp-config-sample.php /var/www/html/wp-config.php

With WordPress files copied to /var/www/html, I registered the EC2 instance with the target group, giving our ALB something to route traffic to. Next, I updated the wp-config.php file to connect WordPress to our RDS database.


Scaling Up: Auto Scaling Group

Here’s where the three-tier architecture truly shines. I created an Auto Scaling Group (ASG) to ensure our WordPress site can handle fluctuating traffic. This group automatically adds or removes EC2 instances based on traffic, ensuring we have enough capacity.

Using a Launch Template, I set up new EC2 instances to automatically mount EFS and run the necessary services using this script:

#!/bin/bash

# update the software packages on the ec2 instance 
sudo yum update -y

# install the apache web server, enable it to start on boot, and then start the server immediately
sudo yum install -y git httpd
sudo systemctl enable httpd 
sudo systemctl start httpd

# install php 8 along with several necessary extensions for wordpress to run
sudo yum install -y \
php \
php-cli \
php-cgi \
php-curl \
php-mbstring \
php-gd \
php-mysqlnd \
php-gettext \
php-json \
php-xml \
php-fpm \
php-intl \
php-zip \
php-bcmath \
php-ctype \
php-fileinfo \
php-openssl \
php-pdo \
php-tokenizer

# Install Mysql-Client 
sudo wget https://dev.mysql.com/get/mysql80-community-release-el9-1.noarch.rpm 
sudo dnf install mysql80-community-release-el9-1.noarch.rpm -y
sudo rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2023
sudo dnf repolist enabled | grep "mysql.*-community.*"
sudo dnf install -y mysql-community-server 

# start and enable the mysql server
sudo systemctl start mysqld
sudo systemctl enable mysqld

# environment variable
EFS_DNS_NAME=fs-0010eb9b6121b2db3.efs.eu-west-2.amazonaws.com

# mount the efs to the html directory 
echo "$EFS_DNS_NAME:/ /var/www/html nfs4 nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 0 0" | sudo tee -a /etc/fstab
sudo mount -a

# set permissions
sudo chown apache:apache -R /var/www/html
sudo chmod 755 -R /var/www/html

# restart the webserver
sudo systemctl restart httpd
sudo systemctl restart php-fpm

Wrapping Up: SSL Configuration

Last but not least, I secured the WordPress site by enforcing SSL. This was done by adding a listener for HTTPS on the ALB and modifying wp-config.php to force SSL connections:

/* SSL Settings */
define('FORCE_SSL_ADMIN', true);

// Get true SSL status from AWS load balancer
if(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
  $_SERVER['HTTPS'] = '1';
}

Conclusion

Deploying WordPress on AWS using a Three-Tier Architecture may seem complex, but it offers the scalability, security, and flexibility needed for modern web applications. With this setup, we now have a robust WordPress site that can handle high traffic, stay secure with SSL, and scale effortlessly, thanks to AWS’s Auto Scaling and EFS.

For a more visual walkthrough, check out my YouTube video, linked below, where I demonstrate this setup in action. Feel free to leave your thoughts or questions in the comments below!


0
Subscribe to my newsletter

Read articles from OLUWASEUN directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

OLUWASEUN
OLUWASEUN

Oluwaseun is a versatile Network, Cloud, and DevOps Engineer with over six years of experience. He also possesses DevOps and DevSecOps expertise, ensuring efficient and secure solutions in cloud environments. With over two years of experience as a trainer, he has trained over 200 participants in Cloud and DevOps and manages a YouTube channel dedicated to sharing his knowledge. Oluwaseun has a proven reputation for delivering flexible, scalable, and secure cloud solutions using the AWS Well-Architected Framework. He collaborates seamlessly with business stakeholders to achieve project objectives on time. A visionary professional, he excels in researching and adopting new technologies aligned with strategic business needs. He is meticulous, creative, and adaptable to diverse work cultures.