Back to Blog

AWS Aurora Serverless v2: Setup, Auto-Pause, RDS Proxy, and Connection Pooling

Set up AWS Aurora Serverless v2 for production. Covers Terraform configuration, ACU scaling, auto-pause for dev environments, RDS Proxy for connection pooling, IAM authentication, and cost comparison with standard Aurora.

Viprasol Tech Team
April 8, 2027
13 min read

Aurora Serverless v2 solves one of the hardest database capacity problems: unpredictable traffic. Standard RDS requires you to provision for peak — you pick an instance size and pay for it whether traffic is 1 req/s or 1,000 req/s. Aurora Serverless v2 scales in fractions of an ACU (Aurora Capacity Unit) within seconds, so you pay for what you actually use.

The tradeoff: it's more expensive per unit than standard Aurora at constant load, but significantly cheaper when traffic is variable or bursty — which describes most SaaS applications.

When Aurora Serverless v2 Makes Sense

ScenarioRecommendation
Steady high traffic (>50% utilization)Standard Aurora provisioned
Variable traffic (SaaS, APIs)Aurora Serverless v2 ✅
Dev/staging environmentsAurora Serverless v2 with auto-pause ✅
Latency-critical (<5ms DB response)Standard Aurora (no cold start)
Multiple microservices sharing DBAurora Serverless v2 + RDS Proxy ✅

Terraform Setup

# terraform/aurora-serverless.tf

# DB Subnet Group (subnets must span multiple AZs)
resource "aws_db_subnet_group" "aurora" {
  name       = "${var.app_name}-aurora"
  subnet_ids = var.private_subnet_ids
  tags       = var.common_tags
}

