I deployed a supplied PHP/MySQL application for a fictitious research organisation in an AWS Academy Learner Lab. My work covered network separation, application fixes, database migration, repeatable instance configuration, security controls and validation within a US$50 lab budget.
This retrospective uses my revised final reports dated 9 and 18 October 2026 and their embedded figures. The images are original lab evidence, cropped and redacted for publication. No new AWS tests were run for this article. Where a screenshot contradicts the report's narrative, the distinction is stated below.
Separate the public entry point from the application and data.
The final design in us-east-1 used an application VPC and a dedicated database VPC. An internet-facing Application Load Balancer accepted HTTP requests and forwarded them to private EC2 web servers. The application reached private RDS over VPC peering, not through the Internet Gateway or NAT Gateway.
- 01 / Public requestBrowserHTTP request to the application
- 02 / Public entryApplication Load BalancerRoutes to the web target group
- 03 / Private application VPCEC2 web tierPrivate subnets in two Availability Zones; Auto Scaling group
- 04 / Peered database VPCRDS MySQLPrivate database subnets; TCP 3306

The request path was browser → ALB → private EC2 → VPC peering → RDS. The bastion host provided a separate, restricted administrative path. This distinction matters: the ALB was the public application entry point, but the bastion also had a public address for authorised SSH.
| Layer | Addressing and placement |
|---|---|
| Application VPC | 10.0.0.0/16; public subnets 10.0.1.0/24 and 10.0.2.0/24; private application subnets 10.0.11.0/24 and 10.0.12.0/24. |
| Database VPC | 10.1.0.0/16; private subnets 10.1.1.0/24 and 10.1.2.0/24, with no Internet Gateway or NAT Gateway. |
A peering connection was only part of connectivity.
I added a route for 10.1.0.0/16 in the application private route table and a return route for 10.0.0.0/16 in the database route table. The non-overlapping CIDRs made the separation explicit. Both private application subnets shared the intended private routing rather than receiving public IPv4 addresses.

The NAT Gateway served outbound access from private application instances. The more specific database route bypassed it. I chose one NAT Gateway rather than one per Availability Zone to control cost. That leaves an outbound-connectivity dependency on one AZ; it does not make the lab a fully redundant production design.
For the database network, I completed the supplied CloudFormation template with the VPC CIDR, two private subnets, explicit route-table associations and an RDS DB subnet group. The report records the stack reaching CREATE_COMPLETE. This was infrastructure as code for the database network, not a claim that the entire application environment was automated.
Turn the working server into a reusable web tier.
I first configured a temporary public EC2 instance using Amazon Linux 2023, Apache, PHP 8.2, the AWS SDK for PHP and MySQL client packages. That made it easier to isolate application and database problems before adding the load balancer and private instances. The initial public server was not the final application tier.
Once the supplied application worked, I created WebServer-App-AMI and used it in a launch template. The template selected a t2.micro instance, the web security group and the Academy instance profile, with public IPv4 assignment disabled. Auto Scaling selected the two private application subnets rather than the template fixing one subnet.
The ALB target group used HTTP on port 80 and a health-check path of /index.php. Attaching the target group to the Auto Scaling group connected instance replacement with load-balancer registration. The normal configuration was minimum two, desired two and maximum four instances, with a 50% average-CPU target and 120-second warm-up.
Move the database without losing the application's connection to it.
I imported the supplied SQL dump into an initial private RDS instance in the application VPC. The report records countrydata_final containing 214 rows. I then created a manual snapshot and restored it as countriesdb-final using the subnet group in the dedicated database VPC.

After the restore, the report records another count of 214 rows and successful PHP queries before the initial RDS instance was deleted. The retained snapshot screenshot supports the migration method; the post-restore count is a documented report result, not a new query performed for this portfolio.


