AWS Builder Center
Building a 3-Tier Architecture on AWS: A Practical End-to-End Guide

Building a 3-Tier Architecture on AWS: A Practical End-to-End Guide

Learn how to design and deploy a secure, highly available 3-tier application using Amazon VPC, Application Load Balancer, EC2 Auto Scaling, private subnets, NAT Gateway, and a managed database.

Cloud Data Engineer

Introduction

A common mistake when learning AWS architecture is to think of an application as simply:
User → EC2 → Database
That may work for a small experiment, but production applications usually need stronger separation between the public entry point, application workloads, and data.
A 3-tier architecture separates an application into three logical layers:
  1. Presentation Tier — receives user requests.
  2. Application Tier — processes business logic.
  3. Data Tier — stores application data.
A typical AWS implementation looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
INTERNET
|
v
+-------------------+
| Application |
| Load Balancer |
| Public Subnets |
+---------+---------+
|
v
+-------------------+
| Application Tier |
| Private Subnets |
| EC2 + Auto Scaling|
+---------+---------+
|
v
+-------------------+
| Data Tier |
| Private Subnets |
| Amazon RDS |
+-------------------+
The important principle is:
Only the components that need to be public should be publicly reachable.

1. Understanding the Three Tiers

Tier 1 — Presentation Tier

This is the entry point for users.
Examples include:
  • Application Load Balancer
  • Web servers
  • Frontend applications
In our architecture, the Application Load Balancer (ALB) is internet-facing.
Users communicate with the ALB instead of directly connecting to application EC2 instances.
1
2
3
4
5
User
|
| HTTPS
v
ALB

Tier 2 — Application Tier

This layer contains the application's business logic.
For example:
  • REST API
  • Backend service
  • Authentication logic
  • Business processing
We can run the application on EC2 instances inside private subnets.
1
2
3
4
5
6
7
ALB
|
v
Private EC2
|
v
Backend Application
Because these instances don't need to accept direct internet traffic, there is no reason to expose them with public IP addresses.

Tier 3 — Data Tier

This layer stores application data.
For this project, we can use:
Amazon RDS
Examples include:
  • PostgreSQL
  • MySQL
  • MariaDB
The database should remain inside private subnets.
1
2
3
4
Application EC2
|
v
RDS
The database security group should allow database traffic only from the application tier.

2. Complete Architecture

A more production-oriented design could look like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
INTERNET
|
v
+---------------------------+
| Application Load Balancer |
| Public Subnet |
| AZ-1 |
+-------------+-------------+
|
|
+-------------+-------------+
| |
v v
+-------------+ +-------------+
| Application | | Application |
| EC2 | | EC2 |
| Private AZ1 | | Private AZ2 |
+------+------+ +------+------+
| |
+-------------+-------------+
|
v
+---------------+
| Amazon RDS |
| Private Subnet|
+---------------+

Private EC2
|
v
NAT Gateway
|
v
Internet Gateway
|
v
Internet
For high availability, the ALB and application tier should span multiple Availability Zones.

3. Step 1 — Design the VPC

Before creating resources, design the network.
Let's use:
1
2
VPC CIDR:
10.0.0.0/16
We can divide it into four subnets:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Availability Zone 1

Public Subnet
10.0.1.0/24

Private App Subnet
10.0.11.0/24

Availability Zone 2

Public Subnet
10.0.2.0/24

Private App Subnet
10.0.12.0/24
For the database, we should use separate private subnets:
1
2
3
4
5
Private DB Subnet AZ-1
10.0.21.0/24

Private DB Subnet AZ-2
10.0.22.0/24
Our logical structure becomes:
1
2
3
4
5
6
7
8
9
10
11
VPC
│
├── AZ-1
│ ├── Public Subnet
│ ├── Private App Subnet
│ └── Private DB Subnet
│
└── AZ-2
├── Public Subnet
├── Private App Subnet
└── Private DB Subnet
Each subnet belongs to one Availability Zone.

4. Why Multiple Availability Zones?

