Library / SDK
terraform-aws-modules/terraform-aws-vpc avatar
terraform-aws-modules/terraform-aws-vpc

terraform-aws-vpc: the AWS VPC module that decides your NAT bill

Terraform module to create AWS VPC resources 🇺🇦

3,269 stars4,627 forksHCLApache-2.0

At a glance

What is it?
terraform-aws-modules/terraform-aws-vpc builds VPCs, subnets, route tables and NAT gateways from one module block. The judgement: its value is in the NAT gateway flags and subnet taxonomy, and those are also where the surprises live.
Who is it for?
Adopt it if you are already on Terraform and want the subnet taxonomy (public, private, database, elasticache, redshift, intra) and NAT gateway modes handled by one block instead of hand-written aws_subnet and aws_route_table resources. Skip it if you need a VPC that does not look like the module's model: a single flat subnet layout, or peering and endpoint wiring you intend to own resource by resource.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 13 days ago.
What is it written in?
Mainly HCL, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem terraform-aws-vpc solves, and who it is actually for

A VPC in AWS is not one resource. It is a VPC, an internet gateway, one or more NAT gateways with Elastic IPs, a public and private route table per availability zone, route table associations, and a set of subnets whose CIDR blocks have to line up with the AZs they live in. Writing that by hand in Terraform is a few hundred lines of near-identical resources before you have deployed a single application.

This module collapses that into one module block. The README's usage example passes name, cidr, azs, private_subnets, public_subnets and two feature flags, and the module works out the rest. The people it is for are platform and infrastructure engineers who have already chosen Terraform and want the AWS network layer to be a parameterised block rather than a directory of hand-maintained resources. It is less useful if you only need one subnet and one route table, because the module's model (AZ-aligned subnet tiers) is more structure than that case needs.

How the module decides how many NAT gateways you pay for

The NAT gateway logic is the part of this module with real financial consequences, and the README documents three modes. The default is one NAT gateway per subnet. The module determines the count from the max() of the private subnet lists: database_subnets, elasticache_subnets, private_subnets and redshift_subnets. The README is explicit that intra_subnets is excluded from that calculation, because intra subnets are designed to have no internet access through NAT.

The README's own worked example makes the arithmetic concrete. With two database subnets, two elasticache subnets, five private subnets, two redshift subnets and three intra subnets, five NAT gateways are created, because five is the largest private subnet list. Setting single_nat_gateway = true sends all private subnet traffic through one gateway placed in the first public subnet. Setting one_nat_gateway_per_az = true with single_nat_gateway = false places one gateway per AZ in var.azs, and the README states two requirements for that mode: var.azs must be specified, and the number of public subnet CIDR blocks must be greater than or equal to the number of AZs, so each gateway has a public subnet to sit in. If both flags are true, the README says single_nat_gateway wins. That precedence is worth knowing before you debug a plan that shows one gateway when you expected three.

private versus intra subnets, and why Lambda sizing pushes you to intra

The module distinguishes private subnets from intra subnets. With NAT gateways enabled, private subnets get routes for internet traffic pointing at the NAT gateways. Intra subnets get no internet routing, which the README frames in terms of RFC1918 Category 1 subnets.

The README's stated use case is AWS Lambda inside a VPC. Lambda allocates Elastic Network Interfaces in proportion to the traffic it receives, so a function under load consumes addresses from its subnet. The README's suggestion follows from that: allocate a large private subnet for those allocations while keeping the traffic internal to the VPC, reaching internal resources or VPC endpoints for AWS services instead of the public internet. If you put a chatty Lambda in a small private subnet, address exhaustion is the failure you will hit, and it will look like a Lambda problem rather than a subnet sizing problem. The intra tier exists so that internal-only workloads do not consume NAT gateway capacity or addresses in the private tier.

Installing terraform-aws-vpc and a first VPC with public and private subnets

The module is consumed through the Terraform registry, so there is no package to install. The README's usage block is the entry point: you add a module block with source = "terraform-aws-modules/vpc/aws" to your configuration and let Terraform fetch it during init.

