journalaws / vpc

*Configuring an AWS Site-to-Site VPN

May 26, 2026·22 min read
Architecture diagram for Configuring an AWS Site-to-Site VPN

Summary

In this lab I set up an AWS Site-to-Site VPN between an AWS VPC and an on-premises data center. I didn't have a physical data center, so I simulated one with a second VPC in a different region. On the AWS side, I created a VPC with a private subnet and an instance, EC2-A, plus a Virtual Private Gateway. On the data center side, I created a VPC with a public subnet holding a VPN-Server instance and a private subnet holding an application server, EC2-B. I created a Customer Gateway pointing to VPN-Server's public IP and a Site-to-Site VPN connection using static routing. Then I installed LibreSwan on VPN-Server, configured it from the AWS VPN configuration file, and started the IPsec tunnel. After updating the route tables on both sides and turning off VPN-Server's source/destination check so it could act as a router, EC2-B could ping EC2-A across the VPN using only private IP addresses.

Architecture

AWS sideSimulated data center
RegionCanada (Central) ca-central-1US East (N. Virginia) us-east-1
VPCAWS-VPC, 10.0.0.0/16DC-VPC, 192.168.0.0/16
SubnetsPrivate subnet 10.0.0.0/24, ca-central-1aPublic subnet 192.168.0.0/24, private subnet 192.168.100.0/24
InstancesEC2-A (private, no public IP)VPN-Server (public), EC2-B (private, no public IP)
Internet accessNoneInternet Gateway for the public subnet
VPN endpointVirtual Private GatewayVPN-Server running LibreSwan
VPN resourceDetails
Virtual Private Gateway (VGW)Attached to AWS-VPC
Customer Gateway (CGW)IP address = VPN-Server's public IP
Site-to-Site VPN connectionVGW ↔ CGW, static routing, static prefix 192.168.0.0/16

Traffic path from EC2-B to EC2-A:

EC2-B (192.168.100.x)
  → DC private route table: 10.0.0.0/16 → VPN-Server
  → VPN-Server (LibreSwan encrypts the packet)
  → IPsec tunnel over the internet
  → Virtual Private Gateway
  → EC2-A (10.0.0.x)

Part 1: Building the AWS Network

1. Created AWS-VPC

I switched the console to Canada (Central), opened the VPC console, and created a VPC named AWS-VPC with the CIDR block 10.0.0.0/16. This VPC represented the cloud side of the connection.

2. Created the private subnet

I created a subnet in AWS-VPC with the CIDR block 10.0.0.0/24 in ca-central-1a. It represented internal AWS resources, so it didn't need an Internet Gateway or any public access.

3. Created a route table for the subnet

I created a route table in AWS-VPC, then opened its Subnet associations tab, clicked Edit subnet associations, selected the private subnet, and saved. I used a dedicated route table so I could add a route to the VPN later without touching the VPC's main route table.

4. Launched EC2-A

I launched an instance named EC2-A with these settings:

SettingValue
AMIAmazon Linux
VPC / SubnetAWS-VPC / the private subnet
Auto-assign public IPDisable

Security group inbound rules:

TypeProtocolPortSourceReason
All ICMP - IPv4ICMPAll192.168.0.0/16Lets EC2-A answer pings from anywhere in DC-VPC (VPN-Server and EC2-B). Traffic through the VPN keeps its original private address, so the source is DC-VPC's private range.

That was the only traffic EC2-A would accept. It didn't need an SSH rule, because it had no public IP and AWS-VPC had no Internet Gateway, so nothing on the internet could reach it anyway. I noted its private IPv4 address for testing later.


Part 2: Building the Simulated Data Center

1. Created DC-VPC

I switched the console to US East (N. Virginia) and created a VPC named DC-VPC with the CIDR block 192.168.0.0/16. This VPC played the role of the company's on-premises network. Its range didn't overlap with AWS-VPC's 10.0.0.0/16, which was required so each side could route to the other.

2. Created and attached an Internet Gateway

I created an Internet Gateway and attached it to DC-VPC. The VPN server needed internet access, because the IPsec tunnel to AWS travels over the public internet.

3. Created two subnets

SubnetCIDRPurpose
Public subnet192.168.0.0/24Holds VPN-Server, the data center's VPN router
Private subnet192.168.100.0/24Holds EC2-B, an internal application server

4. Created route tables