# Security Group: allow inbound from app tier
resource "aws_security_group" "aurora" {
  name   = "${var.app_name}-aurora"
  vpc_id = var.vpc_id

  ingress {
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = [aws_security_group.app.id, aws_security_group.rds_proxy.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# Aurora Serverless v2 Cluster
resource "aws_rds_cluster" "app" {
  cluster_identifier      = var.app_name
  engine                  = "aurora-postgresql"
  engine_mode             = "provisioned"    # Required for Serverless v2
  engine_version          = "16.4"
  database_name           = var.db_name
  master_username         = "postgres"
  manage_master_user_password = true        # Secrets Manager manages password rotation

  db_subnet_group_name    = aws_db_subnet_group.aurora.name
  vpc_security_group_ids  = [aws_security_group.aurora.id]

  storage_encrypted       = true
  kms_key_id              = aws_kms_key.aurora.arn

  backup_retention_period = 14             # 14 days of automated backups
  preferred_backup_window = "03:00-04:00"  # UTC
  deletion_protection     = var.environment == "production"

  # Enable Data API (for Lambda without VPC, or AWS Console queries)
  enable_http_endpoint    = var.enable_data_api

  serverlessv2_scaling_configuration {
    min_capacity = var.aurora_min_acu  # 0.5 ACU minimum for dev, 2 for prod
    max_capacity = var.aurora_max_acu  # 8 for staging, 64 for production
    # Auto-pause: set seconds_until_auto_pause (dev only — pauses after N seconds idle)
    # NOT recommended for production (resume takes 15-30 seconds)
    seconds_until_auto_pause = var.environment != "production" ? 300 : null
  }

  skip_final_snapshot     = var.environment != "production"
  final_snapshot_identifier = var.environment == "production"
    ? "${var.app_name}-final-snapshot" : null

  tags = var.common_tags
}

# Writer instance (required even for Serverless v2)
resource "aws_rds_cluster_instance" "writer" {
  identifier           = "${var.app_name}-writer"
  cluster_identifier   = aws_rds_cluster.app.id
  instance_class       = "db.serverless"    # Serverless v2 class
  engine               = aws_rds_cluster.app.engine
  engine_version       = aws_rds_cluster.app.engine_version
  db_subnet_group_name = aws_db_subnet_group.aurora.name

  performance_insights_enabled          = true
  performance_insights_retention_period = 7  # days (7 free, 731 costs extra)
  monitoring_interval                   = 60 # seconds (Enhanced Monitoring)
  monitoring_role_arn                   = aws_iam_role.rds_monitoring.arn

  tags = var.common_tags
}

# Reader instance (Multi-AZ read scaling)
resource "aws_rds_cluster_instance" "reader" {
  count                = var.environment == "production" ? 1 : 0
  identifier           = "${var.app_name}-reader"
  cluster_identifier   = aws_rds_cluster.app.id
  instance_class       = "db.serverless"
  engine               = aws_rds_cluster.app.engine
  engine_version       = aws_rds_cluster.app.engine_version
  db_subnet_group_name = aws_db_subnet_group.aurora.name

  performance_insights_enabled = true
  tags                         = var.common_tags
}

# Parameter group: PostgreSQL tuning for Aurora
resource "aws_rds_cluster_parameter_group" "app" {
  name   = "${var.app_name}-pg16"
  family = "aurora-postgresql16"

  parameter {
    name  = "shared_preload_libraries"
    value = "pg_stat_statements,auto_explain"
  }
  parameter {
    name  = "log_min_duration_statement"
    value = "1000"  # Log queries >1s
  }
  parameter {
    name  = "auto_explain.log_min_duration"
    value = "5000"  # Explain queries >5s
  }
  parameter {
    name  = "log_connections"
    value = "1"
  }
}

output "cluster_endpoint"        { value = aws_rds_cluster.app.endpoint }
output "cluster_reader_endpoint" { value = aws_rds_cluster.app.reader_endpoint }
output "cluster_port"            { value = aws_rds_cluster.app.port }

☁️ 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

RDS Proxy for Connection Pooling

Aurora Serverless v2 handles connection pooling less aggressively than standard Aurora. RDS Proxy sits between your app and Aurora, pooling connections and preventing connection storms from Lambda cold starts:

# terraform/rds-proxy.tf

resource "aws_security_group" "rds_proxy" {
  name   = "${var.app_name}-rds-proxy"
  vpc_id = var.vpc_id

  egress {
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = [aws_security_group.aurora.id]
  }

  ingress {
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = [aws_security_group.app.id]
  }
}

# IAM role for Proxy to read Secrets Manager
resource "aws_iam_role" "rds_proxy" {
  name = "${var.app_name}-rds-proxy"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Service = "rds.amazonaws.com" }
      Action    = "sts:AssumeRole"
    }]
  })
}

resource "aws_iam_role_policy" "rds_proxy_secrets" {
  role = aws_iam_role.rds_proxy.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = ["secretsmanager:GetSecretValue"]
      Resource = [aws_rds_cluster.app.master_user_secret[0].secret_arn]
    }]
  })
}

resource "aws_db_proxy" "app" {
  name                   = var.app_name
  debug_logging          = false
  engine_family          = "POSTGRESQL"
  idle_client_timeout    = 1800    # 30 min idle before proxy closes client conn
  require_tls            = true
  role_arn               = aws_iam_role.rds_proxy.arn
  vpc_security_group_ids = [aws_security_group.rds_proxy.id]
  vpc_subnet_ids         = var.private_subnet_ids

  auth {
    auth_scheme               = "SECRETS"
    secret_arn                = aws_rds_cluster.app.master_user_secret[0].secret_arn
    iam_auth                  = "DISABLED"   # Set to "REQUIRED" for IAM-only auth
  }
}

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

  connection_pool_config {
    connection_borrow_timeout    = 120  # Max seconds to wait for pooled connection
    max_connections_percent      = 90   # Use 90% of Aurora's max_connections
    max_idle_connections_percent = 50   # Keep 50% of connections open when idle
  }
}

resource "aws_db_proxy_target" "app" {
  db_cluster_identifier = aws_rds_cluster.app.id
  db_proxy_name         = aws_db_proxy.app.name
  target_group_name     = aws_db_proxy_default_target_group.app.name
}