hcl
module "vpc" {
  source = "terraform-aws-modules/vpc/aws"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

  enable_nat_gateway = true
  enable_vpn_gateway = true

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}

With three private subnets and single_nat_gateway left at its default, the plan will contain three NAT gateways, one per private subnet. If that is not what you want, set the flag before applying, as the README does in its single NAT gateway scenario:

hcl
  enable_nat_gateway = true
  single_nat_gateway = true
  one_nat_gateway_per_az = false

Run terraform plan and read the NAT gateway and Elastic IP resources before you apply. Those are the line items that decide what the VPC costs per hour, and the module's defaults are the expensive end of the range. The repository also ships runnable configurations under examples/, including examples/simple/, examples/complete/, examples/ipv6-dualstack/ and examples/network-acls/, which are the fastest way to see which variables a given topology needs.

Reusing NAT Elastic IPs so a rebuilt VPC keeps the same addresses

By default the module provisions new Elastic IPs for its NAT gateways, so destroying and recreating a VPC releases the addresses. The README describes a way around that: allocate the EIPs outside the module and pass them in, which keeps the IPs alive across a VPC rebuild. This matters when something downstream has allowlisted your egress addresses.

The README allocates three EIPs as a separate resource, then sets reuse_nat_ips = true and external_nat_ip_ids to the list of EIP ids on the module. The count has to match the NAT gateway count. The README notes that with single_nat_gateway = false and three subnets you need three EIPs, while with single_nat_gateway = true one EIP is enough. Getting that count wrong is a plan-time failure, not a silent misconfiguration, but it is easy to hit when you change the NAT mode without revisiting the EIP resource. The aws_eip resource in the README example uses vpc = true.

Conditional creation, RDS public access, and network ACLs

Three smaller features round out the module. The create_vpc argument toggles the whole module, which the README presents as a workaround for Terraform versions before 0.13 where count was not allowed in a module block. On current Terraform that argument is mostly legacy, and using it to switch a VPC on and off in place is a heavier operation than the name suggests.

For public access to RDS instances, which the README explicitly says is not recommended for production, four arguments are needed together: create_database_subnet_group, create_database_subnet_route_table, create_database_internet_gateway_route, and both enable_dns_hostnames and enable_dns_support. The README presents these as a set because they are: the route and the DNS settings have to agree or the instance will not resolve or route as expected.

Network ACL management is opt-in. Once a VPC exists, AWS creates a default network ACL, and the module can manage it with manage_default_network_acl = true. Each subnet type can also get its own ACL with custom rules, for example public_dedicated_network_acl = true for a dedicated public ACL. The README text is cut off mid-sentence at that point, so the full rule syntax is not spelled out in the excerpt; examples/network-acls/ is the place to look for a working configuration.

Where terraform-aws-vpc is the wrong tool

The strongest argument against the module is the one the README makes itself. v6.x still supports creating a VPC Flow Log inside the root module, but the README labels that deprecated behavior and states it will be removed in v7.0.0, directing users to the standalone flow-log module under modules/. If you are on v6.x with a root-level flow log, you are carrying a configuration that has a scheduled removal date, and migrating it means the flow log resource moves from the root module to a separate module block, which changes addresses in state.

The second limitation is the subnet model itself. The module's tiers (public, private, database, elasticache, redshift, intra) and its AZ-aligned layout are an opinion about how a VPC should be shaped. If your network does not fit that shape, you spend your time working around the module rather than using it. Peering, VPC endpoint services and security group rules are separate resources regardless; the module does not remove that work, and the related searches for terraform aws vpc peering and terraform aws_vpc_endpoint_service point at resources you write yourself. The third is the NAT default: one gateway per private subnet is the correct default for availability, and it is also the most expensive option, so a team that copies the README example without reading the NAT section gets a bill that reflects a decision nobody made.

The alternative: hand-written aws_vpc and aws_subnet resources