/query2.php at an ALB hostname returning country, population and urban-population rows. The hostname's unique identifier is concealed. This supports a successful browser query in that capture, not continuous availability or independent proof of the database's internal route. Open for full resolution.Two code problems made this more than a console exercise. The life-expectancy page used session variables instead of the connection variables populated by the shared parameter loader. I aligned its connection with the other query pages. After migration, list-based resource selection could choose the wrong database while old and new resources coexisted. I changed the lookup to request countriesdb-final explicitly, then obtained its endpoint and RDS-managed secret ARN from that same response.
I also removed supplied code that wrote the database password to the application error log. Using Secrets Manager does not protect a password if the application subsequently logs it; tracing the credential all the way through the code was an important part of this fix.
Verify the intended traffic, not just a successful page.
The web security group accepted HTTP from the ALB security group and SSH from the bastion security group. Bastion SSH was restricted to an authorised public /32. Database ingress allowed TCP 3306 from the two private application subnet CIDRs. I intended to use a web security-group reference across VPCs, but the report records that option as unavailable in this lab configuration; the CIDR rules were the implemented fallback, not an AWS-wide limitation.
The EC2 instances used the supplied LabInstanceProfile/LabRole, and the PHP application retrieved RDS-managed credentials through Secrets Manager. These were lab controls, not a custom least-privilege IAM design or a completed security audit.

I enabled Flow Logs for the database VPC and inspected the files delivered to S3 after generating queries. The records show accepted MySQL traffic from both private application subnet ranges. They do not name the peering connection. The peering interpretation comes from combining those records with the configured forward and return routes, rather than treating an ACCEPT record as proof of the whole architecture.
Demonstrate the policy, without overstating performance.
The lightweight PHP application did not reach the normal 50% CPU target during the initial short tests. I temporarily lowered the target to 2% and used concurrent ApacheBench requests against the ALB. The report gives a run configured with 200 concurrent requests and a 600-second time limit, with multiple processes used to increase load.

This demonstrated automatic scale-out under the adjusted test policy. It was not a benchmark of maximum throughput or proof that the normal 50% policy met a production service objective. The report states that the target was restored to 50% and capacity settings to minimum two, desired two and maximum four after the demonstration.
Replacement activity and request errors are separate observations.
The report describes terminating an Auto Scaling web instance and running a loop against the ALB's /index.php page. Its prose describes continued HTTP 200 responses. The retained request trace is more qualified: most visible samples are 200, but it also contains a 502 at 12:07:53 and a 504 at 12:07:59. The available evidence does not establish a shared timeline linking those responses to the termination event.

The root cause of either error is unknown; the trace does not show whether they were caused by instance termination, target registration or another factor. The loop's sample times are not a continuous availability measurement. A stronger follow-up test would capture request latency, ALB access logs and target-health transitions against a synchronised termination timeline.

The report states that two healthy targets were restored. The retained activity history supports automatic replacement, but does not independently show the replacement passing the target group's health check. I therefore separate the observed successful launch from the report's final-health statement. The test documents replacement activity and a request trace containing errors; it does not establish uninterrupted service or what caused those errors.
The checks between layers mattered most.
The main lesson was to troubleshoot across boundaries: an application bug could resemble a database problem, working database credentials could still be exposed by logging, and an active peering connection could remain unusable without routes and ingress rules. Capturing CLI output and activity history helped me distinguish configuration from observed behaviour.
The environment remained an academic deployment. It used HTTP rather than HTTPS, one NAT Gateway, small lab instances and the Academy-provided IAM role. Multi-AZ RDS was configured, but no direct RDS failover test is documented. Resources were stopped or recreated between sessions to manage cost, so the reports are historical evidence rather than a claim of an always-running service.
The 18 October report adds a browser query capture through the final ALB, but Figure 20 still duplicates an ALB configuration screenshot instead of showing target-group health. A future validation pass should capture final target health, investigate the 502/504 responses against a synchronised timeline, exercise database failover explicitly and evaluate TLS, least-privilege IAM and outbound redundancy before any production-readiness claim.
Sources: original 2016ICT revised final reports dated 9 and 18 October 2026, including their embedded diagrams and console captures. Nine selected figures are reproduced here with publication-safe crops/redactions. The full reports and raw materials are not published. Read the evidence notes and limitations.