You finally got your Lambda function working. It hits an external API, processes the response, saves the result. Everything is great. Then you send the API endpoint to your client and they ask: “Can you give us the IP so we can whitelist it?”
And you realize you have no idea what IP Lambda is using.
If you have been here before, you know the frustration. Lambda does not have a fixed outbound IP. Every time it runs, it can come from a different address. For internal AWS workflows that is completely fine. But the moment you need to connect to a third-party API with IP allowlisting, a database behind a firewall, or an enterprise system that requires a known source address, you are stuck.
This post explains why that happens, what the correct fix is, and how to wire it all up with Terraform.
Why Lambda Has No Fixed IP
Lambda is a managed service. AWS handles the underlying compute, the networking, the scaling. You do not own the servers and you do not control what IP address they sit behind. When your function runs, AWS picks a host from a pool, and that host could have any number of public IPs.
Even if you attach your Lambda to a VPC, that alone does not give you a stable outbound IP. A lot of people assume that VPC attachment is the answer because it feels more “controlled.” It is not. Being inside a VPC just means your function can reach private resources in that VPC. Outbound internet traffic still leaves through a path you do not control unless you set one up explicitly.
The thing you need is a NAT Gateway with an Elastic IP attached to it.
How the Architecture Works
The idea is straightforward once you see it laid out:
Your Lambda function lives in a private subnet with no direct route to the internet. All outbound traffic flows through the NAT Gateway, which sits in a public subnet and has an Elastic IP attached to it. That Elastic IP is static. It does not change unless you release it yourself. That is the IP you give to your third-party vendor or firewall rule.
Step by Step Setup
1. Create a VPC
You need a VPC with at least two subnets. One private subnet for Lambda, one public subnet for the NAT Gateway.
2. Create an Internet Gateway
The public subnet needs a route to the internet. You attach an Internet Gateway to your VPC and add a route from the public subnet to it.
3. Allocate an Elastic IP
An Elastic IP is just a static public IP address that you own in your AWS account. You allocate it once and it stays yours until you explicitly release it.
4. Create a NAT Gateway
The NAT Gateway goes into the public subnet and you associate your Elastic IP with it. This is the single hop all your Lambda traffic will pass through.
5. Update the Private Subnet Route Table
The private subnet’s route table needs a default route (0.0.0.0/0) pointing to the NAT Gateway. Without this, your Lambda function has no path to reach the internet at all.
6. Attach Lambda to the Private Subnet
In your Lambda configuration, specify the VPC and the private subnet. Also configure a security group that allows outbound traffic on whatever ports you need.
That is the whole setup. Now every request your Lambda makes to the outside world will appear to come from your Elastic IP.
Terraform Example
Here is a complete working example you can drop into your infrastructure:
# VPC
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
}
# Public subnet for NAT Gateway
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
}
# Private subnet for Lambda
resource "aws_subnet" "private" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
availability_zone = "us-east-1a"
}
# Internet Gateway
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
}
# Elastic IP for NAT Gateway
resource "aws_eip" "nat" {
domain = "vpc"
}
# NAT Gateway in the public subnet
resource "aws_nat_gateway" "nat" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public.id
depends_on = [aws_internet_gateway.igw]
}
# Route table for public subnet
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
# Route table for private subnet (routes through NAT)
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat.id
}
}
resource "aws_route_table_association" "private" {
subnet_id = aws_subnet.private.id
route_table_id = aws_route_table.private.id
}
# Security group for Lambda
resource "aws_security_group" "lambda_sg" {
vpc_id = aws_vpc.main.id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Output the static IP
output "static_ip" {
value = aws_eip.nat.public_ip
}
The static_ip output is the address you hand over to whoever needs to allowlist you.
Common Mistakes
Putting Lambda in the public subnet. This seems logical but it does not work. Lambda in a public subnet cannot use the NAT Gateway, and AWS does not automatically assign a public IP to Lambda functions inside a VPC. You get no outbound internet access at all. Lambda must be in the private subnet.
Forgetting the route table. Creating the NAT Gateway is not enough. If the private subnet’s route table does not have a 0.0.0.0/0 route pointing at the NAT Gateway, traffic has nowhere to go. Your Lambda will just time out trying to reach the internet with no useful error message to explain why.
Security group blocking outbound traffic. By default, security groups block everything. If your Lambda’s security group does not have an egress rule, outbound requests will silently fail. The Terraform example above includes a permissive egress rule. In production you might want to tighten that to specific ports.
DNS resolution failing. If your Lambda cannot resolve hostnames, make sure your VPC has enable_dns_support and enable_dns_hostnames set to true. Without those, DNS queries do not work inside the VPC and you will get confusing timeout errors on requests that should otherwise succeed.
NAT Gateway cost shock. More on this below.
Cost Considerations
NAT Gateway pricing has two parts and people often only think about one of them.
First, there is the hourly charge just for having a NAT Gateway running. At the time of writing this is around $0.045 per hour in us-east-1, which works out to roughly $32 per month even if your Lambda never runs.
Second, there is a per-GB data processing charge on all traffic that flows through it. If your Lambda is pulling large responses from external APIs or moving a lot of data, this adds up quickly.
For most production use cases this is a perfectly acceptable cost. The reliability and simplicity of NAT Gateway is worth it. One thing worth knowing: if your Lambda only needs to talk to other AWS services like S3 or DynamoDB, a VPC endpoint lets you do that from a private subnet without going through NAT Gateway at all, which is both cheaper and faster.
Final Thoughts
The pattern here is not complicated. Private subnet for Lambda, public subnet for NAT Gateway, Elastic IP on the NAT Gateway, route table wiring them together. Once you set it up it just works and you never think about it again.
Use NAT Gateway. The cost is predictable, the setup is simple, and it just works. If you only need to reach AWS services, check whether a VPC endpoint covers your needs before adding NAT Gateway at all.
The IP in aws_eip.nat.public_ip is yours. Give it to whoever asked and get back to building.