chatgpt image jun 30, 2026, 04 08 55 pm
diagram

AWS Project | EchoForm: A Highly Available LAMP Web Application on AWS with Application Load Balancing (ALB), Deployed Across Dual EC2 Instances with a Private MySQL Backend, Routed via Amazon Route 53

This guide walks through deploying two separate, simple web applications on two Ubuntu EC2 app servers (ec2-1 and ec2-2), each using Apache2 + PHP to collect form data, and both writing to the same MySQL database running on a third EC2 instance in a private subnet.


chatgpt image jun 30, 2026, 04 08 55 pm
diagram

1. Architecture Overview

EchoForm — Step-by-Step Deployment Guide

Project: EchoForm — A Highly Available LAMP Web Application on AWS with Application Load Balancing (ALB), deployed across dual EC2 instances in different Availability Zones, with a private MySQL backend.

Follow these steps in order.


Phase 1 — Networking (VPC, Subnets, Gateways)

Step 1: Create the VPC

  1. AWS Console → VPC → Create VPC.
  2. CIDR block: 10.0.0.0/16.
  3. Name: echoform-vpc.

Step 2: Create the subnets

Create three subnets inside echoform-vpc:

Subnet NameCIDRAvailability ZoneType
echoform-public-az-a10.0.1.0/24AZ-a (e.g. us-east-1a)Public
echoform-public-az-b10.0.2.0/24AZ-b (e.g. us-east-1b)Public
echoform-private-db10.0.3.0/24Either AZPrivate

Step 3: Create and attach an Internet Gateway

  1. VPC → Internet Gateways → Create → attach to echoform-vpc.

Step 4: Create a NAT Gateway

  1. Allocate an Elastic IP.
  2. VPC → NAT Gateways → Create → place it in echoform-public-az-a.
  3. Assign the Elastic IP to it.

Step 5: Configure route tables

Public route table (rt-public) — associate with both public subnets:

DestinationTarget
10.0.0.0/16local
0.0.0.0/0Internet Gateway

Private route table (rt-private) — associate with the private subnet:

DestinationTarget
10.0.0.0/16local
0.0.0.0/0NAT Gateway

Phase 2 — Security Groups

Step 6: Create three security groups

sg-alb (attached to the ALB)

  • Inbound: HTTP 80 from 0.0.0.0/0 (add HTTPS 443 later)
  • Outbound: all traffic

sg-app-servers (attached to ec2-1 and ec2-2)

  • Inbound: HTTP 80 from sg-alb only
  • Inbound: SSH 22 from your admin IP or bastion only
  • Outbound: all traffic

sg-db-server (attached to ec2-db)

  • Inbound: MySQL 3306 from sg-app-servers only
  • Inbound: SSH 22 from your admin IP or bastion only
  • Outbound: all traffic

Phase 3 — Launch EC2 Instances

Step 7: Launch the database server

  1. EC2 → Launch Instance.
  2. Name: ec2-db.
  3. AMI: Ubuntu 24.04 LTS.
  4. Subnet: echoform-private-db.
  5. Auto-assign public IP: Disabled.
  6. Security group: sg-db-server.
  7. Launch and note its private IP (yours is 10.0.3.25).

Step 8: Launch app server 1

  1. Name: ec2-1.
  2. AMI: Ubuntu 24.04 LTS.
  3. Subnet: echoform-public-az-a.
  4. Auto-assign public IP: Enabled.
  5. Security group: sg-app-servers.
  6. Launch and note both IPs (yours: public 100.58.215.82, private 10.0.1.103).

Step 9: Launch app server 2

  1. Name: ec2-2.
  2. AMI: Ubuntu 24.04 LTS.
  3. Subnet: echoform-public-az-b.
  4. Auto-assign public IP: Enabled.
  5. Security group: sg-app-servers.
  6. Launch and note both IPs (yours: public 3.86.186.215, private 10.0.2.55).

Phase 4 — Set Up the Database Server (ec2-db)

SSH into ec2-db via bastion/SSM (it has no public IP, so you cannot SSH directly from your laptop unless you go through a bastion or another instance).

Step 10: Install MySQL

sudo apt update
sudo apt install -y mysql-server
sudo systemctl enable mysql
sudo systemctl start mysql

Step 11: Allow remote connections

sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

Change:

bind-address = 127.0.0.1

to:

bind-address = 0.0.0.0

Restart:

sudo systemctl restart mysql

Step 12: Create the database, table, and users

sudo mysql -u root
CREATE DATABASE appdb;
USE appdb;

CREATE TABLE contact_submissions (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(150) NOT NULL,
    message TEXT NOT NULL,
    source_instance VARCHAR(50) NOT NULL,
    created_at DATETIME NOT NULL
);

-- ec2-1 and ec2-2 are in DIFFERENT subnets, so grant both
CREATE USER 'app_user'@'10.0.1.%' IDENTIFIED BY 'YourStrongPasswordHere';
CREATE USER 'app_user'@'10.0.2.%' IDENTIFIED BY 'YourStrongPasswordHere';
GRANT SELECT, INSERT, UPDATE ON appdb.* TO 'app_user'@'10.0.1.%';
GRANT SELECT, INSERT, UPDATE ON appdb.* TO 'app_user'@'10.0.2.%';
FLUSH PRIVILEGES;
EXIT;

