# 🌐 Deploying a 3-Tier Web Application Architecture on AWS using VPC

In this project, I have designed and deployed a **3-Tier Web Application** architecture within a custom Virtual Private Cloud (VPC) using AWS services. This architecture follows industry best practices for security, scalability, and separation of concerns

> 🔒 **Secure** | ⚙️ **Modular** | ☁️ **AWS-Powered**

In this blog, I’ll demonstrate how I deployed a **3-tier application** on AWS using **custom VPC**. This architecture includes:

* **Nginx** as a Reverse Proxy (Web Layer)
    
* **Apache Tomcat** as the Application Server
    
* **MySQL** as the Database Server
    

## 🧱 What is 3-Tier Architecture?

A 3-tier architecture separates the app into:

1. **Web Tier** (Nginx) – Handles incoming HTTP requests
    
2. **App Tier** (Tomcat) – Runs backend application logic
    
3. **DB Tier** (MySQL) – Stores application data
    

## 🔧 Tech Stack & AWS Services Used

| Layer | Component | AWS Service |
| --- | --- | --- |
| Web | Nginx | EC2 in Public Subnet |
| App | Tomcat | EC2 in Private Subnet |
| DB | MySQL | EC2 in Private Subnet |
| Network | VPC, Subnets, Route Tables | AWS VPC |
| Others | NAT Gateway, IGW, SGs | AWS Infra |

## 🗺️ High-Level Architecture Diagram

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752733002921/712a857f-3459-4bee-af92-d2e03762ed16.png align="center")

This setup includes:

* **1 Public Subnet** for Nginx
    
* **2 Private Subnets**: App Tier (Tomcat) and DB Tier (MySQL)
    
* **Security Groups** with limited, directional access
    
* **NAT Gateway** for outbound internet access from private subnets
    

## 🪜 Step-by-Step Implementation

### ✅ Step 1: Create VPC

* **CIDR Block:** `10.1.0.0/16`
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752733109900/c34f32f9-f207-47e8-ac84-3d8e6294eed8.png align="center")

### ✅ Step 2: Create Subnets

* `10.1.1.0/24` – Public (Web: Nginx)
    
* `10.1.2.0/24` – Private (App: Tomcat)
    
* `10.1.3.0/24` – Private (DB: MySQL)
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752733258149/a96e39bc-bdba-4792-b84d-f806488bd2ca.png align="center")

### ✅ Step 3: Setup Internet Gateway + NAT

* IGW for public subnet (Nginx)
    
* NAT Gateway for public subnets (Web)
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752733354085/163f7353-1a72-4e0a-9140-f97c4fb1b2c0.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752733337592/792f8e51-1b1c-4f60-a1e9-2fa0d0a7429b.png align="center")

### ✅ Step 4: Configure Route Tables

* Public Route Table: `0.0.0.0/0 → IGW`
    
* Private Route Table: `0.0.0.0/0 → NAT`
    

### ✅ A. Public Route Table Configuration

* I created a **Route Table** named `web-rt`.
    
* I **associated** the **public subnet** (used for Nginx) with this `web-rt`.
    
* Then, I edited the route in `web-rt`:
    
    * **Destination**: `0.0.0.0/0` (this allows internet traffic)
        
    * **Target**: **Internet Gateway** (attached to the VPC)
        
* This allows public instances like Nginx server to access the internet directly.
    

### 🔒 B. Private Route Table Configuration

* I created another **Route Table** named `private-rt`.
    
* I **associated both private subnets** (one for Tomcat app and one for MySQL DB) with this `private-rt`.
    
* Then, I edited the route in `private-rt`:
    
    * **Destination**: `0.0.0.0/0`
        
    * **Target**: **NAT Gateway** (deployed in the public subnet)
        
* This setup allows private instances to **access the internet only for updates** (e.g., apt install), **without being exposed** to incoming public traffic.
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752733665222/d1317054-0d22-49e3-b23b-572ad27446f7.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752733721561/cdda6581-3208-4eea-9a93-92382065a8a2.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752733648562/e2b00add-4fe6-41b8-b613-0dbf50831383.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752733690138/9ce0dcd5-87b9-481e-bf28-5eb7eb092ebb.png align="center")

### ✅ Step 5: Launch EC2 Instances

#### 🌍 Web Tier (Nginx)

* EC2 in public subnet
    
* Installed **Nginx**
    
* Acts as **Reverse Proxy** forwarding to Tomcat
    
* SG allows ports: **80 (HTTP)** and **8080 (proxy)**
    

#### 🛡 Bastion Host

* EC2 in public subnet for SSH access to private EC2s
    
* SG allows port: **22**
    

#### ⚙ App Tier (Tomcat)

* EC2 in private subnet
    
* Installed **Apache Tomcat**
    
