Back to Blog

AWS Lambda in VPC: RDS Access, NAT Gateway vs Interface Endpoints, and Cold Start Impact

Run AWS Lambda functions inside a VPC to access RDS and private resources. Covers VPC configuration, security groups, NAT gateway vs VPC interface endpoints for internet access, cold start impact, and Terraform setup.

Viprasol Tech Team
May 3, 2027
12 min read

Lambda functions run outside your VPC by default — they have internet access but can't reach private resources like RDS, ElastiCache, or internal services. Placing Lambda inside a VPC solves that, but introduces new problems: cold starts increase by 1–3 seconds during ENI (Elastic Network Interface) attachment, and internet access requires either a NAT gateway or VPC endpoints.

This guide covers VPC Lambda architecture, the right security group rules, and how to eliminate the cold start penalty.

When Lambda Needs a VPC

NeedSolution
Access RDS PostgreSQL/MySQLVPC required
Access ElastiCache RedisVPC required
Access internal microservicesVPC required
Access DynamoDBVPC endpoint or public (no VPC needed)
Access S3VPC endpoint or public (no VPC needed)
Access Secrets ManagerVPC endpoint (recommended)
Internet access from VPC LambdaNAT gateway required

Architecture Overview

VPC (10.0.0.0/16)
├── Private Subnets (10.0.1.0/24, 10.0.2.0/24) — Lambda + RDS
│   ├── Lambda function (ENI attached per AZ)
│   └── RDS PostgreSQL (Multi-AZ)
├── Public Subnets (10.0.101.0/24, 10.0.102.0/24)
│   └── NAT Gateway (for Lambda internet access)
└── VPC Endpoints
    ├── Secrets Manager (interface endpoint — no NAT needed)
    ├── S3 (gateway endpoint — free)
    └── DynamoDB (gateway endpoint — free)

☁️ Is Your Cloud Costing Too Much?

Most teams overspend 30–40% on cloud — wrong instance types, no reserved pricing, bloated storage. We audit, right-size, and automate your infrastructure.

  • AWS, GCP, Azure certified engineers
  • Infrastructure as Code (Terraform, CDK)
  • Docker, Kubernetes, GitHub Actions CI/CD
  • Typical audit recovers $500–$3,000/month in savings

Terraform: VPC Lambda Setup

# terraform/lambda-vpc.tf

# Security group for Lambda
resource "aws_security_group" "lambda" {
  name        = "${var.app_name}-lambda-${var.environment}"
  vpc_id      = var.vpc_id
  description = "Lambda function security group"

  # No inbound rules needed — Lambda is invoked by AWS, not by incoming connections

  # Outbound: allow Lambda to connect to RDS
  egress {
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = [aws_security_group.rds.id]
    description     = "PostgreSQL access"
  }

  # Outbound: allow Lambda to call VPC endpoints (HTTPS)
  egress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]  # Needed for VPC endpoints + NAT gateway internet
    description = "HTTPS outbound (VPC endpoints + internet via NAT)"
  }

  # Outbound: allow DNS resolution
  egress {
    from_port   = 53
    to_port     = 53
    protocol    = "udp"
    cidr_blocks = [var.vpc_cidr]
    description = "DNS"
  }

  tags = { Name = "${var.app_name}-lambda-${var.environment}" }
}

# Security group for RDS: allow inbound from Lambda only
resource "aws_security_group" "rds" {
  name        = "${var.app_name}-rds-${var.environment}"
  vpc_id      = var.vpc_id
  description = "RDS security group — Lambda access only"

  ingress {
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = [aws_security_group.lambda.id]
    description     = "PostgreSQL from Lambda"
  }

  # No direct internet access to RDS
  tags = { Name = "${var.app_name}-rds-${var.environment}" }
}

# Lambda function with VPC config
resource "aws_lambda_function" "app" {
  function_name = "${var.app_name}-${var.environment}"
  role          = aws_iam_role.lambda.arn
  package_type  = "Image"
  image_uri     = "${var.ecr_repo}:${var.image_tag}"

  timeout     = 30
  memory_size = 1024
  architectures = ["arm64"]

  vpc_config {
    # Deploy to multiple AZs for high availability
    # Lambda creates one ENI per AZ for each function version in use
    subnet_ids         = var.private_subnet_ids  # Private subnets (no internet)
    security_group_ids = [aws_security_group.lambda.id]
  }

  environment {
    variables = {
      NODE_ENV   = var.environment
      AWS_REGION = var.aws_region
    }
  }

  # Secrets injected from Secrets Manager at invocation
  # (requires execution role with secretsmanager:GetSecretValue)
}