Suppose everything is deployed in one Availability Zone:
1
2
3
4
5
AZ-1
|
+-- ALB
+-- EC2
+-- Database
If that AZ experiences a failure, the entire application can be affected.
Instead:
1
2
3
4
5
6
7
AZ-1 AZ-2

EC2 EC2
| |
+----------+-----------+
|
ALB
Spreading workloads across multiple AZs reduces dependence on a single Availability Zone.
This is one of the foundations of highly available AWS architectures.

5. Step 2 — Create the Internet Gateway

Attach an Internet Gateway to the VPC.
The public route table can contain:
1
2
3
4
Destination Target
--------------------------------
10.0.0.0/16 local
0.0.0.0/0 Internet Gateway
This allows resources in the public subnet to communicate with the internet when their security configuration permits it.
The ALB will use these public subnets.

6. Step 3 — Create NAT Gateway

Our application servers are private.
However, they might need outbound internet access for:
  • Operating system updates
  • Installing packages
  • Downloading dependencies
  • Calling approved external APIs
We therefore create a NAT Gateway in a public subnet.
Traffic becomes:
1
2
3
4
5
6
7
8
9
10
11
12
13
Private EC2
|
v
Private Route Table
|
v
NAT Gateway
|
v
Internet Gateway
|
v
Internet
The private application route table can contain:
1
2
3
4
Destination Target
--------------------------------
10.0.0.0/16 local
0.0.0.0/0 NAT Gateway
The key point is:
NAT provides outbound internet access; it does not make the private EC2 instance publicly accessible.
For production environments, NAT Gateway placement and cost should be evaluated carefully, especially when workloads span multiple AZs.

7. Step 4 — Configure Security Groups

This is one of the most important parts of the architecture.
We should create separate security groups for each tier.

ALB Security Group

Allow:
1
2
3
Inbound:

HTTPS 443 → 0.0.0.0/0
For a temporary learning setup, HTTP 80 can also be used.

Application Security Group

Allow application traffic only from the ALB security group.
For example:
1
2
HTTP 80
Source: ALB Security Group
Not:
1
2
HTTP 80
Source: 0.0.0.0/0

Database Security Group

Suppose RDS PostgreSQL uses port 5432.
Allow:
1
2
TCP 5432
Source: Application Security Group
This creates a security chain:
1
2
3
4
5
6
7
8
9
10
Internet
|
v
ALB SG
|
v
Application SG
|
v
Database SG
Each layer trusts only the layer that should communicate with it.

8. Step 5 — Create the Application Load Balancer

Create:
Application Load Balancer
Configuration:
1
2
3
4
5
6
7
8
9
10
11
12
Scheme:
Internet-facing

IP Address Type:
IPv4

VPC:
Application VPC

Subnets:
Public AZ-1
Public AZ-2
The ALB becomes the public entry point.
1
2
3
4
Internet
|
v
ALB
Users should not need to know the private IP addresses of application servers.

9. Step 6 — Create the Target Group

Create a target group for the application EC2 instances.
Example:
1
2
3
4
5
6
7
8
9
10
11
Target Type:
Instances

Protocol:
HTTP

Port:
80

Health Check:
HTTP /
The target group might contain:
1
2
EC2-1 → Healthy
EC2-2 → Healthy
If an instance fails its health checks, the ALB can stop routing normal traffic to that unhealthy target.

10. Step 7 — Create the EC2 Launch Template

Instead of manually configuring every EC2 instance, create a Launch Template.
The template can define:
  • AMI
  • Instance type
  • Security group
  • IAM role
  • EBS volume
  • User Data
  • Tags
For example:
1
2
3
4
5
6
7
8
Launch Template
|
+-- Ubuntu
+-- t3.micro
+-- Application SG
+-- IAM Role
+-- EBS
+-- User Data
This becomes the blueprint for application servers.

11. Step 8 — Configure User Data

We can automatically install the application when an EC2 instance launches.
For example:
1
2
3
4
5
6
7
8
9
10
#!/bin/bash