Use a real password, not the placeholder. Keep it noted somewhere safe — you’ll need the exact same value in Step 16.

Step 13: Confirm MySQL is listening

sudo apt install -y net-tools   # netstat isn't installed by default on 24.04
sudo netstat -plnt | grep 3306

You should see it bound to 0.0.0.0:3306.


Phase 5 — Set Up Each App Server (Repeat on Both ec2-1 and ec2-2)

Step 14: Install Apache2, PHP, and required extensions

sudo apt update
sudo apt install -y apache2 php php-mysqli php-curl libapache2-mod-php mysql-client-core-8.0
sudo systemctl enable apache2
sudo systemctl start apache2

php-curl is required — the app uses it to detect which EC2 instance is handling each request (source_instance). Skipping it causes an HTTP 500 error (Call to undefined function curl_init()).

Step 15: Test connectivity to the database server

mysql -h 10.0.3.25 -u app_user -p appdb

Enter the password from Step 12. If it connects, you’re good. If you get Host '...' is not allowed to connect, re-check the CREATE USER/GRANT for that server’s subnet in Step 12, and the sg-db-server inbound rule.

Step 16: Deploy the application files

On each server, copy the project files (from the app/ folder) to the web root:

sudo rm -rf /var/www/html/*
sudo cp index.html /var/www/html/
sudo cp submit.php /var/www/html/
sudo cp db_config.php /var/www/html/
sudo cp health.php /var/www/html/
sudo chown -R www-data:www-data /var/www/html

Edit db_config.php (identical content on both servers):

sudo nano /var/www/html/db_config.php
$DB_HOST = "10.0.3.25";
$DB_USER = "app_user";
$DB_PASS = "YourStrongPasswordHere";   // must match Step 12
$DB_NAME = "appdb";

Step 17: Restart Apache

sudo systemctl restart apache2

Step 18: Test each app server directly

Visit each public IP in a browser and submit the form:

  • http://100.58.215.82/
  • http://3.86.186.215/

If you get an HTTP 500, check the error log:

sudo tail -50 /var/log/apache2/error.log

Confirm rows landed in MySQL (run on ec2-db):

SELECT * FROM contact_submissions;

Phase 6 — Application Load Balancer

Step 19: Create a target group

  1. EC2 → Target Groups → Create target group.
  2. Type: Instances.
  3. Protocol: HTTP, Port 80.
  4. VPC: echoform-vpc.
  5. Health check path: /health.php.
  6. Register targets: select both ec2-1 and ec2-2.

Step 20: Create the Application Load Balancer

  1. EC2 → Load Balancers → Create → Application Load Balancer.
  2. Name: echoform-alb.
  3. Scheme: Internet-facing.
  4. Subnets: select both echoform-public-az-a and echoform-public-az-b.
  5. Security group: sg-alb.
  6. Listener: HTTP : 80 → forward to the target group from Step 19.
  7. Create the ALB.

Step 21: Confirm target health

EC2 → Target Groups → your target group → Targets tab. Both ec2-1 and ec2-2 should show healthy. If not, double-check /health.php exists on both servers and sg-app-servers allows port 80 from sg-alb.

Step 22: Lock down direct access to the app servers

Edit sg-app-servers and remove any rule allowing port 80 from 0.0.0.0/0. Only sg-alb should be allowed on port 80 going forward — users should only reach the app through the ALB (and later, Route 53), not the raw EC2 public IPs.


Phase 7 — Route 53 (Custom Domain)

Step 23: Create or use a hosted zone

If you own a domain (e.g. yourdomain.com) already registered, go to Route 53 → Hosted Zones → confirm it exists. If not, register or transfer your domain into Route 53 first.

Step 24: Create an alias record

  1. Route 53 → Hosted Zones → your domain → Create Record.
  2. Record name: echoform (so it becomes echoform.yourdomain.com), or leave blank for the root domain.
  3. Record type: A.
  4. Toggle Alias: Yes.
  5. Route traffic to: Alias to Application Load Balancer.
  6. Select the region and echoform-alb.
  7. Save.

Step 25: Test

Visit http://echoform.yourdomain.com (or your chosen subdomain). It should load the form and route to whichever app server the ALB selects.


Phase 8 — Verify End-to-End High Availability

Step 26: Submit the form multiple times

Refresh and resubmit several times through the domain name, not the raw EC2 IPs.

Step 27: Confirm both instances are being used

On ec2-db:

SELECT source_instance, COUNT(*) FROM contact_submissions GROUP BY source_instance;

You should see submissions attributed to both EC2 instance IDs, proving the ALB is distributing traffic across both Availability Zones while both write to the same shared database.

Download the complete project source code. It’s fully open source, free to modify, and comes with no author restrictions. Click the Download button below.

image

1 Comment

Leave a Reply

Your email address will not be published. Required fields are marked *