* SG allows traffic only from Nginx EC2 (Web SG)
    

#### 💾 DB Tier (MySQL)

* EC2 in private subnet
    
* Installed **MySQL**, secured
    
* SG allows port **3306** only from App Server
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752733817188/f9bffe6b-9f9e-4506-862f-e80eeebb120f.png align="center")

#### 🔐 Step 3: **Private Server Access via Public EC2 (Jump Server Method)**

Since **App** and **DB** servers are in **private subnets**, I used the **Web Server (Public EC2)** as a **jump host** to access them.

**Steps Followed:**

1. SSH into Public Web Server using `.pem` file:
    
    ```powershell
    ssh -i "my-key.pem" ubuntu@<public_ip>
    ```
    
2. Created a new file using:
    
    ```powershell
    vim jump.pem
    ```
    
3. Pasted the **private key** of the internal servers (App/DB) in `jump.pem`.
    
4. Changed permission:
    
    ```powershell
    chmod 400 jump.pem
    ```
    
5. Then, from the public server, logged in to private server:
    
    ```powershell
    ssh -i jump.pem ubuntu@10.1.2.97  # App Server
    ssh -i jump.pem ubuntu@10.1.3.105 # DB Server
    ```
    

✅ *This way, I securely accessed private servers using the public EC2 as a jump point.*

## 🌐 Nginx Installation on Web EC2 Instance (Public Subnet)

After launching the **Web EC2 instance** (in the **public subnet**), I connected to it using **SSH** with the default Ubuntu user. Then I installed and configured **Nginx** as follows:

### 🔧 Steps Performed:

1. **SSH into EC2 Instance:**
    
    ```powershell
    ssh -i "keypair.pem" ubuntu@<Public-IP>
    ```
    
2. **Update the System Packages:**
    
    ```powershell
    sudo apt update -y
    ```
    
3. **Install Nginx Web Server:**
    
    ```powershell
    sudo apt install nginx -y
    ```
    
4. **Start the Nginx Service:**
    
    ```powershell
    sudo systemctl start nginx
    ```
    
5. **Enable Nginx to Start on Boot:**
    
    ```powershell
    sudo systemctl enable nginx
    ```
    

## 🚀 Tomcat Installation on App EC2 Instance (Private Subnet)

On the **App Server EC2 instance** (launched in the **private subnet**), I installed and started **Apache Tomcat** to host Java-based web applications.

Since Tomcat requires Java, I installed JDK first, then downloaded and configured Tomcat.

### 🔧 Steps Performed:

1. **Update System Packages:**
    
    ```powershell
    sudo apt update -y
    ```
    
2. **Install Java Development Kit (Required for Tomcat):**
    
    ```powershell
    sudo apt install default-jdk -y
    ```
    
3. **Download the Latest Tomcat (Version 11.0.9):**
    
    ```powershell
    wget https://downloads.apache.org/tomcat/tomcat-11/v11.0.9/bin/apache-tomcat-11.0.9.tar.gz.asc
    ```
    
4. **Extract the Downloaded Archive:**
    
    ```powershell
    tar -xvzf apache-tomcat-11.0.9.tar.gz
    ```
    
5. **Start the Tomcat Server:**
    
    ```powershell
    ls
    cd apache-tomcat-11.0.9.tar.gz
    ls
    cd bin
    ./startup.sh
    ```
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752734185866/7fa6ce94-83da-4f32-a8f1-e5d8579dc7d6.png align="center")

## 🛢️ MySQL Installation on DB EC2 Instance (Private Subnet)

On the **Database Server EC2 instance** (placed in the **private subnet**), I installed **MySQL Server** to manage the backend database of the application securely.

### 🔧 Steps Performed:

1. **Update System Packages:**
    
    ```powershell
    sudo apt update -y
    ```
    
2. **Install MySQL Server:**
    
    ```powershell
    sudo apt install mysql-server -y
    ```
    
3. **Start and Enable MySQL Service:**
    
    ```powershell
    sudo systemctl start mysql
    sudo systemctl enable mysql
    ```
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752736736398/c7545c1f-fd7a-4068-8c6e-87c2d777fd48.png align="center")

## ⚙️ MySQL Configuration on DB EC2 (Private Subnet)

After installing MySQL on the **DB Server (private subnet)**, I performed additional configuration to allow **internal app server access** by setting the **bind-address** to the server’s **private IP**.

### 🔐 Step 1: Login to MySQL as Root User

To securely access MySQL, I logged in using the root user:

```powershell
sudo mysql -u root -p
```

*(You’ll be prompted to enter the root password set during secure installation.)*

---

### 🛠️ Step 2: Edit MySQL Configuration File

I modified the **MySQL bind-address** to allow access from the app server (within the VPC):

```powershell
sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
```

Inside this file, I located the following line:

