Provision an AWS VPC and Security Group with Terraform
Putting an instance in its own VPC means four Terraform resources: a VPC, a subnet, a security group, and the instance wired to all three. Here's the setup that opens only port 80 to the world.
The pieces
- VPC - your private network (a CIDR block like
10.0.0.0/16). - Subnet - a slice of the VPC where the instance lives (
10.0.1.0/24). - Security group - a stateful firewall on the instance; here, allow inbound
80. - Instance - wired to the subnet + security group.
The Terraform
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = { Name = "web-vpc" }
}
resource "aws_subnet" "main" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
}
resource "aws_security_group" "web" {
name = "web"
vpc_id = aws_vpc.main.id
ingress { # only port 80, from anywhere
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress { # allow all outbound
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.default.id
instance_type = "t3.micro"
subnet_id = aws_subnet.main.id # in the subnet
vpc_security_group_ids = [aws_security_group.web.id] # using the SG
}
The details that matter
- Security groups are stateful - allow inbound 80 and the response goes back out automatically; you don't need a matching egress rule for replies.
- Wire the instance to both -
subnet_idandvpc_security_group_ids. Miss the SG and it falls back to the default group (often wrong). - Least privilege ingress - only the ports you actually serve. Don't open
22to0.0.0.0/0; scope SSH to your IP or use SSM. - Resources reference each other by id (
aws_vpc.main.id), which is how Terraform orders creation - VPC before subnet before instance.
Security group rules and common mistakes
A security group is stateful: if you allow an inbound request, the response is automatically allowed back out - no matching egress rule needed. Each rule lists ports plus a source, and the source can be a CIDR (cidr_blocks = ["10.0.0.0/16"]) or another security group id, which is the cleaner way to let one tier talk to another.
The mistakes that bite:
- Opening SSH to the world -
0.0.0.0/0on port 22 invites brute force. Scope it to your IP or a bastion. - Forgetting egress - the
aws_security_groupresource has no egress unless you add one, so the instance cannot reach the internet. Add an egress rule if it needs outbound. - Confusing it with a NACL - security groups are per-instance and stateful; network ACLs are per-subnet and stateless. Use security groups for app-level access control.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Declaring a VPC + subnet + security group in Terraform
- Opening only the ports you serve (least privilege)
- Wiring an instance to its subnet + security group
FAQ
How do I create a VPC and security group in Terraform?
Declare aws_vpc (a CIDR like 10.0.0.0/16), an aws_subnet inside it, an aws_security_group with the ingress rules you want, then set the instance's subnet_id and vpc_security_group_ids to wire it to both.
How do I open only one port in an AWS security group?
Add a single ingress block for that port (e.g. from_port/to_port 80, protocol tcp, cidr_blocks 0.0.0.0/0). Security groups deny by default, so only the ports you list are open.
Do I need an egress rule for responses?
No - security groups are stateful, so the response to an allowed inbound request is automatically permitted. Egress rules only govern connections the instance initiates.
Does a security group need to be associated with a VPC?
Yes. A security group always belongs to a specific VPC and can only attach to resources in that same VPC - you cannot share one across VPCs.
What is the difference between a VPC and a security group?
A VPC is the isolated virtual network (subnets, routing); a security group is a stateful virtual firewall attached to resources inside that VPC, controlling inbound and outbound traffic at the instance level.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →