1. Situation
Your team is launching an online store. Unlike a purely static website, this application must run server-side code for each request.
For example, when a user opens /orders/42, the application must:
- identify the user;
- read order data from a relational database;
- apply business rules; and
- generate a response.
Users can also upload product images and receipts. Traffic is usually moderate, but promotions can create sudden spikes. The business cannot accept an outage every time one virtual machine or one Availability Zone has a problem.
The requirements are:
- a public HTTPS domain;
- no direct internet access to application servers or the database;
- enough capacity when traffic grows;
- continued service when one application instance fails;
- continued service when one Availability Zone has a problem;
- durable storage for relational data and uploaded files; and
- no long-lived AWS access keys stored on servers.
This study develops the foundational AWS pattern:
public entry point -> private application tier -> private data tier
The load balancer is the public reception desk. Amazon EC2 supplies the virtual machines that run the application—the workers in private offices. Amazon RDS is the managed relational database—the protected records room—and Amazon S3 is durable object storage for files. Visitors reach reception; they do not walk directly into every office or the records room.
The goal is not to memorize a diagram. It is to understand which problem forces each layer to exist.
2. Naive Design
The team begins with one EC2 instance that has a public IP address:
The instance does everything:
- accepts HTTPS requests;
- runs the application;
- stores uploaded files on its local disk;
- runs the database;
- stores user sessions in memory; and
- keeps AWS access keys in a configuration file.
This resembles a familiar virtual private server. It is easy to understand, inexpensive at small scale, and reasonable for an early experiment.
Its simplicity comes from combining every responsibility into one failure boundary.
Adding a larger instance postpones that problem. It does not remove it.
3. What Breaks
Follow four failure stories.
Failure 1: the server stops
At 10:00, the instance fails a system status check. The application, database, uploaded files, and in-memory sessions all disappear from service together.
There is no second healthy application target and no component able to route around the failure.
The architecture needs multiple application instances and health-based routing.
Failure 2: a promotion creates a traffic spike
At 14:00, request volume increases fivefold. The single server reaches its CPU and connection limits. Requests become slow, then begin timing out.
Manually resizing the instance requires prediction and intervention. The architecture needs horizontal scaling: adding and removing application instances as demand changes.
Failure 3: the team adds a second server
The second instance helps with compute, but state now splits:
user A uploads image.jpg -> instance A local disk
next request -> instance B cannot find image.jpg
The same problem affects in-memory sessions. Replaceable servers cannot be the only owners of durable or shared state.
The architecture needs state outside the application instances.
Failure 4: public exposure grows with every component
The server accepts user traffic, SSH, and database connections. Long-lived AWS keys sit on disk. A mistake in one security rule can expose far more than the web application.
The architecture needs a narrow public boundary and temporary workload credentials.
These failures lead to four design moves:
one public entrance
+ multiple replaceable app instances
+ managed durable state
+ identity without stored access keys
4. AWS Architecture
Build the architecture one responsibility at a time.
Step 1: create network boundaries across two Availability Zones
A VPC is the application's private network boundary in an AWS Region. An Availability Zone, or AZ, is an isolated location within that Region.
Use at least two AZs so one location is not the only place able to run the application.
Within each AZ, create subnet tiers:
A public subnet has a route to an internet gateway. A private subnet does not have a direct route to the internet gateway. The label comes from routing, not from the resource's name. The route alone does not give an EC2 instance a public IP address; routing and public addressing are separate requirements for direct internet connectivity.
Before using those labels, establish the routing model:
- A route table is a set of rules associated with a subnet. It decides where network traffic from that subnet should go next.
- A route's destination describes which destination IP addresses the rule matches.
- A route's target is the next network component for matching traffic.
- An internet gateway is the VPC component that provides a path between public VPC resources and the internet.
0.0.0.0/0represents every IPv4 destination, so it acts as the general anything else route.
A simplified public-subnet route table contains this rule:
| Destination | Target | Meaning |
|---|---|---|
0.0.0.0/0 | Internet gateway | Send general IPv4 internet traffic to the VPC's internet gateway. |
A private subnet does not use that direct internet-gateway route. It may later use a NAT gateway for initiated outbound traffic, but that still does not make it a public entrance. A real route table also contains a local route for traffic inside the VPC; the table above isolates the rule that makes the public/private distinction easier to see.
The public subnets will hold the internet-facing entry layer. Application and database resources will use the private tiers.
Step 2: add one public entry point
Create an internet-facing Application Load Balancer (ALB) in public subnets across at least two AZs.
The ALB understands HTTP and HTTPS. Its listener waits for traffic on a port such as 443. A listener rule forwards matching requests to a target group, which is the set of application backends eligible to receive them.
The important configuration relationship is:
| Component | Example configuration | Job |
|---|---|---|
| HTTPS listener | Port 443 with an AWS Certificate Manager (ACM) certificate | Accept encrypted public requests. |
| Listener rule | Forward matching requests to the application target group | Choose the backend group for the request. |
| Target group | EC2 application instances on the application port | Hold the possible request destinations. |
| Health check | Request a path such as /health | Decide which registered targets are currently eligible. |
Attach the ACM certificate to the HTTPS listener. Because an ALB is regional, its ACM certificate is created in the same Region as the load balancer.
Route 53 only answers DNS. It tells the browser how to find the ALB; it is not an HTTP hop through which every request passes.
Step 3: make the application tier private and replaceable
Launch EC2 application instances in the private app subnets through an Auto Scaling group.
The Auto Scaling group uses a launch template to describe each new instance: its machine image, instance type, security groups, IAM role, and startup configuration. It also maintains a chosen minimum, desired, and maximum capacity.
The ALB sends traffic only to targets that pass the target group's health check. When Elastic Load Balancing health checks are enabled for the Auto Scaling group, the group can replace an instance that the load balancer reports as unhealthy.
These are related but different jobs:
- the ALB routes around an unhealthy instance;
- the Auto Scaling group restores capacity by launching a replacement; and
- a scaling policy changes capacity when load changes.
The private EC2 instances do not need public IP addresses to receive requests from the ALB. The public connection ends at the ALB. The ALB then opens a separate connection to a healthy target's private IP address inside the VPC, and the application security group allows that connection from the ALB security group.
Step 4: move durable state out of EC2
Place the relational database in private database subnets. First, create an RDS DB subnet group: a list of the subnets where RDS is allowed to place database resources. Include private database subnets from multiple AZs so RDS has more than one placement location. The subnet group creates placement options; it does not create a standby by itself. When the database must survive an AZ failure, enable a Multi-AZ DB instance deployment so RDS also maintains a synchronous standby in another AZ.
Store uploaded objects in S3 rather than on EC2 instance disks.
Multi-AZ protects database availability; its standby is not the read-scaling target in this classic deployment. Backups and point-in-time recovery are still needed because a standby can receive bad data changes as well as good ones.
Once shared state lives in RDS and S3, any healthy application instance can handle the next request. This is the practical meaning of a stateless application tier: an instance is not the only durable owner of data needed by future requests.
For large uploads, the application can create a short-lived S3 presigned URL. The browser then uploads a specific object directly to S3 without receiving AWS credentials and without sending the file bytes through EC2. The URL carries a signature created with the application's AWS identity, so S3 can authorize that limited operation without giving the browser general AWS credentials. Because anyone holding the URL can use it until it expires, keep its operation, object key, and lifetime narrow.
Step 5: give the workload an identity
An IAM role is the AWS identity the application uses while it runs. The role's permissions define which AWS API operations that identity may perform. Because this application runs on EC2, attach the role through an instance profile, which is the AWS mechanism that makes the role available to an EC2 instance. The application can then obtain rotating temporary credentials automatically instead of reading long-lived access keys from a file.
Grant only the required actions and resources. An action is an operation such as reading an S3 object; a resource identifies what that operation may affect, such as one S3 bucket prefix or one Secrets Manager secret.
An IAM role answers:
what AWS APIs may this application call?
A security group answers:
which network connections may reach this resource?
Do not confuse identity permissions with network reachability. A role that permits s3:PutObject does not create a network route to S3, and a route to S3 does not grant permission to upload.
Step 6: add outbound access only where it is needed
Private application instances may need outbound access for operating system updates or external APIs. A public NAT gateway can provide outbound IPv4 access while preventing the internet from initiating connections to those instances.
NAT is not part of the user's inbound request path:
| Direction | Path | What the first public component does |
|---|---|---|
| Inbound user request | User → internet-facing ALB → private EC2 | The ALB accepts the public request and forwards it privately. |
| Outbound application request | Private EC2 → NAT gateway → internet gateway → internet | NAT translates the instance's private source address for a connection the instance initiated. |
The NAT gateway does not accept unsolicited inbound user traffic and does not sit between the user and the ALB.
For S3 and DynamoDB traffic, a gateway VPC endpoint can provide a private route and avoid sending that traffic through a NAT gateway.
The completed architecture is:
5. Request Or Data Flow
Learn three lifecycles instead of memorizing every arrow at once.
Lifecycle 1: normal application request
- Route 53 answers the DNS query for
app.example.comwith the ALB target. - The browser opens an HTTPS connection to the ALB.
- The ALB listener evaluates its rules and selects the target group.
- The ALB sends the request to one healthy EC2 target over private networking.
- The application reads or writes relational data through the RDS endpoint.
- The application returns the response through the ALB to the user.
Lifecycle 2: an application instance fails
- The target stops returning the expected health-check response.
- The ALB marks it unhealthy and stops routing new requests to it.
- Healthy targets in the other AZ continue receiving traffic.
- If ELB health checks are enabled on the Auto Scaling group, the group replaces the unhealthy instance.
- The replacement starts from the launch template, passes health checks, and enters service.
Health checks should test whether the application can serve useful traffic. A check that returns success while the application cannot operate gives the load balancer false confidence. Avoid making the check so dependent on every downstream system that one database issue causes every app target to be replaced simultaneously.
Lifecycle 3: a user uploads a file
For a simple upload, the application receives the file and writes it to S3 using its instance role.
For a larger direct upload:
The application still decides who may upload and what object key is allowed. S3 carries the file bytes.
6. Security Controls
Chain security groups by tier
Use security group references to express which tier may call the next tier:
| Security group | Allow inbound from | Typical purpose |
|---|---|---|
| ALB security group | Internet clients on HTTPS | Public application entry |
| Application security group | ALB security group on the application port | Prevent direct client access to EC2 |
| Database security group | Application security group on the database port | Prevent direct client and ALB access to RDS |
This is more precise than allowing the entire VPC CIDR at every layer.
Keep application and database resources private
Do not assign public IP addresses to normal application instances. Do not make RDS publicly accessible. The fact that the product is public does not make every component public.
Use roles for AWS API access
Attach a least-privilege instance role rather than storing access keys in the AMI, source code, user data, or environment files managed by hand.
Store application secrets separately
An EC2 role proves the application's AWS identity. A database password is still a secret. Store secrets in a managed service such as Secrets Manager and grant the role permission to retrieve only the required secret.
Protect administrative access
Avoid opening SSH to the world. Prefer Systems Manager Session Manager or tightly controlled administrative paths when interactive access is required.
Encrypt the intended paths
Use HTTPS from users to the ALB. Use encryption for RDS, S3, EBS, backups, and application-to-database connections when the security requirement calls for it.
7. Resilience Controls
Run real capacity in more than one AZ
Selecting two subnets is not enough if the Auto Scaling group runs only one instance. To tolerate one instance or AZ failure without losing all application capacity, maintain healthy instances across multiple AZs and enough spare capacity for the remaining AZ to carry the load.
Combine routing with replacement
The ALB removes unhealthy targets from service. Auto Scaling replaces failed capacity. Neither action repairs application bugs, so monitor whether a new release causes every target to fail the same health check.
Keep compute replaceable
Do not make local files or in-memory sessions the only copy of important state. Use RDS, S3, DynamoDB, ElastiCache, or another suitable shared service according to the data's durability and consistency requirements.
Protect the database separately
Use RDS Multi-AZ for managed failover when database availability matters. Use automated backups and point-in-time recovery for historical recovery. These solve different problems.
Review Highly Available RDS App for the database failover lifecycle and application reconnect behavior.
Avoid moving the failure boundary to NAT
If private instances need internet access during an AZ failure, the NAT design must survive that failure too. A single NAT gateway runs in one AZ. If application subnets in other AZs all depend on it, losing its AZ also removes their internet egress even when their EC2 instances remain healthy.
Monitor the user path, not only the servers
Use CloudWatch metrics and alarms for signals such as ALB response time and 5XX errors, healthy target count, Auto Scaling capacity, EC2 resource pressure, and RDS connections and storage. Centralize application logs so a terminated instance does not take its only diagnostic history with it.
An alarm should describe an action-worthy symptom. For example, low healthy-target count reveals lost serving capacity more directly than an alarm that reports only one instance's CPU.
8. Performance Controls
Use a scaling metric that represents application pressure. Useful signals may include:
- ALB request count per target;
- CPU utilization for compute-heavy work;
- queue depth for worker-style processing; or
- a custom latency or concurrency metric.
CPU is not automatically the best metric. An application can run out of database connections or request workers while CPU remains moderate.
Set a realistic instance warm-up period so a newly launched instance has time to start before scaling decisions treat it as fully ready.
Move static assets and public downloads away from the application tier when useful. S3 and CloudFront can deliver those objects without consuming EC2 request capacity. The next study, CloudFront WAF Protected Web Edge, develops that global edge.
Tune the database only after identifying the bottleneck. Indexing, connection pooling, instance sizing, caching, and read replicas solve different pressures. A read replica helps read scale; it is not the classic automatic Multi-AZ failover answer.
9. Cost Controls
Set deliberate Auto Scaling minimum, desired, and maximum capacity. A minimum that is too low may violate availability; a minimum that is too high wastes money.
Right-size EC2 and RDS using real metrics. Use Savings Plans or reservations for predictable baseline usage, and use Spot Instances only for capacity that can tolerate interruption.
Watch NAT gateway processing and cross-AZ data-transfer patterns. Use an S3 gateway endpoint when it meets the access pattern so private S3 traffic does not need NAT.
Store uploaded files in S3 rather than growing every application instance's EBS volume. Apply lifecycle rules when older objects or logs can move to cheaper storage classes or expire.
Do not add Multi-AZ, read replicas, caches, CloudFront, and multiple NAT gateways merely because they appear in a reference diagram. Each adds value only when a requirement justifies its availability, performance, security, or operational benefit.
10. Exam Variants
| If the question says... | Think... | Why |
|---|---|---|
| "Highly available web application" | ALB + Auto Scaling targets across multiple AZs | The ALB routes around failures while Auto Scaling maintains capacity |
| "Only the load balancer should be public" | Internet-facing ALB in public subnets; EC2 in private subnets | Public ingress and private execution are separate tiers |
| "Instances need S3 access without stored credentials" | EC2 instance role | The workload receives temporary credentials automatically |
| "Database must not be internet accessible" | Private database subnets + database security group | Only the application tier needs database network access |
| "Automatic relational database failover" | RDS Multi-AZ | A standby is maintained for availability |
| "Scale read-heavy database traffic" | Read replica | Read scaling is different from Multi-AZ failover |
| "Uploaded files disappear when instances are replaced" | Store objects in S3 | Application instances should not own the only durable copy |
| "Private instances need software updates from the internet" | NAT gateway for outbound access | NAT permits initiated outbound connections, not inbound user traffic |
| "Private instances need only S3 or DynamoDB" | Gateway VPC endpoint | Those service paths can avoid NAT |
| "Global users need cached content and edge protection" | Add CloudFront and, when required, AWS WAF | This evolves the regional public edge |
Reconstruct the base design from the requirements:
one public web entrance -> internet-facing ALB
healthy dynamic compute -> target group + EC2 Auto Scaling
no direct server exposure -> private application subnets
relational durable state -> private RDS
uploaded object state -> S3
AWS API permissions -> EC2 instance role
AZ failure tolerance -> real capacity across multiple AZs
11. Common Traps
| Trap | Better reasoning |
|---|---|
| "The app is public, so EC2 must have a public IP." | The ALB is public and reaches EC2 over private VPC addresses. |
Drawing user -> Route 53 -> ALB as the HTTP path | Route 53 answers DNS; the user then connects to the ALB. |
| Sending inbound users through a NAT gateway | NAT provides outbound connectivity for private resources; it is not the public application entrance. |
| Running one instance while selecting two AZs | Subnet selection alone does not create redundant application capacity. |
| Assuming the ALB replaces failed servers | The ALB routes around failure; Auto Scaling replaces capacity when configured to use the relevant health signal. |
| Saving uploads or sessions only on EC2 | Replaceable instances require shared or durable state outside the instance. |
| Storing AWS keys on the server | Use an instance role with temporary credentials. |
| Allowing the entire internet to reach the application port | Let the app security group accept that port only from the ALB security group. |
| Using a read replica for automatic failover | Classic Multi-AZ and read replicas solve availability and read-scaling requirements respectively. |
| Treating Multi-AZ as a backup | Synchronously copied mistakes still require backup recovery. |
| Making the health check depend on every downstream service | A shared downstream failure can make all targets unhealthy and trigger harmful replacement churn. |
Final Mental Model: One-Minute Review
Final Architecture Map
Use this map to consolidate the boundaries: the ALB is public, the application instances are private and replaceable, and durable state lives outside EC2.
| Term | Exact job | Remember it as |
|---|---|---|
| Route 53 | Answers DNS so the client can find the public entry point | The address book |
| Application Load Balancer | Accepts public HTTP/HTTPS traffic and routes it to healthy targets | The reception desk |
| Target group and health checks | Track which application instances are ready to receive requests | The healthy-worker roster |
| Auto Scaling group | Maintains and adjusts the number of EC2 application instances | The staffing manager |
| Private EC2 instances | Run the server-side application without accepting direct internet traffic | Workers in private offices |
| RDS and S3 | Keep relational data and uploaded objects outside replaceable compute | The records room and warehouse |
| IAM role | Gives the application temporary permission to call AWS APIs | The worker's job badge |
| Security groups | Control which network connections may enter each tier | The locks between rooms |
The core pattern is one public entrance, replaceable private compute, and durable state outside the instances. The ALB routes around failure; Auto Scaling replaces capacity; IAM roles authorize AWS API calls; security groups control network paths.
12. Related Topics
Review Public vs Private Subnets for the routing boundary, ALB vs NLB vs GWLB for load-balancer selection, Amazon EC2 Auto Scaling for capacity behavior, Amazon RDS for managed relational databases, Amazon S3 for durable object storage, and Amazon CloudWatch for metrics, alarms, and logs.
Continue to CloudFront WAF Protected Web Edge to evolve this regional architecture into a global cached and protected entry point.
Official AWS references:
- Create an Application Load Balancer
- Security groups for an Application Load Balancer
- Use Elastic Load Balancing with an Auto Scaling group
- IAM roles for Amazon EC2
- Internet gateways and public subnet routing
- NAT gateways
- RDS Multi-AZ DB instance deployments
- Download and upload S3 objects with presigned URLs