Global outage event
If you're experiencing issues with your AWS services, then please refer to the AWS Health Dashboard. You can find the overall status of ongoing outages, the health of AWS services, and the latest updates from AWS engineers.
Active/Standby Centralized Inspection Using VPC Route Server and Transit Gateway
This article demonstrates how to build a centralized multi-VPC inspection architecture using AWS Transit Gateway and Amazon VPC Route Server, where BGP and AS-path prepending enable automatic active/standby failover of inspection appliances for both east-west and north-south traffic — without static routes or manual intervention.
Overview
In the blog post Dynamic routing using Amazon VPC Route Server, we explored how Amazon VPC Route Server enables dynamic BGP-based routing within a VPC. That post covered two scenarios: floating IP failover for application high availability (Scenario 1) and VPC ingress traffic inspection through a firewall with IGW route table integration (Scenario 2).
In this article, we extend those patterns to a centralized multi-VPC inspection architecture using AWS Transit Gateway. This is a common enterprise pattern where multiple spoke VPCs route traffic through a shared inspection VPC for security enforcement. By combining Transit Gateway with VPC Route Server, inspection appliances can dynamically attract traffic via BGP — enabling automatic active/standby failover without static routes or manual intervention.
This pattern supports both east-west traffic (spoke-to-spoke communication routed through inspection) and north-south traffic (spoke-to-external resources routed through inspection), making it suitable for centralized security enforcement across your AWS network.
In this walk-through, we deploy:
- Two spoke VPCs that send all traffic through a Transit Gateway to a dedicated inspection VPC
- Two inspection instances (one per AZ) running GoBGP, peered with a VPC Route Server
- Active/standby routing via AS-path prepending — AZ1 is preferred, AZ2 is standby
- Automatic failover when the active instance's BGP session drops, with automatic failback on recovery
When to use this pattern
As noted in the original blog post, AWS recommends Gateway Load Balancer (GWLB) as the first choice for high availability and redundancy with inspection appliances. However, there are scenarios where GWLB may not be suitable:
- Your appliance does not support GWLB. GWLB requires appliances to support the GENEVE protocol for traffic encapsulation.
- You need active/standby rather than active/active. GWLB distributes traffic across a target group (active/active). If your requirement is to perform stateful inspection with a single active instance and a warm standby, GWLB's load-balancing model doesn't fit.
- You need fine-grained routing control. BGP gives you control over path selection using standard attributes (AS-path, MED), which may be required for specific compliance or operational requirements.
If any of these apply, the VPC Route Server approach described here provides an alternative path to dynamic, automated failover for centralized inspection.
Architecture
The following diagram shows the centralized inspection architecture. Traffic from spoke VPCs traverses the Transit Gateway and enters the inspection VPC, where the VPC Route Server dynamically steers it to the active inspection instance.
Traffic flows
This architecture supports two traffic patterns through the same inspection path:
East-west (spoke-to-spoke): When an instance in Spoke1 communicates with an instance in Spoke2, traffic follows this path:
Spoke1 instance → TGW (spokes RT: 0/0 → inspection attachment)
→ Inspection VPC TGW subnet → Route Server selects AZ1 (shorter AS-path)
→ AZ1 inspects and forwards → TGW (inspection RT: propagated spoke routes)
→ Spoke2 instance
North-south (spoke-to-internet): When a spoke instance sends traffic to the internet, the same inspection path is used. The inspection VPC has an Internet Gateway and public subnets, so after inspection, traffic can be forwarded to the internet:
Spoke instance → TGW (spokes RT: 0/0 → inspection attachment)
→ Inspection VPC TGW subnet → Route Server selects AZ1
→ AZ1 inspects and forwards → Internet Gateway → Internet
In both cases, the Transit Gateway's spokes route table sends all traffic (0.0.0.0/0) to the inspection VPC attachment. Inside the inspection VPC, the route server propagates the preferred route into the TGW subnet route table, directing traffic to the active inspection instance's ENI.
How Active/Standby Works
Both inspection instances advertise 10.0.0.0/8 (optionally can advertise 0.0.0.0/0 to cater for internet traffic) to the route server via BGP. The difference is in the AS-path:
| Instance | AS-Path | Effect |
|---|---|---|
| AZ1 (active) | 65550 | Shorter path — preferred by route server |
| AZ2 (standby) | 65550 65550 | Prepended path — used only when AZ1 is unavailable |
The route server propagates the preferred route (AZ1) into the VPC route table. If AZ1's BGP session drops, the route server automatically updates the route table to point to AZ2.
Prerequisites
- An AWS account with permissions to create VPCs, EC2 instances, Transit Gateways, and VPC Route Servers
- AWS CLI configured, or access to the AWS Management Console
- Familiarity with BGP concepts (ASN, AS-path, peering)
- VPC Route Server available in your target region
Walkthrough
The following steps deploy the complete architecture using a CloudFormation template, then verify BGP peering, route propagation, end-to-end connectivity, and failover behavior. We use GoBGP as a lightweight BGP speaker on the inspection instances. In production, you would replace GoBGP with your preferred network virtual appliance (for example, a firewall vendor appliance that supports BGP).
Step 1: Deploy the CloudFormation Stack
Save the template below and deploy it:
AWSTemplateFormatVersion: '2010-09-09'
Description: Centralized Inspection Architecture with Transit Gateway and VPC Route Server using GoBGP
Parameters:
LatestAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64
Resources:
# ==========================================
# Transit Gateway
# ==========================================
TransitGateway:
Type: AWS::EC2::TransitGateway
Properties:
Description: Transit Gateway for centralized inspection
DefaultRouteTableAssociation: disable
DefaultRouteTablePropagation: disable
Tags:
- Key: Name
Value: TGW-Route-server
TGWRouteTableSpokes:
Type: AWS::EC2::TransitGatewayRouteTable
Properties:
TransitGatewayId: !Ref TransitGateway
Tags:
- Key: Name
Value: spokes
TGWRouteTableInspection:
Type: AWS::EC2::TransitGatewayRouteTable
Properties:
TransitGatewayId: !Ref TransitGateway
Tags:
- Key: Name
Value: inspection
# Default route in spokes route table to inspection attachment
SpokesDefaultRoute:
Type: AWS::EC2::TransitGatewayRoute
DependsOn: InspectionTGWAttachment
Properties:
TransitGatewayRouteTableId: !Ref TGWRouteTableSpokes
DestinationCidrBlock: 0.0.0.0/0
TransitGatewayAttachmentId: !Ref InspectionTGWAttachment
# ==========================================
# Internet Gateway
# ==========================================
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: inspection-igw
# ==========================================
# Spoke1 VPC
# ==========================================
Spoke1VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.45.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: spoke1
InstanceSubnetSpoke1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref Spoke1VPC
CidrBlock: 10.45.1.0/24
AvailabilityZone: !Select [0, !GetAZs '']
Tags:
- Key: Name
Value: instance-subnet-spoke1
TgwSubnetSpoke1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref Spoke1VPC
CidrBlock: 10.45.2.0/24
AvailabilityZone: !Select [0, !GetAZs '']
Tags:
- Key: Name
Value: tgw-subnet-spoke1
Spoke1RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref Spoke1VPC
Tags:
- Key: Name
Value: instance-route-table-spoke1
Spoke1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref InstanceSubnetSpoke1
RouteTableId: !Ref Spoke1RouteTable
Spoke1DefaultRoute:
Type: AWS::EC2::Route
DependsOn: Spoke1TGWAttachment
Properties:
RouteTableId: !Ref Spoke1RouteTable
DestinationCidrBlock: 0.0.0.0/0
TransitGatewayId: !Ref TransitGateway
Spoke1TGWAttachment:
Type: AWS::EC2::TransitGatewayAttachment
DependsOn: TransitGateway
Properties:
TransitGatewayId: !Ref TransitGateway
VpcId: !Ref Spoke1VPC
SubnetIds:
- !Ref TgwSubnetSpoke1
Tags:
- Key: Name
Value: spoke1-attachment
Spoke1TGWAssociation:
Type: AWS::EC2::TransitGatewayRouteTableAssociation
Properties:
TransitGatewayAttachmentId: !Ref Spoke1TGWAttachment
TransitGatewayRouteTableId: !Ref TGWRouteTableSpokes
Spoke1TGWPropagation:
Type: AWS::EC2::TransitGatewayRouteTablePropagation
Properties:
TransitGatewayAttachmentId: !Ref Spoke1TGWAttachment
TransitGatewayRouteTableId: !Ref TGWRouteTableInspection
Spoke1EICEndpoint:
Type: AWS::EC2::InstanceConnectEndpoint
Properties:
SubnetId: !Ref TgwSubnetSpoke1
Tags:
- Key: Name
Value: eic-endpoint-spoke1
Spoke1SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for spoke1 instance
VpcId: !Ref Spoke1VPC
SecurityGroupIngress:
- IpProtocol: -1
CidrIp: 10.0.0.0/8
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: spoke1-sg
# ==========================================
# Spoke2 VPC
# ==========================================
Spoke2VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.46.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: spoke2
InstanceSubnetSpoke2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref Spoke2VPC
CidrBlock: 10.46.1.0/24
AvailabilityZone: !Select [0, !GetAZs '']
Tags:
- Key: Name
Value: instance-subnet-spoke2
TgwSubnetSpoke2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref Spoke2VPC
CidrBlock: 10.46.2.0/24
AvailabilityZone: !Select [0, !GetAZs '']
Tags:
- Key: Name
Value: tgw-subnet-spoke2
Spoke2RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref Spoke2VPC
Tags:
- Key: Name
Value: instance-route-table-spoke2
Spoke2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref InstanceSubnetSpoke2
RouteTableId: !Ref Spoke2RouteTable
Spoke2DefaultRoute:
Type: AWS::EC2::Route
DependsOn: Spoke2TGWAttachment
Properties:
RouteTableId: !Ref Spoke2RouteTable
DestinationCidrBlock: 0.0.0.0/0
TransitGatewayId: !Ref TransitGateway
Spoke2TGWAttachment:
Type: AWS::EC2::TransitGatewayAttachment
DependsOn: TransitGateway
Properties:
TransitGatewayId: !Ref TransitGateway
VpcId: !Ref Spoke2VPC
SubnetIds:
- !Ref TgwSubnetSpoke2
Tags:
- Key: Name
Value: spoke2-attachment
Spoke2TGWAssociation:
Type: AWS::EC2::TransitGatewayRouteTableAssociation
Properties:
TransitGatewayAttachmentId: !Ref Spoke2TGWAttachment
TransitGatewayRouteTableId: !Ref TGWRouteTableSpokes
Spoke2TGWPropagation:
Type: AWS::EC2::TransitGatewayRouteTablePropagation
Properties:
TransitGatewayAttachmentId: !Ref Spoke2TGWAttachment
TransitGatewayRouteTableId: !Ref TGWRouteTableInspection
Spoke2EICEndpoint:
Type: AWS::EC2::InstanceConnectEndpoint
Properties:
SubnetId: !Ref TgwSubnetSpoke2
Tags:
- Key: Name
Value: eic-endpoint-spoke2
Spoke2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for spoke2 instance
VpcId: !Ref Spoke2VPC
SecurityGroupIngress:
- IpProtocol: -1
CidrIp: 10.0.0.0/8
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: spoke2-sg
# ==========================================
# Inspection VPC
# ==========================================
InspectionVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.47.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: inspection-VPC
IGWAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref InspectionVPC
InternetGatewayId: !Ref InternetGateway
# Private subnets for TGW attachment
TgwSubnetInspectionAZ1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref InspectionVPC
CidrBlock: 10.47.1.0/24
AvailabilityZone: !Select [0, !GetAZs '']
Tags:
- Key: Name
Value: tgw-subnet-inspection-az1
TgwSubnetInspectionAZ2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref InspectionVPC
CidrBlock: 10.47.2.0/24
AvailabilityZone: !Select [1, !GetAZs '']
Tags:
- Key: Name
Value: tgw-subnet-inspection-az2
# Public subnets
PublicSubnetInspectionAZ1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref InspectionVPC
CidrBlock: 10.47.3.0/24
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: public-subnet-inspection-az1
PublicSubnetInspectionAZ2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref InspectionVPC
CidrBlock: 10.47.4.0/24
AvailabilityZone: !Select [1, !GetAZs '']
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: public-subnet-inspection-az2
# TGW Subnet Route Table
InspectionTGWSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref InspectionVPC
Tags:
- Key: Name
Value: inspection-tgw-subnet-route-table
InspectionTGWSubnetAZ1Association:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref TgwSubnetInspectionAZ1
RouteTableId: !Ref InspectionTGWSubnetRouteTable
InspectionTGWSubnetAZ2Association:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref TgwSubnetInspectionAZ2
RouteTableId: !Ref InspectionTGWSubnetRouteTable
# Public Subnet Route Table
InspectionPublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref InspectionVPC
Tags:
- Key: Name
Value: public-route-table-inspection
PublicSubnetAZ1Association:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetInspectionAZ1
RouteTableId: !Ref InspectionPublicRouteTable
PublicSubnetAZ2Association:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetInspectionAZ2
RouteTableId: !Ref InspectionPublicRouteTable
PublicDefaultRoute:
Type: AWS::EC2::Route
DependsOn: IGWAttachment
Properties:
RouteTableId: !Ref InspectionPublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicRFC1918Route:
Type: AWS::EC2::Route
DependsOn: InspectionTGWAttachment
Properties:
RouteTableId: !Ref InspectionPublicRouteTable
DestinationCidrBlock: 10.0.0.0/8
TransitGatewayId: !Ref TransitGateway
# Inspection TGW Attachment
InspectionTGWAttachment:
Type: AWS::EC2::TransitGatewayAttachment
DependsOn: TransitGateway
Properties:
TransitGatewayId: !Ref TransitGateway
VpcId: !Ref InspectionVPC
SubnetIds:
- !Ref TgwSubnetInspectionAZ1
- !Ref TgwSubnetInspectionAZ2
Tags:
- Key: Name
Value: inspection-attachment
InspectionTGWAssociation:
Type: AWS::EC2::TransitGatewayRouteTableAssociation
Properties:
TransitGatewayAttachmentId: !Ref InspectionTGWAttachment
TransitGatewayRouteTableId: !Ref TGWRouteTableInspection
# Inspection EC2 Instance Connect Endpoint
InspectionEICEndpoint:
Type: AWS::EC2::InstanceConnectEndpoint
Properties:
SubnetId: !Ref TgwSubnetInspectionAZ1
Tags:
- Key: Name
Value: eic-endpoint-inspection
# ==========================================
# Security Group for Inspection Instances
# ==========================================
InspectionSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for inspection instances
VpcId: !Ref InspectionVPC
SecurityGroupIngress:
- IpProtocol: -1
CidrIp: 10.0.0.0/8
- IpProtocol: -1
CidrIp: 172.16.0.0/12
- IpProtocol: -1
CidrIp: 192.168.0.0/16
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: inspection-sg
# ==========================================
# EC2 Instances in Inspection VPC
# ==========================================
InspectionInstanceAZ1:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: t3.micro
SubnetId: !Ref PublicSubnetInspectionAZ1
SecurityGroupIds:
- !Ref InspectionSecurityGroup
SourceDestCheck: false
Tags:
- Key: Name
Value: inspection-instance-az1
UserData:
Fn::Base64:
Fn::Sub:
- |
#!/bin/bash
set -e
endpointIP=${endpointIP}
# Enable IP forwarding
echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf
sysctl -p
# Install GoBGP
cd /tmp
wget https://github.com/osrg/gobgp/releases/download/v3.20.0/gobgp_3.20.0_linux_amd64.tar.gz
tar -xzf gobgp_3.20.0_linux_amd64.tar.gz
mv gobgp gobgpd /usr/local/bin/
chmod +x /usr/local/bin/gobgp*
# Create config directory
mkdir -p /etc/gobgp
# Get instance IP
INSTANCE_IP=$(hostname -I | awk '{print $1}')
# Create GoBGP configuration
cat > /etc/gobgp/gobgpd.conf << EOF
[global.config]
as = 65550
router-id = "$INSTANCE_IP"
[[neighbors]]
[neighbors.config]
neighbor-address = "$endpointIP"
peer-as = 65500
[[neighbors.afi-safis]]
[neighbors.afi-safis.config]
afi-safi-name = "ipv4-unicast"
EOF
# Create route advertisement script
cat > /usr/local/bin/gobgp-advertise.sh << 'EOF'
#!/bin/bash
sleep 15
/usr/local/bin/gobgp global rib add 10.0.0.0/8
EOF
chmod +x /usr/local/bin/gobgp-advertise.sh
# Create systemd service for GoBGP
cat > /etc/systemd/system/gobgpd.service << 'EOF'
[Unit]
Description=GoBGP Daemon
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/gobgpd -f /etc/gobgp/gobgpd.conf
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
# Create systemd service for route advertisement
cat > /etc/systemd/system/gobgp-advertise.service << 'EOF'
[Unit]
Description=GoBGP Route Advertisement
After=gobgpd.service
Requires=gobgpd.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/gobgp-advertise.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
# Start services
systemctl daemon-reload
systemctl enable gobgpd
systemctl enable gobgp-advertise
systemctl start gobgpd
systemctl start gobgp-advertise
- endpointIP: !GetAtt RouteServerEndpointAZ1.EniAddress
InspectionInstanceAZ2:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: t3.micro
SubnetId: !Ref PublicSubnetInspectionAZ2
SecurityGroupIds:
- !Ref InspectionSecurityGroup
SourceDestCheck: false
Tags:
- Key: Name
Value: inspection-instance-az2
UserData:
Fn::Base64:
Fn::Sub:
- |
#!/bin/bash
set -e
endpointIP=${endpointIP}
# Enable IP forwarding
echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf
sysctl -p
# Install GoBGP
cd /tmp
wget https://github.com/osrg/gobgp/releases/download/v3.20.0/gobgp_3.20.0_linux_amd64.tar.gz
tar -xzf gobgp_3.20.0_linux_amd64.tar.gz
mv gobgp gobgpd /usr/local/bin/
chmod +x /usr/local/bin/gobgp*
# Create config directory
mkdir -p /etc/gobgp
# Get instance IP
INSTANCE_IP=$(hostname -I | awk '{print $1}')
# Create GoBGP configuration
cat > /etc/gobgp/gobgpd.conf << EOF
[global.config]
as = 65550
router-id = "$INSTANCE_IP"
[[neighbors]]
[neighbors.config]
neighbor-address = "$endpointIP"
peer-as = 65500
[[neighbors.afi-safis]]
[neighbors.afi-safis.config]
afi-safi-name = "ipv4-unicast"
EOF
# Create route advertisement script with AS-path prepending
cat > /usr/local/bin/gobgp-advertise.sh << 'EOF'
#!/bin/bash
sleep 15
/usr/local/bin/gobgp global rib add 10.0.0.0/8 aspath "65550 65550" -a ipv4
EOF
chmod +x /usr/local/bin/gobgp-advertise.sh
# Create systemd service for GoBGP
cat > /etc/systemd/system/gobgpd.service << 'EOF'
[Unit]
Description=GoBGP Daemon
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/gobgpd -f /etc/gobgp/gobgpd.conf
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
# Create systemd service for route advertisement
cat > /etc/systemd/system/gobgp-advertise.service << 'EOF'
[Unit]
Description=GoBGP Route Advertisement
After=gobgpd.service
Requires=gobgpd.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/gobgp-advertise.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
# Start services
systemctl daemon-reload
systemctl enable gobgpd
systemctl enable gobgp-advertise
systemctl start gobgpd
systemctl start gobgp-advertise
- endpointIP: !GetAtt RouteServerEndpointAZ2.EniAddress
# ==========================================
# EC2 Instance in Spoke1 VPC
# ==========================================
Spoke1Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: t3.micro
SubnetId: !Ref InstanceSubnetSpoke1
SecurityGroupIds:
- !Ref Spoke1SecurityGroup
Tags:
- Key: Name
Value: spoke1-instance
# ==========================================
# EC2 Instance in Spoke2 VPC
# ==========================================
Spoke2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: t3.micro
SubnetId: !Ref InstanceSubnetSpoke2
SecurityGroupIds:
- !Ref Spoke2SecurityGroup
Tags:
- Key: Name
Value: spoke2-instance
# ==========================================
# VPC Route Server
# ==========================================
RouteServer:
Type: AWS::EC2::RouteServer
Properties:
AmazonSideAsn: 65500
Tags:
- Key: Name
Value: inspection-route-server
RouteServerAssociation:
Type: AWS::EC2::RouteServerAssociation
Properties:
RouteServerId: !Ref RouteServer
VpcId: !Ref InspectionVPC
RouteServerEndpointAZ1:
Type: AWS::EC2::RouteServerEndpoint
DependsOn: RouteServerAssociation
Properties:
RouteServerId: !Ref RouteServer
SubnetId: !Ref PublicSubnetInspectionAZ1
Tags:
- Key: Name
Value: rs-endpoint-az1
RouteServerEndpointAZ2:
Type: AWS::EC2::RouteServerEndpoint
DependsOn: RouteServerAssociation
Properties:
RouteServerId: !Ref RouteServer
SubnetId: !Ref PublicSubnetInspectionAZ2
Tags:
- Key: Name
Value: rs-endpoint-az2
RouteServerPeer1:
Type: AWS::EC2::RouteServerPeer
DependsOn:
- RouteServerEndpointAZ1
- RouteServerEndpointAZ2
- InspectionInstanceAZ1
Properties:
RouteServerEndpointId: !Ref RouteServerEndpointAZ1
PeerAddress: !GetAtt InspectionInstanceAZ1.PrivateIp
BgpOptions:
PeerAsn: 65550
Tags:
- Key: Name
Value: rs-peer-az1
RouteServerPeer2:
Type: AWS::EC2::RouteServerPeer
DependsOn:
- RouteServerEndpointAZ1
- RouteServerEndpointAZ2
- InspectionInstanceAZ2
Properties:
RouteServerEndpointId: !Ref RouteServerEndpointAZ2
PeerAddress: !GetAtt InspectionInstanceAZ2.PrivateIp
BgpOptions:
PeerAsn: 65550
Tags:
- Key: Name
Value: rs-peer-az2
RouteServerPropagation:
Type: AWS::EC2::RouteServerPropagation
DependsOn: RouteServerAssociation
Properties:
RouteServerId: !Ref RouteServer
RouteTableId: !Ref InspectionTGWSubnetRouteTable
Outputs:
TransitGatewayId:
Value: !Ref TransitGateway
Spoke1VPCId:
Value: !Ref Spoke1VPC
Spoke2VPCId:
Value: !Ref Spoke2VPC
InspectionVPCId:
Value: !Ref InspectionVPC
RouteServerId:
Value: !Ref RouteServer
InspectionInstance1PrivateIP:
Value: !GetAtt InspectionInstanceAZ1.PrivateIp
InspectionInstance2PrivateIP:
Value: !GetAtt InspectionInstanceAZ2.PrivateIp
- Navigate to the AWS CloudFormation console in your desired region
- Create a new stack by uploading the template and providing a stack name
The template deploys:
- Transit Gateway with two route tables:
spokes(default route to inspection VPC) andinspection(propagated spoke routes) - Spoke1 VPC (10.45.0.0/16) and Spoke2 VPC (10.46.0.0/16) — each with an EC2 instance and TGW attachment
- Inspection VPC (10.47.0.0/16) with:
- Two inspection EC2 instances running GoBGP (one per AZ)
- A VPC Route Server (ASN 65500) with endpoints in each AZ
- BGP peering between each inspection instance and its local route server endpoint
- Route server propagation enabled on the TGW subnet route table
- EC2 Instance Connect Endpoints in each Spoke VPC for private SSH access
Step 2: Verify BGP Peering
Connect to the AZ1 inspection instance using EC2 Instance Connect endpoint:
Check BGP neighbor status:
/usr/local/bin/gobgp neighbor
You should see the route server endpoint as an established BGP neighbor:
Peer AS Up/Down State |#Received Accepted
<rs-endpoint-ip> 65500 00:05:00 Establ | 0 0
Check advertised routes:
/usr/local/bin/gobgp global rib
Expected output:
Network Next Hop AS_PATH Age
*> 10.0.0.0/8 <instance-ip> 65550 00:05:00
Repeat for the AZ2 instance — you should see the same route but with AS-path 65550 65550.
Step 3: Verify Route Propagation
Check the inspection VPC's TGW subnet route table. The route server should have propagated a route for 10.0.0.0/8 pointing to the AZ1 inspection instance's ENI (the active path):
You should see the route pointing to the AZ1 instance's network interface.
Step 4: Test End-to-End Connectivity
East-west test (spoke-to-spoke): Connect to the spoke1 instance and ping the spoke2 instance:
[ec2-user@ip-10-45-1-205 ~]$ ping 10.46.1.48
PING 10.46.1.48 (10.46.1.48) 56(84) bytes of data.
64 bytes from 10.46.1.48: icmp_seq=1 ttl=124 time=2.09 ms
64 bytes from 10.46.1.48: icmp_seq=2 ttl=124 time=1.75 ms
64 bytes from 10.46.1.48: icmp_seq=3 ttl=124 time=1.03 ms
[ec2-user@ip-10-45-1-205 ~]$ traceroute 10.46.1.48
traceroute to 10.46.1.48 (10.46.1.48), 30 hops max, 60 byte packets
1 * * *
2 ip-10-47-3-42.us-west-2.compute.internal (10.47.3.42) 1.084 ms 1.077 ms 0.884 ms < -------- Use traceroute to confirm traffic is routed through the appliance in AZ 1
3 * * *
4 ip-10-46-1-48.us-west-2.compute.internal (10.46.1.48) 1.939 ms 1.928 ms *
Step 5: Test Failover
Stop the AZ1 inspection instance to simulate a failure:
aws ec2 stop-instances --instance-ids <inspection-instance-az1-id>
The BGP session between AZ1 and the route server will drop. The route server detects this via BGP keepalive timeout and updates the VPC route table to point to the AZ2 instance (the standby path with the prepended AS-path). As discussed in the original blog post, enabling BFD can significantly reduce this detection time to sub-second convergence.
Verify the route table has updated:
The route should now point to the AZ2 instance's network interface.
Test connectivity again from spoke1 — traffic should still flow, now via AZ2:
[ec2-user@ip-10-45-1-205 ~]$ ping 10.46.1.48
PING 10.46.1.48 (10.46.1.48) 56(84) bytes of data.
64 bytes from 10.46.1.48: icmp_seq=1 ttl=124 time=2.57 ms
64 bytes from 10.46.1.48: icmp_seq=2 ttl=124 time=1.84 ms
64 bytes from 10.46.1.48: icmp_seq=3 ttl=124 time=1.68 ms
[ec2-user@ip-10-45-1-205 ~]$ traceroute 10.46.1.48
traceroute to 10.46.1.48 (10.46.1.48), 30 hops max, 60 byte packets
1 * * *
2 ip-10-47-4-229.us-west-2.compute.internal (10.47.4.229) 1.523 ms 1.511 ms 1.603 ms < -------- Use traceroute to confirm traffic is now routed through the appliance in AZ 2
3 * * *
4 ip-10-46-1-48.us-west-2.compute.internal (10.46.1.48) 2.418 ms 2.452 ms 2.594 ms
Start the AZ1 instance again to restore the active path:
aws ec2 start-instances --instance-ids <inspection-instance-az1-id>
Once GoBGP re-establishes the BGP session, the route server will prefer AZ1 again (shorter AS-path) and update the route table automatically.
Cleanup
Delete the CloudFormation stack to remove all resources:
aws cloudformation delete-stack \ --stack-name <Stack-Name> \ --region us-east-1
Conclusion
In the original blog post, we demonstrated how Amazon VPC Route Server enables dynamic routing for floating IP failover and VPC ingress inspection. In this article, we extended those patterns to a centralized multi-VPC inspection architecture using Transit Gateway. The implementation of the VPC Router Server components remains the same if AWS CloudWAN is in use for either East-West and North-South connectivity.
By combining Transit Gateway with VPC Route Server and BGP AS-path prepending, traffic from multiple spoke VPCs is dynamically steered to the preferred inspection instance — covering both east-west (spoke-to-spoke) and north-south (spoke-to-internet) flows. When the active instance fails, the route server automatically shifts traffic to the standby, and shifts it back on recovery. No static routes or manual intervention required.
This pattern is particularly useful when Gateway Load Balancer is not an option — for example, when your appliance doesn't support GENEVE, or when you need active/standby rather than active/active failover. It can be extended to support additional spoke VPCs, more complex routing policies, or integration with commercial firewall vendors that support BGP.
For more details on VPC Route Server concepts and getting started, refer to the VPC Route Server documentation and the Getting Started tutorial.
- Language
- English
