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 twice: a route more specific than the VPC CIDR wins by longest prefix match, and the local route's target can be replaced outright. The first 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. The first is not true at all, in two separate ways.
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 can add a more specific route that takes precedence over the local route 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.
What a route table will not accept is a second route for 10.0.0.0/16 sitting next to the local one, because a route table holds one route per destination prefix. That is a property of route tables, not a protection around the local route — and it is where "cannot be overridden" comes from. The local route itself is editable: AWS documents replacing or restoring its target, and the CLI is replace-route --destination-cidr-block 10.0.0.0/16 --network-interface-id eni-…, which points the whole VPC range at an appliance. A VPC with multiple CIDR blocks has one local route per block and each can be replaced independently.
The more specific route is still the better tool for inspecting traffic between two subnets, because it is scoped to the pair you mean rather than to everything. But the reason to prefer it is blast radius, not permission.
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 local route for 10.0.0.0/16 is left alone here, and it does not need touching: a route for 10.0.2.0/24 is longer, so it wins for that subnet. Both arrows are required. With only the first, the reply follows subnet B's own table straight back and the appliance sees half a conversation, which a stateful device drops — a request that arrives and a reply that never comes. The appliance's own subnet is the third table, and it must not carry the redirect.
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.