output "proxy_endpoint" { value = aws_db_proxy.app.endpoint }

Application Connection

// lib/database/connection.ts
import { Pool } from "pg";

// Always connect to RDS Proxy endpoint (not cluster directly)
// RDS Proxy handles connection pooling and failover
const pool = new Pool({
  host: process.env.RDS_PROXY_ENDPOINT!,
  port: 5432,
  database: process.env.DB_NAME!,
  user: process.env.DB_USER!,
  password: process.env.DB_PASSWORD!,

  // Keep app-level pool small — RDS Proxy handles the real pooling
  max: 5,
  idleTimeoutMillis: 30_000,
  connectionTimeoutMillis: 5_000,
  ssl: { rejectUnauthorized: true },  // RDS Proxy enforces TLS
});

pool.on("error", (err) => {
  console.error("Unexpected error on idle client", err);
});

export { pool };
// For Prisma, use connection pooling via connection string
// DATABASE_URL=postgresql://user:pass@proxy-endpoint:5432/db?connection_limit=5&pool_timeout=30

// prisma/schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

⚙️ 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

Cost Analysis: Aurora Serverless v2 vs Standard Aurora

MetricStandard db.r6g.largeAurora Serverless v2 (2–8 ACU)
Baseline cost (always-on)$0.26/hour = $189/mo2 ACU × $0.12/ACU-hr = $0.24/hr = $173/mo
Peak traffic (8 ACU)Same $189/mo8 ACU × $0.12 = $0.96/hr = variable
50% utilization (avg 4 ACU)$189/mo~$87/mo
Dev environment (auto-pause)$189/mo~$5–20/mo
RDS Proxy (production)+$0.015/VPC hour = +$11/mo+$11/mo
Storage$0.10/GB/month$0.10/GB/month

Key insight: At consistent high load, provisioned wins. At variable load (typical SaaS), Serverless v2 saves 40–60%.

ACU Sizing Guide

1 ACU ≈ 2 GB RAM
Recommended starting points:

Development:   min=0.5  max=4
Staging:       min=1    max=8
Production:    min=2    max=32  (adjust based on observed usage)
High-traffic:  min=4    max=64

Monitor ServerlessDatabaseCapacity CloudWatch metric to tune min/max.

CloudWatch Alarms

# Alert when hitting max ACU (scale-up ceiling)
resource "aws_cloudwatch_metric_alarm" "aurora_max_capacity" {
  alarm_name          = "${var.app_name}-aurora-at-max-capacity"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 3
  metric_name         = "ServerlessDatabaseCapacity"
  namespace           = "AWS/RDS"
  period              = 60
  statistic           = "Maximum"
  threshold           = var.aurora_max_acu * 0.95  # Alert at 95% of max
  dimensions          = { DBClusterIdentifier = aws_rds_cluster.app.id }
  alarm_description   = "Aurora Serverless approaching max capacity — consider increasing max_capacity"
  alarm_actions       = [aws_sns_topic.alerts.arn]
}

See Also


Working With Viprasol

Aurora Serverless v2 cuts database costs for SaaS products with variable traffic — but the Terraform setup, RDS Proxy configuration, and ACU sizing decisions all have non-obvious tradeoffs. Our team has Aurora Serverless v2 in production for multiple SaaS clients, with RDS Proxy for connection pooling, Performance Insights for query optimization, and CloudWatch alarms for capacity monitoring.

What we deliver:

  • Terraform: Aurora Serverless v2 cluster with writer + optional reader
  • RDS Proxy setup with Secrets Manager integration and connection pool tuning
  • Parameter group with pg_stat_statements, auto_explain, and slow query logging
  • CloudWatch alarms for capacity ceiling and connection errors
  • Application connection configuration for Prisma and pg Pool

Talk to our team about your Aurora Serverless setup →

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.