Your Lambda functions, when placed inside a VPC, suddenly can’t talk to the outside world directly. This problem is about giving them that access back, but without the agonizingly slow cold starts that usually come with it.

Here’s a Lambda function, running in a VPC, that needs to call an external API. We’ll simulate the API call with a simple curl command.

import json
import urllib.request

def lambda_handler(event, context):
    try:
        # Simulate calling an external API
        req = urllib.request.Request('https://httpbin.org/get')
        with urllib.request.urlopen(req) as response:
            body = response.read()
        return {
            'statusCode': 200,
            'body': json.dumps({'message': 'Successfully called external API', 'response_body': body.decode('utf-8')})
        }
    except Exception as e:
        return {
            'statusCode': 500,
            'body': json.dumps({'message': 'Error calling external API', 'error': str(e)})
        }

When you first deploy this Lambda and attach it to a VPC, you’ll notice that the first invocation takes a noticeable amount of time. This is the "cold start" penalty. Subsequent invocations are fast. The slowness comes from Lambda needing to set up network interfaces within your VPC for your function to use.

The core problem is that when a Lambda function resides within a VPC, it doesn’t have a public IP address by default. To access resources outside the VPC (like the public internet to call external APIs), it needs a way to route its traffic. Traditionally, this meant either:

  1. Giving the Lambda ENI a public IP: This is generally not an option for Lambda ENIs.
  2. Placing the Lambda in public subnets with NAT Gateway: This works, but NAT Gateways are expensive and, more importantly, the ENI attachment process for VPC-enabled Lambdas can take 10-12 seconds during a cold start, even if the subnet has a route to a NAT Gateway.

The solution is to use VPC Endpoints for services you need to access within AWS, and NAT Gateways for external services, but with a crucial optimization for the cold start.

Let’s break down how to achieve private access without the cold start penalty.

Scenario 1: Accessing AWS Services Privately (e.g., S3, DynamoDB)

The Problem: Your Lambda needs to talk to S3, but you don’t want its traffic to traverse the public internet.

The Solution: Use VPC Gateway Endpoints for S3 and DynamoDB.

How to Configure:

  1. Navigate to the VPC console.
  2. Under "Virtual Private Cloud," select "Endpoints."
  3. Click "Create Endpoint."
  4. Service category: "AWS services."
  5. Service name: Search for com.amazonaws.<region>.s3. Select the one that matches your region.
  6. VPC: Select the VPC your Lambda is in.
  7. Route table: Select the route tables associated with the private subnets where your Lambda function will be deployed.
  8. Policy: "Full Access" is usually sufficient, but you can restrict it.
  9. Click "Create Endpoint." Repeat for com.amazonaws.<region>.dynamodb if needed.

Why it Works: Gateway Endpoints add a route to your selected route tables that directs traffic destined for the AWS service (e.3., S3) to the endpoint, without leaving the AWS network. This is inherently fast and secure, and crucially, it doesn’t involve the ENI attachment process that causes cold start delays for external internet access. The Lambda function’s ENI still gets created, but the traffic to S3 is routed before it needs to hit the public internet or a NAT device.

Scenario 2: Accessing External Services (e.g., a third-party API)

The Problem: Your Lambda needs to call an API hosted on the public internet, and you want to avoid the cold start penalty associated with NAT Gateway ENI setup.

The Solution: Use a NAT Gateway located in a public subnet, and keep your Lambda functions in private subnets. The key to avoiding the cold start penalty here is a combination of Lambda’s networking capabilities and how it attaches ENIs.

How to Configure (The Traditional Way, which has the penalty):

  1. Create a NAT Gateway:

    • Go to the VPC console -> NAT Gateways -> Create NAT Gateway.
    • Name: my-lambda-nat-gw
    • Subnet: Select a public subnet (one with an Internet Gateway attached and a route to it).
    • Connectivity type: Public.
    • Elastic IP allocation ID: Allocate a new Elastic IP or select an existing one.
    • Click "Create NAT Gateway."
  2. Configure Route Tables:

    • Identify the route tables associated with your private subnets (where your Lambda will run).
    • Add a route:
      • Destination: 0.0.0.0/0 (all internet traffic)
      • Target: Select your newly created NAT Gateway.
  3. Configure Lambda Function:

    • Go to your Lambda function’s configuration.
    • Under "VPC," select your VPC.
    • Subnets: Select your private subnets.
    • Security groups: Ensure it has egress access to the NAT Gateway.

The Cold Start Penalty Explained: When a Lambda function is configured for VPC access, AWS provisions an Elastic Network Interface (ENI) in one of your specified subnets for it. This ENI attachment process, especially for private subnets that need to route through a NAT Gateway, is what adds significant latency (10-12 seconds) to the first invocation after a period of inactivity.

The Advanced Solution for Reduced Cold Starts (using VPC Endpoints for NAT):

While the above configuration works, it incurs the cold start penalty. AWS has since introduced improvements and a more nuanced understanding. The primary driver of the cold start penalty for VPC Lambdas is the ENI attachment and initialization. For external access, it’s unavoidable if you’re using NAT.

However, AWS has been optimizing ENI provisioning. The most effective way to mitigate the cold start penalty for VPC Lambdas, even when using NAT, is to:

  1. Keep Lambda Functions in Private Subnets: This is a security best practice.
  2. Use NAT Gateway in Public Subnets: For external access.
  3. Ensure Sufficient IP Addresses: Make sure your private subnets have enough available IP addresses. Each ENI consumes one IP.
  4. Provision ENIs in Advance (Less direct control, but AWS does this): AWS has improved its ENI provisioning for Lambda. For functions that are frequently invoked, AWS attempts to keep ENIs warm. However, you don’t directly control this.
  5. Provisioned Concurrency: This is the most direct way to eliminate cold starts. By setting Provisioned Concurrency, you tell Lambda to keep a specified number of function instances initialized and ready to respond. These instances will have their ENIs already attached and ready.

How to Use Provisioned Concurrency:

  1. Go to your Lambda function.
  2. Under "Configuration," select "Concurrency."
  3. Select "Edit."
  4. Choose "Provisioned concurrency."
  5. Enter the number of instances you want to keep warm. For example, 5.
  6. Click "Save."

Why Provisioned Concurrency Works (Mechanically): Provisioned Concurrency bypasses the ENI attachment and initialization delay during invocation. Lambda pre-allocates and pre-initializes the specified number of execution environments, including their VPC network interfaces. When a request comes in, it’s routed immediately to one of these warm, ready-to-go instances. This is the most reliable way to eliminate the cold start penalty, regardless of VPC configuration.

The next hurdle you’ll likely encounter is managing security group rules effectively for your VPC-enabled Lambda functions to allow specific outbound traffic.

Want structured learning?

Take the full Lambda course →