Networking and VPC — Examples
Example 1 — A private subnet that routes to the internet
Two subnets sit in one virtual network: the load balancer’s and the application’s. The second was named “private” at creation, but the table below is what actually routes. Read the table, then declare the association that makes the name true.
subnet intended route table default route public?
lb-a public rt-public igw yes
app-a private rt-public igw yes
Solution
resource "aws_route_table" "private" {
vpc_id = "<vpc-id>"
# no route to the internet gateway: outbound goes via the translation gateway
}
resource "aws_route_table_association" "app_a" {
subnet_id = "<app-subnet-a>"
route_table_id = aws_route_table.private.id
}
A subnet is public because its route table sends unrouted traffic to the gateway, not because of the label it was given, so moving the association is the whole fix and the application keeps outbound access through the translation gateway. The usual mistake is to trust the name from creation and never re-read the route table after someone attached a new one.
Example 2 — A database port opened to an address range
The application tier must reach the database on its port and nothing else must. The table is the intended access, written as addresses because that was quick. Rewrite it as rules that still hold when an instance is replaced and its address changes.
from to port reason
10.0.0.0/16 db 5432 the application subnet
0.0.0.0/0 lb 443 the public service
Solution
resource "aws_security_group" "db" {
# default deny: only the rule below is allowed in
}
resource "aws_security_group_rule" "db_from_app" {
type = "ingress"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_group_id = aws_security_group.db.id
source_security_group_id = "<app-group>"
}
Referencing the application group rather than its address range means a replaced instance is still allowed and a machine outside the group is not, so a temporary opening never has to be remembered and closed. Administering these hosts through a bastion or a session manager rather than a public address keeps the same surface small. The usual mistake is to add an address range for a day of testing; it survives the deploy, its owner and reason are forgotten, and every subnet can reach the database.