Setup Web Application Stack using AWS Services for Starter (+ PHP/Laravel mini-setup)

Heang Yuthakarn
11 min readMay 2, 2020

--

This article shows you how a good stack looks like. And demonstrates the basic setup of various AWS services such as EC2, RDS, S3, ELB, CloudFront, and ACM to create infrastructure for Web Application.

Let’s imagine that you’re a small software company (or freelance) who got the job from a customer to create their e-catalog website. You wrote the code (maybe using PHP with Laravel framework) that having frontend, to show catalog to visitors, and backoffice, for staff to maintain products list.

Now the coding part is finished. You’re ready to put your code to a server(s) to publish the website publicly. You may think about putting everything into 1 EC2 instance like the below picture on the left.

This may easy setup at the beginning but you need to manage things (such as HTTPS request and redirect, database maintenance) by yourself. Furthermore, this setup won’t allow you to scale-out as we still need to keep all images and database in only 1 machine.

Diagram created from https://cloudcraft.co/

AWS provides us various services to set up infrastructure properly. Help offload a lot of maintenance tasks and make scale-out to serve more load easily.

This article will walk through each step to set up various AWS Services to archive good practice.

This article assumes that you have some knowledge about AWS Services. We will not go through every screenshots but only necessary ones.

Components and Steps to go through

Below are the main steps that we will utilize AWS Services to decouple components to be able to scale separately and make them more secure. Detail of each step is also provided in later sections.

  1. Register Domain name in Route 53. You can skip this step if you already have a domain name to work with and familiar with DNS.
  2. Setup EC2 instance to be webserver (including setup Nginx to support Laravel) in a private network, secure it from accessible directly from the internet.
  3. Setup RDS to be the database for your Web Application. The decoupling of the database from webserver will allow these 2 components scale separately. In addition, as RDS is a managed database service, it offloads database maintenance tasks from us.
  4. Create an SSL Certificate in ACM to use with ELB that we will create in step 5 to make ELB able to handle HTTPS requests.
  5. Setup ELB to handle requests from the internet. ELB can distribute traffic to multiple webservers, make our website easy to scale out. It can also redirect unsecured HTTP traffic to HTTPS and terminate HTTPS requests to send HTTP to our webservers.
  6. Create root domain (e.g. example.com) and “www” subdomain (e.g. www.example.com) DNS records to point to ELB in Route 53. This will make our visitors can access our website by the (easy-to-remember) domain name we registered.
  7. Setup S3 bucket to store static content such as images, CSS, and JS. Moving static content to S3 offloads burden of webservers to serve those, make them have more resource for our website.
  8. Setup IAM user as a “service account” for the webservers to uploading images to the S3 bucket.
  9. Now we stored all static content in the S3 bucket. In order to serve them efficiently, we set up CloudFront as CDN for it. CloudFront will cache those content to the location near our visitors (called edge servers or point of presence) make them load a lot faster.
  10. Create “images” subdomain DNS record (e.g. images.example.com) to point to CloudFront in Route 53.

In the end, we will get an architecture similar to the diagram below.

Diagram created from https://cloudcraft.co/

There are ways to improve this architecture further. For example, putting a bastion host or separate webserver to frontend and backend and allow only backend to connect RDS.

But I don’t put them in as I want this article to be starter kit.

1. Register Domain name

Register a domain name in Route 53 is quite simple. You just go to Route 53 service → Check for the domain name you want → Fill contact detail → Confirm Order.

If you already have your domain name, you can transfer to Route 53 or stay with the one you are using.

Route 53 — Dashboard menu

When the process is completed, you will see your domain name in the “Hosted zones” menu.

Route 53 — Hosted Zones menu

2. Setup EC2 instance

Create an EC2 instance with Ubuntu Server 18.04 LTS (HVM) AMI in default VPC. For Security Group. Select the default VPC security group for now. We will create another security group to attach to this instance later.

This default VPC security group will allow all components those attached to it can communicate with each other.

EC2 — “Launch Instance, Step 6 — Configure Security Group” screen

While waiting for the instance begin created, we’re going to create a new security group to allow SSH (port 22) to the instance. Then attach it to the instance we created.

The reason we create this security group separately is we can detach it later when no need to SSH to the EC2 instance.

EC2 — “Create Security Group” screen. Don’t forget to attach it to the EC2 instance.

Now SSH to the instance by using Public IP that AWS provided, and install necessary packages for Nginx and PHP 7.2 using below commands.

sudo su
apt-get update && apt-get upgrade
apt-get install -y nginx
add-apt-repository -y ppa:ondrej/php
apt-get update
apt-get install -y php7.2-fpm php7.2-cli php7.2-curl php7.2-mysql \
php7.2-gd php7.2-xml php7.2-mbstring php7.2-common php7.2-intl \
php7.2-soap php7.2-xmlrpc php7.2-zip
apt-get -y install gcc make autoconf libc-dev pkg-config \
libmcrypt-dev php7.2-dev php-pear
pecl install mcrypt-1.0.1ln -s /etc/php/7.2/mods-available/mcrypt.ini 20-mcrypt.inisudo systemctl enable nginx.service

Then edit the file /etc/nginx/site-available/default by adding the following lines

server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html/frontend/public;index index.php index.html index.htm index.nginx-debian.html;server_name _;location / {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
try_files $uri $uri/ /index.php?$query_string;
}
location /backend {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
alias /var/www/html/backend/public;
client_max_body_size 100M;
try_files $uri $uri/ @backend;
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_param SCRIPT_FILENAME $request_filename;
fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
}
}location @backend {
rewrite /backend/(.*)$ /backend/index.php?/$1 last;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
}
location ~ /\.ht {
deny all;
}
}

Then reboot an instance.

3. Setup RDS

Create a database of your choice (whether MySQL or MariaDB). Fill in master username and password. Make sure that you select NO publicly accessible and choose the existing default VPC security group.

The RDS will be created in the private network that the only instance in the same default VPC security group can access (in our case, it is the EC2 instance we created in step 2).

RDS — “Create Database” screen

4. Create an SSL Certificate in ACM

Because we would like to use ELB to serve both HTTP and HTTPS requests (we will create it in step 5). So we need to provision an SSL certificate for that. You may not familiar with ACM but it stands for AWS Certificate Manager. The main benefit of using SSL certificates that provision from ACM with other AWS services is it’s FREE (the cheapest in namecheap.com cost $5.88/year).

Before provision, make sure that you are in the same region as EC2 and ELB.

To provisioning an SSL certificate, go to ACM Service → Click Request a certificate → Select a public certificate → Add domain names (in our case root domain and “www” subdomain) → Validate by using DNS validation method

ACM — “Request a Certificate” screen

If you use Route 53 for that domain name, AWS console will provide you a button to automatic add DNS record to Route 53. Otherwise, you need to do it manually with your DNS provider)

Make sure that the status is changed from “Pending validation” to “Issued” before going to the next step.

5. Setup ELB

To create ELB, go to EC2 Service → Choose Load Balancers menu on the left → Click Create Load Balancer → Select Application Load Balancer → Select internet-facing Scheme → Click Add listener to add HTTPS Protocol → select All Availability Zone

ELB — “Create Load Balancer, Step 1” screen

In Certificate name, choose the SSL certificate that we just created from the dropdown list.

ELB — “Create Load Balancer, Step 2” screen

Create a new security group that allows the web (both HTTP and HTTPS) traffic from the internet. You can see the image below for the needed rules.

ELB — “Create Load Balancer, Step 3” screen

Create a new target group. The target group is the group of EC2 instances that we want ELB to distribute requests to.

Don’t worry, just enter the name and leave the rest. We will add the instance we created on the next screen.

As SSL will be terminated at ELB level, the requests to the webservers will be HTTP requests. This’s not only offloads burden of webserver to decrypt HTTPS. It also help coding easier.

ELB — “Create Load Balancer, Step 4” screen

Add the instance we created to Target Group.

ELB — “Create Load Balancer, Step 5” screen

After ELB is created, attach the default VPC security group to it.

Doing this will make ELB can communicate with the EC2 instance that we created.

ELB — “Edit Security Groups” screen

Now the ELB is ready to use. But before that, we want to add some rules to ELB to make sure that we serve our visitors securely via HTTPS no matter how visitors request.

For HTTP requests, we will redirect those traffic to HTTPS on the “www” subdomain. To do this, we click View/Edit rules in Listeners.

