Skip to content

Lambda in a VPC: the networking mistakes that break internet and RDS access

Attach a Lambda function to a VPC and suddenly it cannot reach the internet, or it still cannot reach RDS. The five networking mistakes behind almost every Lambda VPC failure, and how to fix each one.

Attaching a Lambda function to a VPC changes one thing completely: the function stops using Lambda’s own internet-connected network and starts sending all traffic through an elastic network interface inside your subnets. From that moment, your route tables, security groups, and subnet choices decide what the function can reach — and the failure mode is almost never an error message, it is a silent hang followed by a timeout. Nearly every broken Lambda-in-a-VPC setup comes down to five mistakes, and they are all checkable in fifteen minutes.

What’s happening

A VPC-attached function gets a Hyperplane ENI in each subnet you select, and every outbound packet leaves through it. The ENI never receives a public IP address. That single fact explains the most common failure: putting the function in a public subnet, whose route table sends 0.0.0.0/0 to an internet gateway. An internet gateway only works for resources with public IPs — the function’s packets go out, nothing can route back, and every external call hangs until the function times out.

Internet access for a VPC-attached function therefore requires the same path as any private server: subnets whose route table sends 0.0.0.0/0 to a NAT gateway (or NAT instance) that itself lives in a public subnet. Calls to AWS services — S3, Secrets Manager, SQS — can either ride that NAT path or, better, use VPC endpoints and skip the internet entirely.

The second family of failures is security groups. The function’s security group governs its outbound traffic (usually open by default), but the target’s security group must allow inbound from the function. An RDS instance whose security group allows inbound 5432 only from the application server’s group will refuse the Lambda function until the function’s security group is added as a source.

The rest is bookkeeping: DNS resolution settings on the VPC, subnets that run out of free IP addresses, and functions attached to subnets in the wrong VPC entirely — all real cases we have found in production accounts.

What to check first

  • Which subnets the function is attached to, and where their default route points. NAT gateway: good. Internet gateway: that is the bug. No default route: no internet by design — confirm that is intentional.
  • The target’s security group. Does the RDS/ElastiCache/internal-ALB security group allow inbound from the Lambda function’s security group on the right port?
  • Whether both sides are in the same VPC. If not, there must be peering or a transit gateway with routes both ways.
  • VPC DNS settings. enableDnsSupport and enableDnsHostnames must be on for RDS endpoint hostnames to resolve.
  • Free IPs in the subnets. Each unique function configuration consumes ENI capacity; tiny /28 subnets cause failures at scale.

How to verify

Pull the function’s network configuration and follow it outward:

aws lambda get-function-configuration --function-name my-function \
  --query 'VpcConfig'

# Where does each subnet route 0.0.0.0/0?
aws ec2 describe-route-tables \
  --filters "Name=association.subnet-id,Values=subnet-0abc123" \
  --query 'RouteTables[].Routes[?DestinationCidrBlock==`0.0.0.0/0`]'

# What does the target security group allow inbound?
aws ec2 describe-security-groups --group-ids sg-0db123 \
  --query 'SecurityGroups[].IpPermissions'

In the console: Lambda > Functions > (function) > Configuration > VPC shows subnets and security groups; VPC > Subnets > Route table shows the egress path. For a live test, a temporary diagnostic function that attempts a curl to an external URL and a TCP connect to the database, logging each step with timing, tells you in one invocation which hop hangs. VPC routing concepts are documented at https://docs.aws.amazon.com/vpc/.

The fix

  1. Move the function to private subnets that route through a NAT gateway. This is the canonical fix for “no internet after VPC attach.” Select two or three private subnets across AZs, confirm their shared route table sends 0.0.0.0/0 to the NAT gateway, and redeploy. Sizing and cost trade-offs are covered in NAT gateway vs NAT instance.
  2. Add the function’s security group as a source on the target. For RDS: edit the database security group, add an inbound rule for the DB port with the Lambda function’s security group as the source. Reference groups by ID, not CIDR — it survives IP churn.
  3. Use VPC endpoints for AWS service traffic. Gateway endpoints for S3 and DynamoDB are free and remove that traffic from the NAT path; interface endpoints cover Secrets Manager, SQS, and most of the rest. The selection logic is in VPC endpoints for S3 and DynamoDB.
  4. Fix VPC DNS if hostnames do not resolve. Enable DNS support and DNS hostnames on the VPC (aws ec2 modify-vpc-attribute --vpc-id vpc-0abc --enable-dns-hostnames), and remember custom DHCP option sets or Route 53 Resolver rules can override behaviour.
  5. Give Lambda room in the subnets. Dedicate reasonably sized private subnets (a /24 per AZ is a comfortable floor) rather than sharing crowded ones, so ENI allocation never competes with other workloads.
  6. Only attach what needs attaching. If a function does not touch private resources, take it out of the VPC entirely — it regains direct internet access and you delete a whole class of failure.

When this is the wrong move

If the only reason the function is in the VPC is to reach RDS, and connection storms are the actual pain, the deeper fix may be RDS Proxy rather than more network surgery — Lambda’s scale-to-hundreds behaviour is fundamentally hostile to per-invocation database connections. And if you are building NAT infrastructure solely so one small function can call one external API, reconsider: keep that function outside the VPC, or route the call through an existing egress path.

Common mistakes

  • Public subnet plus internet gateway, expecting internet — the classic, because it works for EC2 instances with public IPs and silently fails for Lambda.
  • Testing from an EC2 bastion in a different subnet and concluding “the network is fine” — different subnet, different route table, different result.
  • Opening the database security group to 0.0.0.0/0 “temporarily” during debugging and shipping it that way.
  • Attaching every function in the account to the VPC by default, dragging internet-bound traffic through NAT and paying processing charges for no benefit.

Prevention

  • Standardize two or three “lambda-private” subnets per VPC in your IaC, pre-wired to the NAT route table, and forbid ad-hoc subnet selection.
  • Reference security groups by ID in IaC so the Lambda-to-database rule is created alongside the function, not remembered afterwards.
  • Add gateway endpoints for S3 and DynamoDB to every VPC template by default — they are free and remove both cost and failure surface.
  • Include a network smoke test (external HTTPS call plus database TCP connect) in the deployment pipeline for VPC-attached functions.

VPC-attached Lambda works reliably once the routes, groups, and subnets are deliberate — every silent hang traces back to one of the five mistakes above. If nobody on your team truly owns the AWS environment end to end, that is the root of most of these problems — Stack Harbor runs AWS accounts daily under AWS Operations, and you can book an AWS review.