You can route around the local route, and that is how intra-VPC inspection works
"The local route always wins and cannot be overridden" is the version most people learn. It is wrong in a specific and useful way: a route more specific than the VPC CIDR wins by longest prefix match, which is what makes inspecting traffic between two subnets possible.
· Venerable Networks
Two things are commonly said about the local route in a VPC route table, and they contradict each other. Both get repeated.
The first: the local route always wins and cannot be overridden. The second: you can insert an appliance between two subnets in the same VPC.
The second is true, and the first is true only for the exact VPC CIDR.
The actual rule
Route selection is longest prefix match first. The local route covers the VPC's CIDR — say 10.0.0.0/16. A route for 10.0.2.0/24 is more specific, so it wins for traffic to that subnet.
You cannot replace the 10.0.0.0/16 local route. You can add a more specific route that takes precedence over it for part of that space. AWS documents this as the intra-VPC routing or middlebox pattern, and the mechanism is exactly that: the route destination is a subnet CIDR, not the VPC CIDR, and the target is the appliance's network interface.
So this is legal and does what it looks like:
# In the route table associated with subnet A
resource "aws_route" "inspect_b" {
route_table_id = aws_route_table.subnet_a.id
destination_cidr_block = "10.0.2.0/24" # subnet B, more specific than the VPC CIDR
network_interface_id = aws_network_interface.appliance.id
}Traffic from subnet A to subnet B now goes to the appliance instead of directly.
The details that make or break it
You need the route in both directions. A route in subnet A's table sends A-to-B traffic to the appliance. Return traffic from B to A follows subnet B's route table, which needs the mirror route. One direction only gives you asymmetric routing through a stateful device, which drops it.
The appliance must not do a source/destination check. An EC2 instance drops packets not addressed to it by default. source_dest_check = false is required, and forgetting it produces a total blackhole rather than a partial failure — which is at least easy to spot.
The appliance's own subnet must not have the redirect route. If the appliance sits in a subnet whose route table sends the destination prefix back to the appliance, you have built a loop. In practice this means the appliance's subnet uses a different route table from the ones being inspected, and getting that separation wrong is the most common way these designs fail.
Cross-AZ traffic costs money and adds latency. An appliance in one Availability Zone inspecting traffic between subnets in another means every packet crosses zones twice. For a design that is meant to be transparent, that is a real and recurring bill.
When this is the wrong tool
Intra-VPC inspection via routes is fiddly, and there are usually better options:
Security groups, when the requirement is allow or deny rather than inspection. They are stateful, need no return-path rule, and can reference each other. If the actual requirement is "these subnets should not talk", a security group is the correct answer and route manipulation is not.
Gateway Load Balancer, when you need real appliance capacity. It handles flow stickiness, health checking, and horizontal scaling, which a single ENI target does not. A single appliance ENI is a single point of failure carrying all inspected traffic.
Separate VPCs with a Transit Gateway, when the boundary is genuinely a trust boundary. Subnets in one VPC are a weak boundary — anyone who can modify a route table can remove the inspection. If the separation matters, put it where the blast radius of a route table change is smaller.
Worth knowing that security group referencing across a Transit Gateway does not work through a Gateway Load Balancer or Network Firewall, so choosing inspection has knock-on effects on how you can write rules.
Why the misconception is worth correcting
"The local route cannot be overridden" leads people to conclude that traffic between two subnets in one VPC cannot be inspected, and from there to a design where every workload that needs separation gets its own VPC. That is a much larger architecture, with attachment costs, address planning, and cross-VPC DNS, adopted to work around a constraint that is not real.
The accurate version — the local route wins for the VPC CIDR, and a more specific route wins for anything narrower — leaves the choice open, and then it is a genuine tradeoff between subnet-level inspection and VPC-level separation rather than a forced move.
The check
If you inherit a VPC with intra-VPC routing, the thing to verify is symmetry, because that is what breaks:
aws ec2 describe-route-tables --filters "Name=vpc-id,Values=$VPC" \
--query 'RouteTables[].{rtb:RouteTableId,
subnets:Associations[?SubnetId].SubnetId,
eniRoutes:Routes[?NetworkInterfaceId!=null].[DestinationCidrBlock,NetworkInterfaceId]}' \
--output jsonFor every route pointing at an appliance ENI, there should be a matching route in the destination subnet's table pointing back. A missing mirror is a stateful appliance seeing half a conversation, which presents as a request that arrives and a reply that never comes.
The VPC guide covers route evaluation and the local route in full. For what asymmetric routing through a stateful boundary actually looks like from both ends, the NACL return path lab is the cheapest way to see it — the mechanism is different but the shape of the failure, and the way it hides from both hosts, is the same.