ELB — “Listeners” screen

Add below rule to HTTP Listener.

ELB — “HTTP:80 View/Edit rules” screen

Do the same things to HTTPS Listener. The difference is we want to redirect only the root domain, not both.

ELB — “HTTPS:443 View/Edit rules” screen

6. Point DNS to ELB

Now our webservers and ELB are ready. To make internet users can access our website, we need to add DNS records to point to ELB.

To do this, go to Route 53 Service → Select Hosted Zones → Select the domain name → Click Create Record Set, then add 2 records as shown below. The left is for “www” subdomain and the right is for root domain.

Route 53 — “Create Record Set” screen

We are half-way

So now, we secure our web server and database in the private network and allow traffic to go ELB. We, if need, can scale out the number of the webservers to multiple and add to the ELB target group.

In the following steps, we will separate static content such as images, CSS, and JS to S3 and cache them at CloudFront. All static content will be served through the “images” subdomain, separately from the “www” subdomain.

7. Setup S3 bucket

Firstly, create an S3 bucket for storing static content. After created, go to the Permissions tab to turn off Block Public Access.

S3 — “Bucket Permissions — Block Public Access” screen

Next, go to the Bucket Policy and add the below policy to allow public read-only to the bucket.

Don’t forget to change the name of the bucket to the one you created.

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::images/*"
}
]
}

8. Setup IAM user as “service account” for S3

For security reasons, we will not allow users to upload images directly to S3. Instead, users will upload images to a webserver, then the webserver does that job. So we will create a service account for the webservers to use to upload images to the S3 bucket.

To create user, go to IAM Service → Choose Users menu on the left → Click Add user → Enter User name → Select “Programmatic Access” access type. When the user is created, note down Access key ID and Secret access key (or download CSV file), we need to use these as the credential in our code.

IAM — “Add User, Step 1” screen

Next, we will create a policy to allow that user to upload images to the S3 bucket. The policy is shown below.

Don’t forget to change the name of the bucket to the one you created.

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListObjectsInBucket",
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::images"
]
},
{
"Sid": "AllObjectActions",
"Effect": "Allow",
"Action": "s3:*Object",
"Resource": [
"arn:aws:s3:::images/*"
]
}
]
}

Then attach the policy with the user we just created.

IAM — “Users — Permissions” screen

9. Setup CloudFront

Images stored in S3 bucket are accessible via the URL that S3 automatically generates like “https://images.s3-ap-southeast-1.amazonaws.com/test.jpeg”. This URL is not pretty and we want images to serve through our URL like “https://images.example.com/test.jpeg”.

We will use CloudFront to serve as CDN. It will cache our images on edge servers make them load faster. We will also use CloudFront to change the URL to “images” subdomain.

To using CloudFront as CDN for S3 bucket, go to CloudFront Service → Click Create Distribution → Select Web Delivery Method → Change the setup as shown in red boxes in below pictures (you can leave the rest).

CloudFront — “Create Distribution” screen

Now here is the tricky part. You want to add the Custom SSL Certificate that you provisioned in Step 4, but you cannot find it.

The reason is CloudFront looks for SSL Certificate in US East (N. Virginia) only.

So we just need to repeat Step 4 to go to provision another SSL Certificate in US East (N. Virginia) region. But, this time, putting the “images” subdomain. Then go back to CloudFront Service to finish create the distribution.

ACM — “Request a Certificate” screen

10. Point DNS to CloudFront

This final step is similar to Step 6. We want to add DNS record for “images” subdomain to point to CloudFront Distribution that we just created.

To do this, go to Route 53 Service → Select Hosted zones → Select the domain name → Click Create Record Set, then add a record as shown below.

Route 53 — “Create Record Set” screen

Last but not least, don’t forget to change your code to point to RDS endpoint with correct credential. And use Access key ID and Secret access key of IAM “service account” user to upload images to S3.

Thank you to read up to the end. Hope this article helps you more understand the good practice of Web Application Architecture.

Happy Infrastructure.

--

--

Heang Yuthakarn
Heang Yuthakarn

Written by Heang Yuthakarn

Data Engineer | Infrastructure | Gadget Crazier | Drama King

No responses yet