Route tableAssociated withRoutes
Public route tablePublic subnet192.168.0.0/16 → local, 0.0.0.0/0 → Internet Gateway
Private route tablePrivate subnet192.168.0.0/16 → local

5. Launched VPN-Server

SettingValue
AMIAmazon Linux
VPC / SubnetDC-VPC / public subnet
Auto-assign public IPEnable

Security group inbound rules:

TypeProtocolPortSourceReason
SSHTCP220.0.0.0/0Lets me SSH in from my computer's public IP to install and configure LibreSwan.
All ICMP - IPv4ICMPAll0.0.0.0/0Lets me ping VPN-Server to check it's reachable, and lets EC2-B's pings to AWS-VPC pass through it. As a router, VPN-Server's security group checks all traffic passing through it, not just traffic meant for it.

No rules were needed for the IPsec tunnel (UDP 500 and 4500), because VPN-Server starts the tunnel itself and the security group automatically allows AWS's replies.

I noted its public IP, since AWS would need it to know where the data center's end of the VPN was.

6. Launched EC2-B

SettingValue
AMIAmazon Linux
VPC / SubnetDC-VPC / private subnet
Auto-assign public IPDisable

Security group inbound rules:

TypeProtocolPortSourceReason
SSHTCP22192.168.0.0/16EC2-B has no public IP, so I SSH in from VPN-Server. That connection comes from VPN-Server's private IP inside DC-VPC, not from my computer.
All ICMP - IPv4ICMPAll10.0.0.0/16Lets EC2-A ping EC2-B across the VPN. Not needed for EC2-B pinging EC2-A, since replies to connections EC2-B starts are allowed automatically.

Part 3: Configuring the AWS VPN Components

1. Created the Virtual Private Gateway

Back in Canada (Central), I went to Virtual private gateways in the VPC console, created one, and then clicked Actions → Attach to VPC and selected AWS-VPC.

A Virtual Private Gateway (VGW) is the AWS end of a VPN connection. It's attached to a VPC much like an Internet Gateway, but it only carries VPN traffic.

2. Created the Customer Gateway

Under Customer gateways, I created one and set its IP address to VPN-Server's public IP.

A Customer Gateway (CGW) is AWS's record of the device on the customer's side of the VPN. In a real company, it would be a physical router or firewall. Here, it was VPN-Server.

3. Created the Site-to-Site VPN connection

Under Site-to-Site VPN connections, I clicked Create VPN connection and set:

SettingValue
Target gateway typeVirtual private gateway (the one attached to AWS-VPC)
Customer gatewayExisting (the one pointing to VPN-Server)
Routing optionsStatic
Static IP prefixes192.168.0.0/16

Static routing meant I told AWS directly which address range was on the data center side. The alternative, dynamic routing, uses the BGP protocol to exchange routes automatically, and needs a router that supports BGP.

AWS created the connection with two tunnels, each with its own AWS public IP, so the VPN can stay up if one tunnel fails.

4. Downloaded the configuration file

Once the VPN connection's state was Available, I clicked Download configuration and chose the Openswan format. LibreSwan is based on Openswan, so it uses the same configuration style. The file contained the tunnel IP addresses, the pre-shared keys, and step-by-step settings for the VPN server.


Part 4: Configuring LibreSwan on VPN-Server

1. Installed LibreSwan

I connected to VPN-Server over SSH and installed LibreSwan, the software that creates the IPsec tunnel:

sudo dnf update -y
sudo dnf install libreswan -y

2. Turned on packet forwarding

I opened the system settings file:

sudo nano /etc/sysctl.conf

and added these lines at the end:

net.ipv4.ip_forward = 1
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.default.accept_source_route = 0
  • ip_forward = 1 let the server pass packets between networks, so it could act as a router instead of only handling its own traffic.
  • rp_filter = 0 turned off a check that would drop packets arriving on an unexpected interface, which can happen with tunneled traffic.
  • accept_source_route = 0 kept a safety setting that rejects packets trying to choose their own route.

I applied the changes without restarting:

sudo sysctl -p

The command printed the three settings back, confirming they were active.

3. Enabled LibreSwan's extra configuration folder

I opened /etc/ipsec.conf and made sure this line near the bottom wasn't commented out:

