How to Secure AWS Ingress and Egress with Fortinet FortiGate and GWLB
Deploy FortiGate firewalls with AWS Gateway Load Balancer for transparent ingress and egress traffic inspection
Prerequisites
- AWS account with VPC networking experience
- Familiarity with Transit Gateway and route table manipulation
- FortiGate AMI subscription from AWS Marketplace
- FortiManager license for centralized policy management
- Terraform 1.5+ installed
Last year I helped a financial services company migrate their on-premises FortiGate firewalls to AWS. They wanted the same level of inspection for both inbound and outbound traffic. The challenge: AWS doesn’t have a traditional firewall insertion model. We needed Gateway Load Balancer (GWLB) to transparently intercept traffic at scale — and the architectural decisions we made fundamentally changed their cloud security posture.
Choose Your Architecture Pattern
You have two options. Pick based on your org structure and risk tolerance.
Centralized — A dedicated security VPC hosts all FortiGate instances and GWLB. Workload VPCs connect via Transit Gateway. One set of firewalls for everything.
Distributed — Each workload VPC gets its own GWLB endpoint and FortiGate instances. Each team manages their own security posture.
Centralized architecture with security VPC and Transit Gateway
| Pattern | Pros | Cons |
|---|---|---|
| Centralized | Unified policy, lower cost, easier auditing | Potential bottleneck, single security VPC |
| Distributed | Blast radius isolation, low latency, team autonomy | Higher cost, policy drift risk, more overhead |
Deploy GWLB with FortiGate
resource "aws_lb" "gwlb" {
name = "fortigate-gwlb"
internal = true
load_balancer_type = "gateway"
subnets = aws_subnet.security[*].id
}
resource "aws_lb_target_group" "fortigate" {
name = "fortigate-targets"
port = 6081
protocol = "GENEVE"
vpc_id = aws_vpc.security.id
target_type = "instance"
health_check {
interval = 10
port = "65500"
protocol = "TCP"
}
}
resource "aws_lb_listener" "fortigate" {
load_balancer_arn = aws_lb.gwlb.arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.fortigate.arn
}
}
Launch FortiGate with Auto Scaling
resource "aws_launch_template" "fortigate" {
name = "fortigate-launch-template"
image_id = var.fortigate_ami_id
instance_type = "c5.xlarge"
user_data = base64encode(templatefile("${path.module}/fortigate_bootstrap.txt", {
hostname = "fortigate-instance"
admin_password = random_password.fortigate_admin.result
}))
}
resource "aws_autoscaling_group" "fortigate" {
vpc_zone_identifier = aws_subnet.security[*].id
target_group_arns = [aws_lb_target_group.fortigate.arn]
min_size = 2
max_size = 6
desired_capacity = 2
launch_template {
id = aws_launch_template.fortigate.id
version = "$Latest"
}
}
Create GWLB Endpoints in Workload VPCs
resource "aws_vpc_endpoint_service" "gwlb" {
gateway_load_balancer_arns = [aws_lb.gwlb.arn]
acceptance_required = false
}
resource "aws_vpc_endpoint" "gwlb_endpoint" {
service_name = aws_vpc_endpoint_service.gwlb.service_name
vpc_id = aws_vpc.workload.id
vpc_endpoint_type = "GatewayLoadBalancer"
subnet_ids = [aws_subnet.workload_inspection.id]
}
Then update route tables so workload subnets send traffic to the GWLB endpoint instead of directly to the internet gateway or NAT gateway.
Trace the Ingress Flow
- User sends request to ALB public IP
- ALB route table sends traffic to GWLB endpoint
- GWLB encapsulates packet in Geneve (UDP 6081)
- FortiGate decapsulates, inspects (IPS/IDS, threat scan)
- FortiGate re-encapsulates, returns to GWLB
- GWLB forwards to application instance
- Response follows the same symmetric path
The original source and destination IPs stay intact throughout — FortiGate logs show real client IPs for proper threat correlation.
Trace the Egress Flow
- Application sends outbound request
- Workload subnet routes to GWLB endpoint
- GWLB → FortiGate inspects for malware, applies DNS filtering
- FortiGate returns to GWLB → NAT gateway → Internet
- Return traffic re-enters via GWLB for stateful inspection
Critical: Use a dedicated inspection subnet for GWLB endpoints. This simplifies route table logic and prevents accidental traffic bypass.
Connect FortiManager
FortiManager pushes policies to all FortiGate instances simultaneously. During bootstrap, each FortiGate registers with FortiManager via IP and pre-shared key. New auto-scaled instances automatically enroll and pull the latest policies — no manual intervention needed.
Key Takeaway
FortiGate with AWS GWLB gives you transparent, enterprise-grade traffic inspection without changing your application architecture. The Geneve encapsulation preserves original packet headers, auto-scaling handles traffic spikes, and FortiManager keeps policies consistent. Plan your route tables carefully — they’re the difference between inspected and bypassed traffic.
Questions? Connect with me on LinkedIn.