apt-get update -y
apt-get install -y nginx

systemctl enable nginx
systemctl start nginx

echo "<h1>3-Tier AWS Application</h1>" \
> /var/www/html/index.html
Now every EC2 instance launched from the template can automatically configure itself.
This becomes especially useful when Auto Scaling creates additional instances.

12. Step 9 — Create the Auto Scaling Group

Create an Auto Scaling Group using the Launch Template.
Example:
1
2
3
4
5
6
7
8
Minimum:
2

Desired:
2

Maximum:
6
Select the private application subnets:
1
2
Private App AZ-1
Private App AZ-2
Now our application tier can scale horizontally.
1
2
3
4
5
6
ALB
|
+-------+-------+
| |
v v
EC2-1 EC2-2
If traffic increases:
1
2
3
4
ALB
/ | \
v v v
EC2-1 EC2-2 EC2-3

13. Step 10 — Configure Scaling Policies

Auto Scaling isn't useful simply because it can create instances.
We need to tell it when to scale.
For example:
1
2
3
4
Average CPU > 60%
|
v
Scale Out
And:
1
2
3
4
Average CPU < 30%
|
v
Scale In
In a production system, CPU may not be the best metric.
Depending on the application, useful metrics can include:
  • ALB request count per target
  • CPU utilization
  • Memory utilization
  • Queue depth
  • Application-specific CloudWatch metrics
The scaling metric should represent actual application demand.

14. Step 11 — Create the Database Subnets

Now create a separate subnet group for RDS.
Example:
1
2
3
4
5
DB Subnet AZ-1
10.0.21.0/24

DB Subnet AZ-2
10.0.22.0/24
These should be private subnets.
Create an RDS DB Subnet Group using both database subnets.

15. Step 12 — Create Amazon RDS

For the learning project, we can use:
1
2
3
4
5
6
7
8
9
10
11
Engine:
PostgreSQL

Deployment:
Multi-AZ where appropriate

Subnet:
Private DB Subnet Group

Public Access:
No
The application tier connects to RDS using the database endpoint.
1
2
3
4
5
Application EC2
|
| TCP 5432
v
Amazon RDS
The database should not be directly accessible from the internet.

16. How the Complete Request Works

Let's follow one request.
A user opens:
1
https://example.com

Step 1

DNS resolves the application endpoint.

Step 2

The request reaches the public ALB.
1
2
3
4
Internet
|
v
ALB

Step 3

The ALB checks the target group.
1
2
EC2-1 → Healthy
EC2-2 → Healthy

Step 4

The ALB selects a healthy application server.
1
ALB → EC2-1

Step 5

The application processes the request.
If data is required:
1
2
3
4
EC2
|
v
RDS

Step 6

The response travels back:
1
2
3
4
5
6
7
8
9
10
RDS
|
v
EC2
|
v
ALB
|
v
User

17. Frontend and Backend in a 3-Tier Design

There are actually two ways people commonly describe "3-tier."

Traditional application model

1
2
3
4
5
Presentation
|
Application
|
Data

AWS implementation

1
2
3
4
5
ALB/Web
|
EC2/Application
|
RDS
For a larger application, we can further separate frontend and backend services:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Internet
|
v
ALB
|
v
Frontend Service
|
v
Internal Load Balancer
|
v
Backend Service
|
v
RDS
This can provide stronger separation and independent scaling.

18. Why Use an Internal Load Balancer?

Suppose we have multiple backend instances:
1
2
3
4
5
6
7
Frontend
|
v
Internal ALB
/ \
v v
API-1 API-2
The backend services don't need to be publicly accessible.
The internal ALB provides a private endpoint inside the VPC.
This is useful for microservices and service-to-service communication.

19. VPC Routing

Understanding routing is essential.
The VPC automatically provides a local route for traffic within the VPC CIDR.
For example:
1
2
3
Destination Target
--------------------------------
10.0.0.0/16 local
Public subnet:
1
2
3
0.0.0.0/0
↓
Internet Gateway
Private application subnet:
1
2
3
0.0.0.0/0
↓
NAT Gateway
Database subnet:
1
No direct internet route
This creates different network boundaries for each tier.

20. Why Should the Database Be Private?

A database usually doesn't need to communicate directly with internet users.
Imagine:
1
2
3
4
Internet
|
X
Database
Instead:
1
2
3
4
5
6
7
8
9
10
Internet
|
v
ALB
|
v
Application
|
v
Database
If the database is not directly exposed, its attack surface is significantly reduced.
The database security group should accept connections only from the application tier.

21. High Availability

Our architecture can survive several types of failures better than a single-server deployment.
For example:
1
2
3
4
5
6
7
AZ-1 AZ-2

EC2-1 EC2-2
| |
+-------- ALB -------+
|
Users
If one application instance fails, another healthy instance can continue serving requests.
If Auto Scaling is configured correctly, the failed capacity can also be replaced.
For the database layer, RDS Multi-AZ can provide a standby database in another Availability Zone for failover.

22. Monitoring the Architecture

A production architecture needs visibility.
Useful AWS services include:

Amazon CloudWatch

Monitor:
  • EC2 CPU
  • ALB request count
  • ALB response time
  • Target health
  • Auto Scaling activity
  • RDS metrics

VPC Flow Logs

Useful for investigating network traffic.

ALB Access Logs

Useful for analyzing HTTP requests.

CloudTrail

Useful for auditing AWS API activity.
A useful production mindset is:
If something fails at 2 AM, can I understand what happened without logging into every server manually?

23. Security Improvements for Production

The basic architecture is a strong learning foundation, but production requires additional controls.
I would consider:

AWS WAF

Protect the public ALB against common web attacks.

HTTPS

Use TLS certificates through AWS Certificate Manager.

IAM Roles

Avoid storing AWS credentials inside EC2.

Secrets Manager

Store database passwords and application secrets securely.

Systems Manager

Use Session Manager where appropriate instead of distributing SSH keys.

VPC Endpoints

Use private connectivity to supported AWS services where appropriate, potentially reducing NAT dependency and improving network security.

Encryption

Use encryption for:
  • EBS
  • RDS
  • S3
  • Application traffic
  • Secrets

24. Cost Considerations

A highly available architecture costs more than a single EC2 instance.
Potential cost components include:
  • EC2
  • Application Load Balancer
  • NAT Gateway
  • RDS
  • EBS
  • CloudWatch
  • Data transfer
NAT Gateway deserves special attention because it has both hourly and data-processing charges.
For workloads that heavily access AWS services such as S3, consider whether appropriate VPC endpoints can reduce unnecessary NAT processing and data-transfer paths.
Architecture decisions should therefore consider:
Security + Availability + Performance + Cost
—not just whether the application works.

25. Common Beginner Mistakes

Mistake 1 — Putting EC2 directly on the internet

Better:
1
Internet → ALB → Private EC2

Mistake 2 — Making RDS publicly accessible

Prefer:
1
Private EC2 → Private RDS

Mistake 3 — Allowing 0.0.0.0/0 everywhere

Use security-group-to-security-group rules wherever possible.

Mistake 4 — Running everything in one AZ

Use multiple AZs for workloads that require high availability.

Mistake 5 — Manually configuring every EC2

Use Launch Templates and Auto Scaling.

Mistake 6 — Assuming "private" means "no internet access"

Private instances can have controlled outbound connectivity through NAT Gateway or VPC endpoints.

Mistake 7 — Ignoring data-transfer costs

Cross-AZ and NAT traffic can become significant at scale.

26. Testing the Architecture

After deployment, don't just check whether the website opens.
Test each layer.

Test 1 — ALB

1
curl http://<ALB-DNS>

Test 2 — Target Health

Verify that targets show:
1
Healthy

Test 3 — Scaling

Generate controlled traffic and observe Auto Scaling.

Test 4 — Failure