The real alternative is not another module. It is writing the AWS provider resources directly: aws_vpc, aws_subnet, aws_internet_gateway, aws_nat_gateway, aws_eip, aws_route_table, aws_route and aws_route_table_association. That gives you exact control over every attribute and no upgrade surface beyond the AWS provider itself. The cost is that you own the AZ-to-subnet mapping, the route table fan-out and the association wiring, which is precisely the repetitive part the module exists to remove.

The difference in approach is where change happens. With hand-written resources, adding a third private subnet is a new aws_subnet block plus a route table association you write. With the module, it is one more CIDR in private_subnets, and the module recalculates the NAT gateway count from the max() rule. That recalculation cuts both ways: it is convenient when you want the extra gateway, and it is a surprise when you only wanted the subnet. A middle path is to use the module for the base VPC and subnets while writing peering, endpoints and security group rules as standalone resources alongside it, which is what the module's own examples directory does.

Maintenance, upgrades and what the Apache-2.0 licence means here

The repository is not archived, and the last push was on 2026-09-18, the same date as the v6.7.3 release, with v6.7.2 and v6.7.1 in the preceding weeks. That is a steady release cadence, and the CHANGELOG.md and .releaserc.json at the repository root indicate releases are cut through a defined process.

The upgrade cost is the v6 to v7 transition. The README already names one breaking change: removal of in-module flow log creation in v7.0.0. Anyone using that feature should treat the migration as scheduled work, not a possibility. Beyond that, the module's inputs are a large variables.tf, so the practical upgrade risk is a variable whose default or behaviour changes between minor versions; pinning the module version in the source argument is the control you have.

The licence is Apache-2.0, which permits commercial and private use and requires that the licence and attribution notices be preserved. That is a summary of the licence identifier in the repository, not legal advice; if you redistribute the module or a derivative inside a product, have your own counsel read the LICENSE file.

Editorial conclusion

Adopt it if you are already on Terraform and want the subnet taxonomy (public, private, database, elasticache, redshift, intra) and NAT gateway modes handled by one block instead of hand-written aws_subnet and aws_route_table resources. Skip it if you need a VPC that does not look like the module's model: a single flat subnet layout, or peering and endpoint wiring you intend to own resource by resource. Before you plan an apply, read variables.tf for the NAT flags you are not setting, and move any root-level flow log onto the standalone modules/flow-log module, since the README states the in-module version is deprecated and will be removed in v7.0.0.

Frequently asked questions

What is a VPC in Terraform, and what does the terraform-aws-vpc module create?

A VPC is the AWS network boundary, and in Terraform it is normally several resources rather than one. The terraform-aws-vpc module creates the VPC along with subnets, route tables, NAT gateways, network ACLs and the associations between them, driven by inputs such as cidr, azs, private_subnets and public_subnets.

Can Terraform be used with AWS?

Yes. This module is consumed through the Terraform registry as terraform-aws-modules/vpc/aws and provisions AWS VPC resources, so Terraform is the tool and AWS is the target in this workflow.

What is the AWS equivalent of Terraform?

The README does not name an AWS equivalent of Terraform. What it shows is Terraform consuming this module to create AWS VPC resources, with the AWS provider as the target rather than a replacement for Terraform.

What is the main purpose of Terraform?

In this project's context, Terraform is the tool that reads the module block and applies the resulting AWS resources. The README's usage example is a module block with a source, a name, a cidr, subnet lists and feature flags.

What does a terraform aws vpc example look like?

The README's example declares a module with source = "terraform-aws-modules/vpc/aws", a name, a cidr of 10.0.0.0/16, three AZs, three private and three public subnets, enable_nat_gateway and enable_vpn_gateway set to true, and a tags map. The repository also ships runnable configurations under examples/, including examples/simple/ and examples/complete/.

Official sources

  1. License: Apache-2.0
  2. Project website
  3. README
  4. Releases
  5. terraform-aws-modules/terraform-aws-vpc on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/terraform-aws-modules-terraform-aws-vpc.svg)](https://hysenlabs.com/projects/terraform-aws-modules-terraform-aws-vpc)