# Lambda requires ENI management permissions
resource "aws_iam_role_policy_attachment" "lambda_vpc" {
  role       = aws_iam_role.lambda.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
}

Cold Start: The ENI Problem (Pre-2020 vs Today)

Before 2020, VPC Lambda had notorious 10–30 second cold starts caused by ENI creation per function instance. AWS fixed this in 2020 with pre-created ENIs shared across functions. Today's cold start impact is much smaller.

Current cold start breakdown for VPC Lambda (2027):

ComponentNon-VPC LambdaVPC Lambda
ENI attachment0ms~0ms (pre-created)
Container init200–500ms200–500ms
Runtime init100–300ms100–300ms
Code init50–200ms50–200ms
Total typical cold start350–1,000ms400–1,100ms

The VPC penalty is now typically 50–100ms, not 10–30 seconds. For most applications, VPC Lambda is fine.

Remaining cold start mitigation strategies:

// 1. Initialize DB connection outside handler (reused on warm invocations)
import { Pool } from "pg";

// Created once per Lambda instance, reused across invocations
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 1,          // Lambda: only 1 concurrent execution per instance
  idleTimeoutMillis: 300_000,  // Keep connection for 5 minutes
});

export const handler = async (event: any) => {
  // pool is already initialized — no connection overhead on warm invocations
  const { rows } = await pool.query("SELECT $1::text AS message", ["hello"]);
  return { message: rows[0].message };
};
# 2. Provisioned concurrency (pre-warm instances — no cold start)
resource "aws_lambda_provisioned_concurrency_config" "app" {
  count                             = var.environment == "production" ? 1 : 0
  function_name                     = aws_lambda_function.app.function_name
  qualifier                         = aws_lambda_alias.live.name
  provisioned_concurrent_executions = 2  # Keep 2 warm instances

  # Cost: ~$25/month per 1GB instance running 24/7
}

⚙️ DevOps Done Right — Zero Downtime, Full Automation

Ship faster without breaking things. We build CI/CD pipelines, monitoring stacks, and auto-scaling infrastructure that your team can actually maintain.

  • Staging + production environments with feature flags
  • Automated security scanning in the pipeline
  • Uptime monitoring + alerting + runbook automation
  • On-call support handover docs included

VPC Endpoints: Avoid NAT Gateway Costs

NAT gateway charges: $0.045/hour (~$32/month) + $0.045/GB processed. For Lambda functions that call Secrets Manager, S3, or DynamoDB heavily, VPC endpoints save money and remove the internet dependency.

# terraform/vpc-endpoints.tf

# Gateway endpoints: FREE (S3 and DynamoDB only)
resource "aws_vpc_endpoint" "s3" {
  vpc_id            = var.vpc_id
  service_name      = "com.amazonaws.${var.aws_region}.s3"
  vpc_endpoint_type = "Gateway"
  route_table_ids   = var.private_route_table_ids

  tags = { Name = "${var.app_name}-s3-endpoint" }
}

resource "aws_vpc_endpoint" "dynamodb" {
  vpc_id            = var.vpc_id
  service_name      = "com.amazonaws.${var.aws_region}.dynamodb"
  vpc_endpoint_type = "Gateway"
  route_table_ids   = var.private_route_table_ids
}

# Interface endpoints: $7.30/AZ/month each
# Worth it if NAT gateway traffic would cost more

# Secrets Manager: Lambda fetches secrets without internet
resource "aws_vpc_endpoint" "secrets_manager" {
  vpc_id              = var.vpc_id
  service_name        = "com.amazonaws.${var.aws_region}.secretsmanager"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = var.private_subnet_ids
  security_group_ids  = [aws_security_group.vpc_endpoints.id]
  private_dns_enabled = true   # Lambda calls secretsmanager.region.amazonaws.com — resolved to private IP

  tags = { Name = "${var.app_name}-secrets-endpoint" }
}

# SSM Parameter Store
resource "aws_vpc_endpoint" "ssm" {
  vpc_id              = var.vpc_id
  service_name        = "com.amazonaws.${var.aws_region}.ssm"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = var.private_subnet_ids
  security_group_ids  = [aws_security_group.vpc_endpoints.id]
  private_dns_enabled = true
}

# SQS (if Lambda consumes from SQS)
resource "aws_vpc_endpoint" "sqs" {
  vpc_id              = var.vpc_id
  service_name        = "com.amazonaws.${var.aws_region}.sqs"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = var.private_subnet_ids
  security_group_ids  = [aws_security_group.vpc_endpoints.id]
  private_dns_enabled = true
}