Terminate one application instance and observe whether:
  • ALB removes it from service
  • Auto Scaling launches a replacement
  • The application remains available

Test 5 — Database Connectivity

Verify that the application can connect to RDS.

Test 6 — Security

Confirm that:
1
Internet → EC2
is not directly possible.
Also verify that:
1
Internet → RDS
is not possible.

27. Recommended Build Order

When building this project yourself, don't randomly create resources.
Follow this sequence:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
1. Plan CIDR
↓
2. Create VPC
↓
3. Create Subnets
↓
4. Create Internet Gateway
↓
5. Create Route Tables
↓
6. Create NAT Gateway
↓
7. Create Security Groups
↓
8. Create RDS Subnet Group
↓
9. Create RDS
↓
10. Create Launch Template
↓
11. Create Target Group
↓
12. Create Application Load Balancer
↓
13. Create Auto Scaling Group
↓
14. Configure Scaling
↓
15. Configure DNS/HTTPS
↓
16. Test
↓
17. Monitor
Following the dependency order makes troubleshooting much easier.

28. Turning This into a GitHub Project

For a portfolio project, I would not stop at creating everything manually in the AWS Console.
A better next step is Infrastructure as Code.
For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
aws-3-tier-architecture/
│
├── README.md
│
├── architecture/
│ └── architecture.png
│
├── terraform/
│ ├── provider.tf
│ ├── variables.tf
│ ├── outputs.tf
│ ├── vpc.tf
│ ├── subnets.tf
│ ├── routes.tf
│ ├── nat.tf
│ ├── security-groups.tf
│ ├── alb.tf
│ ├── target-group.tf
│ ├── launch-template.tf
│ ├── autoscaling.tf
│ └── rds.tf
│
├── application/
│ ├── frontend/
│ └── backend/
│
└── scripts/
└── user-data.sh
Then the entire environment can be recreated consistently.

29. Final Architecture

Putting everything together:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
INTERNET
|
v
Route 53 / DNS
|
v
+----------------------+
| Application LB |
| Public Subnets |
+----------+-----------+
|
+----------+----------+
| |
v v
+-------------+ +-------------+
| Application | | Application |
| EC2 / ASG | | EC2 / ASG |
| Private AZ1 | | Private AZ2 |
+------+------+ +------+------+
| |
+----------+----------+
|
v
+----------------------+
| Amazon RDS |
| Private Subnets |
| Multi-AZ |
+----------------------+

Private Application EC2
|
v
NAT Gateway
|
v
Internet Gateway
|
v
Internet
The security boundaries look like:
1
2
3
4
5
6
7
8
9
10
11
12
13
Internet
|
| HTTPS
v
ALB Security Group
|
| Application Port
v
Application Security Group
|
| DB Port
v
Database Security Group

Conclusion

Building a 3-tier architecture is not simply about launching an EC2 instance and connecting a database.
It is about understanding how the application should communicate, where each component should live, who is allowed to communicate with whom, and how the system behaves when traffic or failures occur.
The core architecture is:
Internet → ALB → Private Application Tier → Private Database Tier
And the supporting AWS services provide:
  • VPC → network isolation
  • Subnets → resource placement
  • Route Tables → traffic direction
  • Internet Gateway → public internet connectivity
  • NAT Gateway → controlled outbound connectivity
  • Security Groups → stateful network access control
  • ALB → traffic distribution
  • Launch Template → consistent EC2 configuration
  • Auto Scaling → elasticity and recovery
  • RDS → managed relational database
  • CloudWatch → monitoring and observability
The biggest lesson is:
A production-oriented AWS architecture is designed around boundaries: network boundaries, security boundaries, failure boundaries, and scaling boundaries.
Once these concepts become clear, AWS services stop feeling like isolated products and start becoming building blocks for complete solutions.
This is also the direction I'm taking in my AWS Solutions Architect learning journey: not just learning how to create a service, but understanding why it belongs in an architecture and what happens when the workload grows or something fails. 🚀☁️
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article