include /etc/ipsec.d/*.conf

This told LibreSwan to load any connection files placed in /etc/ipsec.d/, which is where I put the AWS settings.

4. Created the tunnel configuration

I created the connection file:

sudo nano /etc/ipsec.d/aws.conf

and pasted the Tunnel 1 section from the downloaded configuration file, with a few changes:

conn Tunnel1
	authby=secret
	auto=start
	left=%defaultroute
	leftid=VPN_SERVER_PUBLIC_IP
	right=AWS_TUNNEL1_PUBLIC_IP
	type=tunnel
	ikelifetime=8h
	keylife=1h
	phase2alg=aes128-sha256
	ike=aes128-sha256;modp2048
	keyingtries=%forever
	keyexchange=ike
	leftsubnet=192.168.0.0/16
	rightsubnet=10.0.0.0/16
	dpddelay=10
	dpdtimeout=30
	dpdaction=restart_by_peer
SettingWhat it means
left / leftidThe data center side: VPN-Server and its public IP
rightThe AWS side: the public IP of Tunnel 1
leftsubnetThe data center's network, DC-VPC's CIDR
rightsubnetThe AWS network, AWS-VPC's CIDR
phase2alg / ikeUpdated to the stronger encryption settings AWS recommends
auto=startBring the tunnel up automatically when IPsec starts
dpd... settingsDead peer detection: restart the tunnel if AWS stops responding

I also removed the auth=esp line from the downloaded file, since newer LibreSwan versions don't accept it.

5. Added the pre-shared key

I created the secrets file:

sudo nano /etc/ipsec.d/aws.secrets

and pasted the line from step 5 of the configuration file, in this format:

VPN_SERVER_PUBLIC_IP AWS_TUNNEL1_PUBLIC_IP: PSK "PRE_SHARED_KEY"

The pre-shared key is a password both ends of the tunnel use to prove who they are.

6. Started IPsec

sudo systemctl start ipsec.service
sudo systemctl status ipsec.service

The status showed active (running). A running service doesn't guarantee the tunnel works, though, so I tested connectivity next.


Part 5: Routing Traffic Through the VPN

1. First ping test

From VPN-Server, I pinged EC2-A's private IP:

ping EC2_A_PRIVATE_IP

It got no replies. The tunnel could carry traffic to AWS, but EC2-A's route table had no route back to 192.168.0.0/16, so its replies had nowhere to go.

2. Added a route on the AWS side

In Canada (Central), I opened the private subnet's route table in AWS-VPC, clicked Edit routes, and added:

DestinationTarget
192.168.0.0/16Virtual Private Gateway

I ran the ping again, and this time EC2-A replied. In the VPN connection's Tunnel details tab, Tunnel 1 now showed Up. Tunnel 2 stayed Down, because I had only configured Tunnel 1 on VPN-Server.

3. Turned off Source/Destination Check on VPN-Server

At this point, only VPN-Server itself could reach EC2-A. For EC2-B to use the VPN, VPN-Server had to forward other instances' traffic.

By default, EC2 drops any traffic passing through an instance that isn't addressed to or from that instance. That's the Source/Destination Check. In US East (N. Virginia), I selected VPN-Server, clicked Actions → Networking → Change source/destination check, checked Stop, and saved.

4. Added a route on the data center side

I opened DC-VPC's private route table, clicked Edit routes, and added:

DestinationTarget
10.0.0.0/16Instance → VPN-Server

Now any traffic from the private subnet headed for AWS-VPC would go to VPN-Server, which would send it through the tunnel.


Part 6: Verifying End-to-End Connectivity

1. Connected to EC2-B through VPN-Server

EC2-B had no public IP, so I reached it through VPN-Server. On VPN-Server, I created a key file:

nano key.pem

pasted in the contents of EC2-B's private key, saved it, and locked down its permissions:

chmod 400 key.pem

Then I connected to EC2-B using its private IP:

ssh -i key.pem ec2-user@EC2_B_PRIVATE_IP

2. Pinged EC2-A from EC2-B

From EC2-B, I pinged EC2-A's private IP:

ping EC2_A_PRIVATE_IP

EC2-A replied. Traffic from a private server in the simulated data center was traveling through VPN-Server, across the encrypted IPsec tunnel, through the Virtual Private Gateway, and into a private subnet in a different region, without either private instance having a public IP.


Why the Lab Was Set Up This Way

Why VPN-Server needed a public IP

The VPN tunnel runs over the public internet, so both ends needed an address the other end could reach. AWS's tunnel endpoints already had public IPs. To build the tunnel, VPN-Server and AWS exchanged packets across the internet, first to agree on encryption keys (IKE) and then to carry the encrypted traffic (IPsec). The customer gateway told AWS where to send its side of that conversation, and the only thing it stored was VPN-Server's public IP. Without one, the tunnel could never have formed.

Only VPN-Server needed a public IP. EC2-A and EC2-B stayed private, because their traffic traveled inside the encrypted tunnel between the two public endpoints. That's why VPN-Server was in the public subnet with a route to the Internet Gateway, while EC2-B stayed in the private subnet.

The public IP from Auto-assign public IP changes whenever an instance is stopped and started. If that had happened, the customer gateway would have kept pointing to the old address and the tunnel would have broken. In a real setup, VPN-Server would get an Elastic IP so its address never changes, the same way a physical VPN router in a data center gets a static public IP from its internet provider.

What VPN-Server did

VPN-Server was the data center's gateway for all traffic to and from AWS, in both directions.

DirectionWhat happened
AWS → data centerThe packet arrived encrypted through the tunnel. LibreSwan decrypted it, saw it was for EC2-B (192.168.100.x), and VPN-Server forwarded it over DC-VPC's local network.
Data center → AWSEC2-B sent a packet to 10.0.0.x. The private route table's 10.0.0.0/16 → VPN-Server route sent it to VPN-Server, which encrypted it and sent it through the tunnel.

That gave it two jobs at once:

  • VPN endpoint: encrypting and decrypting traffic at the data center's end of the tunnel.
  • Router: passing traffic between the tunnel and the servers behind it.

Both jobs depended on IP forwarding being turned on in Linux and the Source/Destination Check being turned off in EC2. Replies took the same path in reverse, which is why the private route table's route back to VPN-Server was just as important as the route to AWS. On the AWS side, the Virtual Private Gateway played the matching role.

Where the AWS VPN resources lived

The customer gateway was created in Canada (Central), even though VPN-Server was in N. Virginia. A customer gateway isn't a device that runs anywhere. It's a record on the AWS side that describes the other end of the VPN. Since the VPN connection links the virtual private gateway to the customer gateway, and AWS only links resources in the same region, all three AWS VPN resources had to be in Canada (Central) with AWS-VPC:

  • Virtual private gateway
  • Customer gateway
  • Site-to-Site VPN connection

Nothing VPN-related was created in N. Virginia. That side was only DC-VPC, its subnets, and VPN-Server running LibreSwan, standing in for a real data center that AWS wouldn't otherwise know about.

Which IPs went where

I never set an IP for the virtual private gateway. AWS assigned the public IPs to the VPN connection's tunnels, shown as Outside IP address on the connection's Tunnel details tab and in the downloaded configuration file.

IPBelongs toUsed in
Tunnel 1 outside IPAWS's end of the VPNright= in aws.conf, and aws.secrets
VPN-Server's public IPThe data center's endThe customer gateway, leftid= in aws.conf, and aws.secrets
Inside tunnel IPs (169.254.x.x)The inside of the tunnelOnly needed for dynamic (BGP) routing, so not used here

Each tunnel has its own pre-shared key, so the key in aws.secrets had to come from the same tunnel as the IP.

Why VPN-Server's rules allowed SSH and ICMP from anywhere

VPN-Server had two inbound rules, each covering a different kind of traffic reaching it:

Rule on VPN-ServerAllowsNeeded for
SSH from 0.0.0.0/0Logins from the internetMe setting up the VPN from my computer
All ICMP - IPv4 from 0.0.0.0/0Pings from anywhereTesting VPN-Server, and letting EC2-B's pings pass through it

The SSH rule. I set up the whole VPN by SSHing into VPN-Server from my computer: installing LibreSwan, editing sysctl.conf and aws.conf, and starting IPsec. That connection came from my home public IP, so the rule had to allow a source on the internet. 0.0.0.0/0 worked but was wider than necessary. Setting the source type to My IP would have allowed only my computer.

The ICMP rule. This rule did two jobs:

  1. Pinging VPN-Server itself. I could ping its public IP from my computer to check it was reachable.
  2. Letting EC2-B's pings pass through. When EC2-B pinged EC2-A, the packet went to VPN-Server first, before it was encrypted. It arrived at VPN-Server's network interface as an ordinary ICMP packet from 192.168.100.x, and the security group checked it like any other inbound traffic. Without this rule, the ping would have been dropped at VPN-Server before it ever reached the tunnel.

Because VPN-Server acted as a router, its security group applied to all traffic passing through it, not just traffic meant for it. In a real setup, if EC2-B also needed HTTPS or SSH to reach EC2-A, VPN-Server's security group would need inbound rules for those protocols from DC-VPC too.

Why no rules for the tunnel itself. IPsec uses UDP ports 500 and 4500, but VPN-Server didn't need inbound rules for them. It started the tunnel itself (auto=start), so AWS's replies were allowed automatically by the stateful security group. Those rules would only be needed if AWS had to start the connection.

A tighter version of VPN-Server's rules:

TypePortSourceWhy
SSH22My IPMe managing the server
All ICMP - IPv4All192.168.0.0/16DC-VPC servers' traffic passing through to AWS
All ICMP - IPv4All10.0.0.0/16Pings from AWS-VPC arriving at VPN-Server itself

Why EC2-A only allowed ICMP from 192.168.0.0/16

EC2-A's single inbound rule, All ICMP - IPv4 from 192.168.0.0/16, let it answer pings from anywhere in DC-VPC, covering both VPN-Server (192.168.0.x) and EC2-B (192.168.100.x), and nothing else.

The source had to be a private range because traffic through a VPN keeps its original address. When EC2-B pinged EC2-A, the packet was encrypted, sent through the tunnel, and decrypted at the virtual private gateway, but its source stayed EC2-B's private IP the whole way. Unlike an Internet Gateway or NAT Gateway, nothing translated it.

The lab only needed ICMP because ping was the simplest way to prove the VPN worked. In a real setup, data center servers would need to reach EC2-A for actual work, so I'd add one rule for each kind of traffic it should accept, as narrowly as possible:

NeedTypePortSource
Web app or API on EC2-AHTTPS443192.168.100.0/24, the app servers' subnet
Admins managing EC2-ASSH22A specific admin machine, e.g. 192.168.100.50/32
EC2-A as a MySQL databaseMYSQL/Aurora3306Only the app servers that use it
Connectivity checksAll ICMP - IPv4All192.168.0.0/16

The VPN itself doesn't restrict any traffic. Once the tunnel and routes are in place, the security groups (and any custom network ACLs) on each side decide exactly what's allowed across it. If EC2-A also needed to start connections to the data center, EC2-B's security group would need matching inbound rules too.

Why EC2-B's rules allowed SSH from inside DC-VPC

EC2-B had two inbound rules, each letting in one kind of traffic from one specific place:

Rule on EC2-BAllowsNeeded for
SSH from 192.168.0.0/16Logins from inside DC-VPCMe reaching EC2-B through VPN-Server
All ICMP - IPv4 from 10.0.0.0/16Pings from AWS-VPCEC2-A pinging EC2-B across the VPN

The SSH rule. EC2-B had no public IP, so I couldn't SSH into it from my computer. VPN-Server was the only machine in DC-VPC reachable from the internet, so I logged in to it first and connected to EC2-B from there. That second connection came from VPN-Server's private IP (192.168.0.x), not from my computer's public IP, so the SSH rule had to allow a source inside DC-VPC. For that hop, VPN-Server was acting as a bastion host as well as the VPN router.

192.168.0.0/16 was wider than necessary, since it allowed SSH from anywhere in DC-VPC. A tighter rule would allow only the public subnet (192.168.0.0/24), or only VPN-Server's private IP with /32.

The ICMP rule. 10.0.0.0/16 is AWS-VPC's range, so this rule let EC2-A ping EC2-B. Like EC2-A's rule, its source was a private range because traffic through the VPN keeps its original address.

This rule wasn't what made my test work, though. In the lab, EC2-B pinged EC2-A. Since EC2-B started that connection, EC2-A needed an inbound rule to accept it, but the replies coming back to EC2-B were allowed automatically because security groups are stateful. EC2-B's ICMP rule only matters when the ping goes the other way, from EC2-A to EC2-B, so it makes the test work in both directions.

Why I tested from EC2-B instead of VPN-Server

SSHing into EC2-B was how I got a terminal on it so I could run the real test from there.

Pinging EC2-A from VPN-Server only proved the tunnel worked, since VPN-Server was the end of the tunnel itself. The point of a Site-to-Site VPN is for ordinary servers in the data center to reach AWS, and EC2-B played that role. When its ping succeeded, it proved the whole chain worked:

  1. EC2-B's route table sent AWS-bound traffic to VPN-Server.
  2. VPN-Server forwarded it, which depended on IP forwarding being on and the Source/Destination Check being off.
  3. The tunnel carried it to AWS.
  4. The replies found their way back along the same path.

If any of those steps had been missing, VPN-Server's own ping would still have worked, but EC2-B's would have failed.

Copying the key onto VPN-Server wasn't the only way to reach EC2-B. An SSH jump from my computer would have avoided putting the key on the server:

ssh -i KEY_NAME.pem -J ec2-user@VPN_SERVER_PUBLIC_IP ec2-user@EC2_B_PRIVATE_IP

An EC2 Instance Connect Endpoint in DC-VPC would also have worked, like the one I used to reach the database server in the private subnet lab.

Why a VPN instead of VPC peering

The simulated data center was really just a second VPC, so in practice the two would be connected with VPC peering: it's simpler, needs no VPN server, stays on AWS's network, and has no hourly charge. The lab used a Site-to-Site VPN because it was standing in for a network outside AWS, like an office or data center, which can't be peered with.


What I Learned

  • An AWS Site-to-Site VPN connects a VPC to an on-premises network through an encrypted IPsec tunnel over the public internet.
  • The AWS end is a Virtual Private Gateway, attached to the VPC. The customer end is described to AWS as a Customer Gateway, which points to the public IP of the customer's VPN device.
  • Every VPN connection has two tunnels to different AWS endpoints for redundancy. In production, both would be configured so the connection survives if one goes down.
  • Static routing means listing the remote network's CIDR by hand. Dynamic routing uses BGP to share routes automatically.
  • The two networks need non-overlapping CIDR blocks (10.0.0.0/16 and 192.168.0.0/16), or routing between them wouldn't work.
  • A working tunnel isn't enough. Both sides need routes: AWS-side traffic to 192.168.0.0/16 must go to the VGW, and data-center-side traffic to 10.0.0.0/16 must go to the VPN device.
  • An EC2 instance acting as a router needs IP forwarding turned on in Linux and its Source/Destination Check turned off in EC2. Otherwise it drops traffic that isn't addressed to itself.
  • A route table entry can target an EC2 instance, which is how the private subnet's traffic was sent to VPN-Server.
  • Compared with VPC peering, a Site-to-Site VPN is for connecting to networks outside AWS, like an office or data center. Peering connects two VPCs over AWS's own network without encryption overhead.

Notes

  • VPN-Server started the tunnel itself (auto=start), so its security group didn't need inbound rules for IPsec. If AWS had to start the connection, the security group would need to allow UDP 500 and UDP 4500 (IKE and IPsec NAT traversal) from the tunnel IPs.
  • Copying EC2-B's private key onto VPN-Server was fine for a lab, but it's risky in practice. SSH's jump host feature (ssh -J) would avoid putting the key on the server.
  • The downloaded configuration file and aws.secrets contain the pre-shared keys. They shouldn't be shared, screenshotted into documents, or committed to a repository.

Cleanup

This lab created resources in two regions, and several of them are billed by the hour: the Site-to-Site VPN connection, all three EC2 instances, and VPN-Server's public IPv4 address. I deleted everything in this order:

1. Deleted the VPN connection (Canada Central)

Under Site-to-Site VPN connections, I selected the connection, clicked Actions → Delete VPN connection, and confirmed. This is the most important step, since the VPN connection is billed every hour it exists, whether or not traffic is flowing.

2. Deleted the Customer Gateway (Canada Central)

Once the VPN connection showed Deleted, I went to Customer gateways, selected the gateway, and clicked Actions → Delete customer gateway.

3. Detached and deleted the Virtual Private Gateway (Canada Central)

Under Virtual private gateways, I selected the gateway, clicked Actions → Detach from VPC, waited for it to show Detached, then clicked Actions → Delete virtual private gateway.

4. Terminated the instances

In Canada (Central), I terminated EC2-A. In US East (N. Virginia), I terminated VPN-Server and EC2-B. I waited until all three showed Terminated.

5. Deleted the security groups

In each region, I deleted the security groups created for the instances.

6. Deleted the data center network (N. Virginia)

  1. Detached the Internet Gateway from DC-VPC and deleted it.
  2. Deleted the public and private subnets.
  3. Deleted the two custom route tables.
  4. Deleted DC-VPC.

7. Deleted the AWS network (Canada Central)

I deleted the private subnet, its custom route table, and AWS-VPC.

8. Confirmed nothing was left

In both regions, I checked that Site-to-Site VPN connections, Virtual private gateways, Customer gateways, Instances, and Your VPCs didn't show anything from this lab. I made sure not to delete the default VPC in either region.