Deploying a Highly Available AWS Application with Bastion Access, Transit Gateway, and Auto Scaling.

Published: (January 19, 2026 at 07:00 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

Introduction

In this project I built a secure, highly‑available AWS architecture that mirrors real‑world production environments. By the end of this guide you will understand how to deploy:

  • A bastion‑managed environment
  • A private, auto‑scaled application layer
  • Secure VPC‑to‑VPC communication using a Transit Gateway
  • End‑to‑end logging and DNS routing

The architecture consists of two VPCs with isolated responsibilities, a Transit Gateway for private communication, public and private subnets across multiple AZs, a bastion host for secure access, an Auto Scaling Group behind a Network Load Balancer, and centralized logging/DNS routing.

VPC Design

VPCPurposeCIDR Block
VPC‑A (bastion‑vpc)Secure administrative access192.168.0.0/16
VPC‑B (app‑vpc)Highly available, private application layer172.32.0.0/16

This separation ensures that administrative access is decoupled from the application environment.

Subnet Layout

Bastion VPC (public only)

SubnetCIDRAZType
Public Subnet192.168.1.0/24us-east-1aPublic

Application VPC

SubnetCIDRAZType
Public Subnet172.32.1.0/24us-east-1aPublic
Private Subnet A172.32.2.0/24us-east-1aPrivate
Private Subnet B172.32.3.0/24us-east-1bPrivate

Private subnets across multiple AZs provide high availability.

Internet Connectivity

  1. Internet Gateways – Create an IGW for each VPC and attach it to the respective VPC.
  2. Public route tables – Add a default route 0.0.0.0/0 → Internet Gateway.

NAT for Private Subnets

  1. Allocate an Elastic IP.
  2. Create a NAT Gateway in the public subnet of the Application VPC.
  3. Update the private route tables: 0.0.0.0/0 → NAT Gateway.

Transit Gateway

  1. Create a Transit Gateway.

  2. Attach both VPCs (Bastion and Application) to the TGW.

  3. In each VPC’s route table, add a route:

    • Destination: CIDR of the other VPC
    • Target: Transit Gateway

This eliminates the need for VPC peering and scales better.

Security Groups

Security GroupInbound RulesOutbound Rules
Bastion SGSSH (22) from your IPAll traffic
Application SGSSH (22) from Bastion SG
HTTP (80) from Load Balancer
All traffic

These rules enforce a least‑privilege network posture.

Bastion Host

Launch an EC2 instance in the Bastion VPC public subnet with:

  • Instance type: t3.micro
  • Security group: Bastion SG
  • Elastic IP attached

This host becomes the sole SSH entry point into the environment.

Golden AMI (Reusable Image)

#!/usr/bin/env bash
sudo apt update -y
sudo apt install -y apache2 git awscli amazon-cloudwatch-agent
sudo systemctl start apache2
sudo systemctl enable apache2

Installed components

  • Apache
  • Git
  • AWS CLI
  • CloudWatch Agent
  • SSM Agent (pre‑installed on Amazon Linux 2)

Stop the instance and create an AMI named golden-ami.

Observability

  1. CloudWatch Log Group/vpc/flowlogs
  2. Enable VPC Flow Logs for both VPCs, targeting CloudWatch with separate log streams per VPC.

IAM Role for EC2

Create an IAM role with:

  • Trusted entity: EC2
  • Managed policy: AmazonSSMManagedInstanceCore
  • Custom inline policy: read‑only access to a specific S3 bucket (restricted bucket access)

Attach this role to all EC2 instances to enable secure instance management via Session Manager without SSH keys.

S3 Bucket for Configuration

  • Create a bucket (e.g., my-app-config-bucket).
  • Enable default encryption.
  • Block all public access.
  • Store application configuration files here.

Application Deployment

Launch Template / User Data

#!/usr/bin/env bash
sudo apt install -y git
git clone  /var/www/html
systemctl restart apache2

Use the Golden AMI with:

  • Instance type: t3.micro
  • Security group: Application SG
  • IAM role: the role created above

Auto Scaling Group

ParameterValue
Min size2
Desired capacity2
Max size4
SubnetsPrivate subnet (us-east-1a) & Private subnet (us-east-1b)
Target typeinstance
Health checkEnabled (EC2)
Instance protectionOptional (to avoid termination during deployments)

Network Load Balancer

  • Scheme: Internet‑facing
  • Listeners: TCP :80 → target group (port 80)
  • Target type: Instance
  • Subnets: Public subnets (172.32.1.0/24 in each AZ)

DNS

Create a Route 53 record:

  • Type: CNAME
  • Name: app.example.com
  • Value: NLB DNS name

Validation

  1. SSH into the Bastion host.
  2. From the Bastion, SSH into a private EC2 instance (or use Session Manager).
  3. Open a browser and navigate to http://app.example.com.

You should see the application loading successfully.

Expected Results

  • Application is reachable via the custom domain.
  • Auto Scaling responds to traffic spikes.
  • VPC Flow Logs and application logs appear in CloudWatch.

Lessons Learned

  • Secure AWS network design using a Transit Gateway vs. VPC peering.
  • Bastion‑based access patterns for hardened environments.
  • Deploying auto‑scaling workloads in private subnets.
  • Implementing observability and production‑ready logging.
Back to Blog

Related posts

Read more »