# Security group for VPC endpoints: allow HTTPS from Lambda
resource "aws_security_group" "vpc_endpoints" {
  name   = "${var.app_name}-vpc-endpoints"
  vpc_id = var.vpc_id

  ingress {
    from_port       = 443
    to_port         = 443
    protocol        = "tcp"
    security_groups = [aws_security_group.lambda.id]
    description     = "HTTPS from Lambda"
  }
}

Decision: NAT Gateway vs VPC Endpoints

Lambda calls: Secrets Manager, SSM, SQS, S3, DynamoDB (AWS services only)
→ Use VPC endpoints. No NAT gateway needed. Cheaper, no internet dependency.

Lambda calls: External APIs (Stripe, Resend, OpenAI, etc.)
→ Need NAT gateway. VPC endpoints only work for AWS services.

Lambda calls: Both external APIs AND AWS services
→ NAT gateway + VPC endpoints for S3/DynamoDB (gateway endpoints are free).
   Interface endpoints for heavy AWS service callers.

Connection Pooling: PgBouncer for Lambda

Lambda with VPC + RDS: each Lambda instance opens its own connection. At scale (100 concurrent Lambda instances), you can exhaust RDS max_connections.

# RDS Proxy: connection pooling managed by AWS
resource "aws_db_proxy" "postgres" {
  name                   = "${var.app_name}-proxy"
  debug_logging          = false
  engine_family          = "POSTGRESQL"
  idle_client_timeout    = 1800
  require_tls            = true
  role_arn               = aws_iam_role.rds_proxy.arn

  auth {
    auth_scheme = "SECRETS"
    iam_auth    = "REQUIRED"          # IAM auth — no password in connection string
    secret_arn  = var.db_secret_arn
  }

  vpc_security_group_ids = [aws_security_group.rds_proxy.id]
  vpc_subnet_ids         = var.private_subnet_ids
}

resource "aws_db_proxy_default_target_group" "postgres" {
  db_proxy_name = aws_db_proxy.postgres.name

  connection_pool_config {
    max_connections_percent      = 70    # Use up to 70% of RDS max_connections
    max_idle_connections_percent = 50
    connection_borrow_timeout    = 120   # Wait up to 2 min for connection
  }
}

# Lambda connects to proxy endpoint, not RDS directly
# DATABASE_URL = postgres://user@proxy-endpoint.proxy-xxx.us-east-1.rds.amazonaws.com:5432/db

Cost Comparison

ArchitectureMonthly Cost (typical)
Lambda without VPC~$0 networking
Lambda + VPC + NAT Gateway$32+ (NAT) + data transfer
Lambda + VPC + VPC Endpoints only$7.30/endpoint/AZ
Lambda + RDS Proxy$0.015/vCPU-hour of RDS

Rule of thumb: If your Lambda functions call more than 10GB/month through NAT gateway, replacing with VPC endpoints saves money.

See Also


Working With Viprasol

Lambda-in-VPC architecture has real tradeoffs: you gain access to private resources but add networking complexity. Our team designs VPC Lambda architectures with the right security groups, interface endpoints for heavy AWS service callers (avoiding NAT gateway costs), RDS Proxy for connection pooling, and provisioned concurrency where cold starts matter.

What we deliver:

  • Lambda + RDS security groups with minimal egress (port 5432 to RDS SG only)
  • VPC endpoint configuration: S3/DynamoDB (gateway, free) + Secrets Manager/SQS (interface)
  • RDS Proxy setup for Lambda connection pooling at scale
  • Decision matrix: when to use NAT gateway vs VPC endpoints
  • Terraform module with all components (lambda, SGs, endpoints, proxy)

Talk to our team about your Lambda networking architecture →

Or explore our cloud infrastructure services.

Share this article:

About the Author

V

Viprasol Tech Team

Custom Software Development Specialists

The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 100+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement. Based in India, serving clients globally.

MT4/MT5 EA DevelopmentAI Agent SystemsSaaS DevelopmentAlgorithmic Trading

Need DevOps & Cloud Expertise?

Scale your infrastructure with confidence. AWS, GCP, Azure certified team.

Free consultation • No commitment • Response within 24 hours

Viprasol · Big Data & Analytics

Making sense of your data at scale?

Viprasol builds end-to-end big data analytics solutions — ETL pipelines, data warehouses on Snowflake or BigQuery, and self-service BI dashboards. One reliable source of truth for your entire organisation.