```plaintext
bind-address = 127.0.0.1
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752734455242/e0c1296c-376a-4a5f-9ee4-1459500bcc30.png align="center")

And changed it to my **DB EC2’s private IP**, for example:

```plaintext
bind-address = 10.0.2.15
```

> ✅ This step ensures MySQL accepts connections only from internal sources (e.g., the app server), not from the public internet — keeping the database secure.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752734479134/cc97d642-9061-49f7-9e1a-2a7e1ba9ae7e.png align="center")

---

### 🔄 Step 3: Restart MySQL to Apply Changes

```powershell
sudo systemctl restart mysql
```

## 🔗 Network Connectivity Testing (Ping & Telnet)

To ensure all the instances in my 3-tier architecture (Web, App, and DB) are properly connected and communicating within the VPC, I performed two essential network checks:

---

### ✅ 1. **Ping Test (Initial Connectivity Verification)**

Before running any application-level commands, I verified the basic connectivity between all EC2 instances using `ping`.

* From **Web (10.1.1.44)**:
    
    * Ping to App server (`10.1.2.97`)
        
    * Ping to DB server (`10.1.3.105`)
        
* From **App (10.1.2.97)**:
    
    * Ping to Web server (`10.1.1.44`)
        
    * Ping to DB server (`10.1.3.105`)
        
* From **DB (10.1.3.105)**:
    
    * Ping to Web server (`10.1.1.44`)
        
    * Ping to App server (`10.1.2.97`)
        

> 📝 All ping tests were successful, confirming that the subnet routing and security group rules were correctly configured for basic communication.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752735954614/dbcd1f78-5b0a-48b6-848b-e1192acc8a98.png align="center")

### ✅ Verifying Internal Connectivity using Telnet in 3-Tier Architecture

To ensure all components in the 3-Tier Architecture (Web, App, and DB) can communicate with each other, we use the `telnet` command to test port-level connectivity.

Below is how each instance should verify connection with others using **Telnet**:

---

#### 🔹 From Web Server (Public Subnet - IP: `10.1.1.44`)

* Check connectivity to **App Server (Tomcat)**:
    
    ```powershell
    telnet 10.1.2.97 8080
    ```
    
* Check connectivity to **Database Server (MySQL)**:
    
    ```powershell
    telnet 10.1.3.105 3306
    ```
    

---

#### 🔹 From App Server (Private Subnet - IP: `10.1.2.97`)

* Check connectivity to **Web Server**:
    
    ```powershell
    telnet 10.1.1.44 22
    ```
    
* Check connectivity to **Database Server**:
    
    ```powershell
    telnet 10.1.3.105 3306
    ```
    

---

#### 🔹 From DB Server (Private Subnet - IP: `10.1.3.105`)

* Check connectivity to **Web Server**:
    
    ```powershell
    telnet 10.1.1.44 22
    ```
    
* Check connectivity to **App Server**:
    
    ```powershell
    telnet 10.1.2.97 8080
    ```
    

---

> ✅ If Telnet successfully connects (i.e., blank screen or "Connected"), it confirms that the port is open and reachable from that instance.

> ❌ If Telnet fails (i.e., "Connection refused" or "Unable to connect"), check **Security Groups**, **NACLs**, and **Routing Tables**.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752735815959/ea072fff-0ca7-4e23-b415-0ebf26884c24.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752735820273/75ec1098-e843-40e1-9699-5dec5bbd1138.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752735830041/9b9d7673-7a90-4e65-8c0e-9763a4954a54.png align="center")

## 📝 Conclusion

This project demonstrates a successful deployment of a secure and scalable 3-tier architecture on AWS using Nginx (Web), Tomcat (App), and MySQL (DB).  
It follows best practices for network isolation, access control, and modular application deployment in a cloud environment.

## **👨‍💻 About the Author**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1751797710818/123a7231-3dca-4273-ad68-7bd026f69b95.png?auto=compress,format&format=webp align="left")

This project is a deep dive into the AWS ecosystem designed to strengthen my foundation in cloud-native architecture, automation, and service integration **using only AWS services**.

This series isn't just about using AWS; it's about **mastering the core services that power modern cloud infrastructure**.

---

### 📬 Let's Stay Connected

* 📧 **Email**: [**gujjarapurv181@gmail.com**](mailto:gujjarapurv181@gmail.com)
    
* 🐙 **GitHub**: [**github.com/ApurvGujjar07**](https://github.com/ApurvGujjar07)
    
* 💼 **LinkedIn**: [**linkedin.com/in/apurv-gujjar**](https://www.linkedin.com/in/apurv-gujjar)
    

---

> ***💡 If you found this project useful, or have any suggestions or feedback, feel free to reach out or drop a comment I’d love to connect and improve.  
> This is just the beginning many more builds, deployments, and learnings ahead.***
