A secure-by-default Amazon RDS Proxy β TLS required in transit, IAM client auth on by default, debug logging off, private-subnet-only β that fronts an RDS instance or Aurora cluster with a managed connection pool, its default target group, target registration, and optional read/write-split endpoints, all from a single composite call. Built for the AWS provider v6.x.
- π Provisions an Amazon RDS Proxy (
aws_db_proxy) β AWS's fully-managed database connection pooler β as the keystone resource, smoothing connection storms from serverless/containerized fleets and adding failover speed-up in front of RDS and Aurora. - ποΈ Owns the proxy's default target group (
aws_db_proxy_default_target_group) for connection-pool tuning β max connections percent, borrow timeout, idle reaping, init query, session-pinning filters. - π― Registers the backing database (
aws_db_proxy_target) β an RDS instance or an Aurora cluster (mutually exclusive) β via a churn-freefor_each, or lets you register out of band by leaving both identifiers null. - πͺ’ Creates additional proxy endpoints (
aws_db_proxy_endpoint) as afor_eachmap keyed by a stable caller name, so you can carveREAD_ONLYendpoints fanned out to Aurora readers for read/write splitting. - π Secure by default:
require_tls = true, per-useriam_auth = "REQUIRED",debug_logging = false, a conservative 1800s idle timeout, and private subnets only β there is no public option. - π Credentials are never module inputs. The proxy reads
{username, password}at runtime from AWS Secrets Manager using the caller-supplied IAM role β nothing secret ever lands in Terraform state.
π‘ Why it matters: the proxy sits directly in the path of PII under privacy-regulation. A proxy that accepts plaintext connections or skips IAM auth widens the blast radius far beyond the convenience saved β so this module ships locked down and makes you opt out explicitly.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- β Star this repository to help others discover this Terraform module.
- π€ Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- β Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
flowchart LR
VPC["terraform-aws-vpc"]
SG["terraform-aws-security-group"]
IAM["terraform-aws-iam-role"]
SM["terraform-aws-secrets-manager"]
KMS["terraform-aws-kms"]
RDS["terraform-aws-rds"]
AUR["terraform-aws-rds-aurora"]
PROXY["terraform-aws-rds-proxy"]
APP["Application tier<br/>(ECS / EKS / Lambda / EC2)"]
VPC -->|vpc_subnet_ids| PROXY
SG -->|vpc_security_group_ids| PROXY
IAM -->|role_arn| PROXY
SM -->|auth secret_arn| PROXY
KMS -.->|encrypts secret| SM
RDS -->|db_instance_identifier| PROXY
AUR -->|db_cluster_identifier| PROXY
PROXY -->|endpoint / proxy_endpoints| APP
style PROXY fill:#FF9900,color:#fff
RDS Proxy sits at the data-access tier β downstream of the networking, security-group, IAM, and Secrets Manager foundations, in front of the terraform-aws-rds / terraform-aws-rds-aurora backing database, and upstream of the application fleet that connects to its endpoints instead of to the database directly.
flowchart TD
subgraph caller["Caller-supplied (by reference)"]
SUBNETS["vpc_subnet_ids<br/>(terraform-aws-vpc)"]
SGS["vpc_security_group_ids<br/>(terraform-aws-security-group)"]
ROLE["role_arn<br/>(terraform-aws-iam-role)"]
SECRET["auth[*].secret_arn<br/>(terraform-aws-secrets-manager)"]
DB["db_instance_identifier /<br/>db_cluster_identifier<br/>(terraform-aws-rds / _aurora)"]
end
subgraph mod["terraform-aws-rds-proxy"]
P["aws_db_proxy.this<br/>keystone β TLS required, IAM auth"]
TG["aws_db_proxy_default_target_group.this<br/>connection-pool config"]
T["aws_db_proxy_target.this<br/>for_each β registers backing DB"]
EP["aws_db_proxy_endpoint.this<br/>for_each β read/write split"]
end
SUBNETS --> P
SGS --> P
ROLE --> P
SECRET --> P
P --> TG
TG --> T
DB --> T
P --> EP
style P fill:#FF9900,color:#fff
| Resource | Role |
|---|---|
aws_db_proxy.this |
Keystone β the proxy itself: subnets, SGs, TLS, auth entries, idle timeout |
aws_db_proxy_default_target_group.this |
Connection-pool config (max connections %, borrow timeout, init query, pinning filters) |
aws_db_proxy_target.this |
Registers the backing RDS instance or Aurora cluster (for_each, 0 or 1) |
aws_db_proxy_endpoint.this |
Optional additional read/write or read-only endpoints (for_each over var.endpoints) |
| Requirement | Version |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/aws |
>= 6.0, < 7.0 |
No provider {} block is declared inside the module β it inherits the caller's configured provider (and credential chain / Region). See the AWS Prerequisites Region note below.
RDS Proxy is managed through the rds: IAM action namespace (it shares the RDS control plane). The two critical entries are iam:PassRole (to hand the proxy its Secrets-reading role) and the proxy role's runtime secretsmanager:GetSecretValue. The Terraform identity needs (least-privilege):
| Action | Required for | Notes |
|---|---|---|
rds:CreateDBProxy, rds:DeleteDBProxy, rds:ModifyDBProxy, rds:DescribeDBProxies |
Proxy lifecycle | Keystone resource |
rds:ModifyDBProxyTargetGroup, rds:DescribeDBProxyTargetGroups |
Default target group | Connection-pool config (created implicitly with the proxy) |
rds:RegisterDBProxyTargets, rds:DeregisterDBProxyTargets, rds:DescribeDBProxyTargets |
Target registration | Only when db_instance_identifier / db_cluster_identifier is set |
rds:CreateDBProxyEndpoint, rds:DeleteDBProxyEndpoint, rds:ModifyDBProxyEndpoint, rds:DescribeDBProxyEndpoints |
Extra endpoints | One per endpoints map entry |
rds:AddTagsToResource, rds:RemoveTagsFromResource, rds:ListTagsForResource |
Tagging | All taggable resources |
iam:PassRole |
Pass the proxy's IAM role (role_arn) to rds.amazonaws.com |
Critical. Scope to the specific role ARN |
secretsmanager:GetSecretValue, secretsmanager:DescribeSecret |
Reading the credential secret(s) | Needed by the proxy role at runtime, on each auth[*].secret_arn |
kms:Decrypt |
Decrypting a CMK-encrypted secret | Granted to the proxy role, only when the secret uses a customer-managed key |
ec2:CreateNetworkInterface, ec2:DeleteNetworkInterface, ec2:DescribeNetworkInterfaces |
ENIs the proxy plants in your subnets | Proxy-managed; describe is typically already present |
β οΈ secretsmanager:GetSecretValueandkms:Decryptbelong to the proxy's IAM role (the principalrds.amazonaws.comassumes), not to the Terraform deploy identity. The Terraform identity only needsiam:PassRoleon that role. Scope resource ARNs toarn:aws:rds:<region>:<account>:db-proxy:*and the specific secret/role ARNs rather than"*"where governance allows.
- No dedicated service-linked role. RDS Proxy does not use an SLR β it authenticates to Secrets Manager via the caller-supplied
role_arn. That IAM role must trustrds.amazonaws.comand be allowedsecretsmanager:GetSecretValue(pluskms:Decryptfor a CMK-encrypted secret) on everyauth[*].secret_arn. Wire it fromterraform-aws-iam-role. See Setting up network prerequisites and adding an IAM role and Configuring IAM authentication for RDS Proxy. - Credential secret. Each auth entry references a Secrets Manager secret holding the database user's
{ "username": "...", "password": "..." }JSON. Wire fromterraform-aws-secrets-manager. The proxy reads it at runtime β it is never a module variable. - Backing database, same VPC. The RDS instance or Aurora cluster you register must already exist, be in the same VPC as the proxy, and be reachable on its DB port. RDS Proxy supports specific engines/versions only (Aurora MySQL/PostgreSQL, RDS MySQL/MariaDB/PostgreSQL/SQL Server) β confirm with RDS Proxy region and version availability.
- Networking. Provide subnets in at least two Availability Zones (
vpc_subnet_ids, β₯ 2 β enforced by validation). The proxy plants ENIs into those subnets. Use AZs that support DB proxies β subnets in unsupported AZs can produce perpetual plan drift. The security group must permit the DB port from clients. - Region model. The module relies on provider inheritance β there is no
regionvariable. The caller's provider block (or alias) sets the Region. RDS Proxy is a regional service β none of the us-east-1 global-service rules (CloudFront / WAF / ACM) apply. - Service quotas (Quotas and limitations for RDS Proxy): default soft limit of 20 DB proxies per Region (raisable via Service Quotas), with per-proxy limits on target groups, endpoints, and registered targets. Plan endpoint fan-out within those limits.
terraform-aws-rds-proxy/
βββ providers.tf # terraform{} + required_providers (aws >= 6.0, < 7.0); no provider{} block
βββ variables.tf # typed inputs: identity β required β optional β tags β timeouts
βββ main.tf # proxy (this), default target group, target registration, extra endpoints
βββ outputs.tf # id + arn, endpoint(s), target, target-group + endpoint maps, tags_all
βββ SCOPE.md # boundary, IAM, prerequisites, gotchas, secure defaults
βββ README.md # this file
The smallest working call β networking, security, IAM, the credential secret, and the backing database all wired from upstream modules:
module "rds_proxy" {
source = "git::https://github.com/microsoftexpert/terraform-aws-rds-proxy?ref=v1.0.0"
name = "casey-core-proxy"
engine_family = "POSTGRESQL" # FORCE-NEW
role_arn = module.proxy_role.arn # trusts rds.amazonaws.com, reads the secret
vpc_subnet_ids = module.vpc.private_subnet_ids # >= 2 AZs
vpc_security_group_ids = [module.proxy_sg.id]
auth = [{
secret_arn = module.db_secret.arn # {username, password} in Secrets Manager
# iam_auth defaults to "REQUIRED" (secure baseline)
}]
db_instance_identifier = module.rds.id # register the backing RDS instance
tags = {
Environment = "prod"
DataClass = "PII"
CostCenter = "platform-data"
}
}Everything not shown inherits the secure baseline: TLS required, IAM client auth required, debug logging off, 1800s idle timeout, private subnets only.
| Input | Type | Source module |
|---|---|---|
role_arn |
string (IAM role ARN) |
terraform-aws-iam-role |
auth[*].secret_arn |
string (Secrets Manager secret ARN) |
terraform-aws-secrets-manager |
vpc_subnet_ids |
list(string) (β₯ 2 AZs) |
terraform-aws-vpc |
vpc_security_group_ids |
list(string) |
terraform-aws-security-group |
db_instance_identifier |
string (optional) |
terraform-aws-rds |
db_cluster_identifier |
string (optional) |
terraform-aws-rds-aurora |
| (CMK encrypting the secret) | by reference via the secret | terraform-aws-kms |
| Output | Description | Consumed by |
|---|---|---|
id |
Proxy id (name / ARN per the provider) | references |
arn |
Proxy ARN β cross-resource reference type | IAM policies, monitoring, AWS Backup |
name |
Proxy name | CLI / console |
endpoint |
Default proxy endpoint host | application connection string |
engine_family |
Engine family the proxy speaks | wiring / validation |
default_target_group_arn / default_target_group_name |
Default target group | references |
target |
Registered backing target (endpoint/port/rds_resource_id/type), or null |
monitoring, DR tooling |
proxy_endpoints |
Map of extra endpoint key β host | read/write splitting |
proxy_endpoint_arns / proxy_endpoint_ids |
Maps of extra endpoint key β ARN / id | references |
tags_all |
All tags incl. provider default_tags |
governance / audit |
1 Β· Minimal proxy fronting an RDS instance (secure defaults)
module "rds_proxy" {
source = "git::https://github.com/microsoftexpert/terraform-aws-rds-proxy?ref=v1.0.0"
name = "casey-proxy-min"
engine_family = "MYSQL"
role_arn = module.proxy_role.arn
vpc_subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.proxy_sg.id]
auth = [{ secret_arn = module.db_secret.arn }]
db_instance_identifier = module.rds.id
}2 Β· Aurora cluster target + read/write-split endpoints
module "rds_proxy" {
source = "git::https://github.com/microsoftexpert/terraform-aws-rds-proxy?ref=v1.0.0"
name = "casey-proxy-aurora"
engine_family = "POSTGRESQL"
role_arn = module.proxy_role.arn
vpc_subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.proxy_sg.id]
auth = [{ secret_arn = module.db_secret.arn }]
db_cluster_identifier = module.aurora.cluster_identifier # Aurora cluster, not an instance
endpoints = {
readonly = { target_role = "READ_ONLY" } # READ_ONLY requires an Aurora cluster target
}
}
# module.rds_proxy.endpoint => writer (default RW) endpoint
# module.rds_proxy.proxy_endpoints.readonly => reader endpoint
target_role = "READ_ONLY"is only valid against an Aurora cluster target β an RDS instance has no reader role.
3 Β· Tags (merge with provider default_tags)
# Provider-level default_tags is the CALLER's concern (root module / pipeline):
provider "aws" {
default_tags { tags = { ManagedBy = "terraform", Org = "" } }
}
module "rds_proxy" {
source = "git::https://github.com/microsoftexpert/terraform-aws-rds-proxy?ref=v1.0.0"
name = "casey-proxy-tagged"
engine_family = "MYSQL"
role_arn = module.proxy_role.arn
vpc_subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.proxy_sg.id]
auth = [{ secret_arn = module.db_secret.arn }]
db_instance_identifier = module.rds.id
tags = {
Environment = "prod"
DataClass = "PII"
Org = "-Data" # resource tag wins over default_tags on key conflict
}
}
# module.rds_proxy.tags_all => { ManagedBy, Org=-Data, Environment, DataClass }4 Β· Customer-managed KMS (CMK) for the credential secret
# The CMK encrypts the SECRET β the proxy module references the secret, not the key.
# The proxy ROLE must be allowed kms:Decrypt on this key.
module "kms" {
source = "git::https://github.com/microsoftexpert/terraform-aws-kms?ref=v1.0.0"
description = "CMK for RDS Proxy credential secret"
alias = "alias/casey-proxy-secret"
}
module "db_secret" {
source = "git::https://github.com/microsoftexpert/terraform-aws-secrets-manager?ref=v1.0.0"
name = "casey/proxy/db-creds"
kms_key_arn = module.kms.arn # CMK-encrypted secret
# secret_string carries {"username":"...","password":"..."}
}
module "rds_proxy" {
source = "git::https://github.com/microsoftexpert/terraform-aws-rds-proxy?ref=v1.0.0"
name = "casey-proxy-cmk"
engine_family = "POSTGRESQL"
role_arn = module.proxy_role.arn # role policy includes kms:Decrypt on module.kms.arn
vpc_subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.proxy_sg.id]
auth = [{ secret_arn = module.db_secret.arn }]
db_instance_identifier = module.rds.id
}5 Β· Connection-pool tuning (default target group)
module "rds_proxy" {
source = "git::https://github.com/microsoftexpert/terraform-aws-rds-proxy?ref=v1.0.0"
name = "casey-proxy-pool"
engine_family = "MYSQL"
role_arn = module.proxy_role.arn
vpc_subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.proxy_sg.id]
auth = [{ secret_arn = module.db_secret.arn }]
db_instance_identifier = module.rds.id
connection_pool_config = {
max_connections_percent = 75
max_idle_connections_percent = 50
connection_borrow_timeout = 120
init_query = "SET time_zone = 'UTC'"
session_pinning_filters = ["EXCLUDE_VARIABLE_SETS"] # MySQL only
}
}
EXCLUDE_VARIABLE_SETSis the only valid pinning filter, and it applies to MySQL engines only.
6 Β· Multiple auth entries (several DB users / secrets)
module "rds_proxy" {
source = "git::https://github.com/microsoftexpert/terraform-aws-rds-proxy?ref=v1.0.0"
name = "casey-proxy-multiuser"
engine_family = "POSTGRESQL"
role_arn = module.proxy_role.arn # must read BOTH secrets
vpc_subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.proxy_sg.id]
db_cluster_identifier = module.aurora.cluster_identifier
auth = [
{
secret_arn = module.app_secret.arn
username = "app_rw"
description = "Application read/write user"
},
{
secret_arn = module.report_secret.arn
username = "report_ro"
description = "Reporting read-only user"
},
]
}7 Β· IPv6 / dual-stack endpoint
module "rds_proxy" {
source = "git::https://github.com/microsoftexpert/terraform-aws-rds-proxy?ref=v1.0.0"
name = "casey-proxy-ipv6"
engine_family = "MYSQL"
role_arn = module.proxy_role.arn
vpc_subnet_ids = module.vpc.ipv6_subnet_ids
vpc_security_group_ids = [module.proxy_sg.id]
auth = [{ secret_arn = module.db_secret.arn }]
db_instance_identifier = module.rds.id
endpoint_network_type = "IPV6" # subnets must be IPv6-only
target_connection_network_type = "IPV6"
}8 Β· Custom idle timeout + provider-default auth scheme (IAM)
module "rds_proxy" {
source = "git::https://github.com/microsoftexpert/terraform-aws-rds-proxy?ref=v1.0.0"
name = "casey-proxy-iam"
engine_family = "POSTGRESQL"
role_arn = module.proxy_role.arn
vpc_subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.proxy_sg.id]
auth = [{ secret_arn = module.db_secret.arn, iam_auth = "REQUIRED" }]
db_instance_identifier = module.rds.id
default_auth_scheme = "IAM_AUTH" # default the proxy to IAM auth
idle_client_timeout = 900 # 15 minutes
}9 Β· Per-endpoint subnets, security groups, and tags
module "rds_proxy" {
source = "git::https://github.com/microsoftexpert/terraform-aws-rds-proxy?ref=v1.0.0"
name = "casey-proxy-endpoints"
engine_family = "POSTGRESQL"
role_arn = module.proxy_role.arn
vpc_subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.proxy_sg.id]
auth = [{ secret_arn = module.db_secret.arn }]
db_cluster_identifier = module.aurora.cluster_identifier
endpoints = {
analytics-ro = {
target_role = "READ_ONLY"
vpc_subnet_ids = module.vpc.analytics_subnet_ids
vpc_security_group_ids = [module.analytics_sg.id]
tags = { Team = "analytics" }
}
}
}10 Β· Register the target out of band (proxy only)
module "rds_proxy" {
source = "git::https://github.com/microsoftexpert/terraform-aws-rds-proxy?ref=v1.0.0"
name = "casey-proxy-notarget"
engine_family = "MYSQL"
role_arn = module.proxy_role.arn
vpc_subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.proxy_sg.id]
auth = [{ secret_arn = module.db_secret.arn }]
# Both db_instance_identifier and db_cluster_identifier omitted β
# no aws_db_proxy_target created; register the backing DB elsewhere.
}Leaving both identifiers
nullskips target registration entirely (targetoutput isnull).
11 Β· SQL Server engine family
module "rds_proxy" {
source = "git::https://github.com/microsoftexpert/terraform-aws-rds-proxy?ref=v1.0.0"
name = "casey-proxy-mssql"
engine_family = "SQLSERVER"
role_arn = module.proxy_role.arn
vpc_subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.proxy_sg.id]
db_instance_identifier = module.rds_mssql.id
auth = [{
secret_arn = module.db_secret.arn
client_password_auth_type = "SQL_SERVER_AUTHENTICATION"
}]
}12 Β· Operation timeouts
module "rds_proxy" {
source = "git::https://github.com/microsoftexpert/terraform-aws-rds-proxy?ref=v1.0.0"
name = "casey-proxy-timeouts"
engine_family = "MYSQL"
role_arn = module.proxy_role.arn
vpc_subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.proxy_sg.id]
auth = [{ secret_arn = module.db_secret.arn }]
db_instance_identifier = module.rds.id
timeouts = {
create = "30m"
update = "30m"
delete = "60m"
}
}13 Β· Disposable dev proxy (secure defaults relaxed)
module "rds_proxy" {
source = "git::https://github.com/microsoftexpert/terraform-aws-rds-proxy?ref=v1.0.0"
name = "casey-proxy-dev"
engine_family = "MYSQL"
role_arn = module.proxy_role.arn
vpc_subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.proxy_sg.id]
db_instance_identifier = module.rds_dev.id
# OPT-OUTS β dev only, never in prod / PII:
require_tls = false # plaintext client connections (discouraged)
debug_logging = true # logs SQL text β can expose PII
auth = [{ secret_arn = module.db_secret.arn, iam_auth = "DISABLED" }]
tags = { Environment = "dev", DataClass = "synthetic" }
}Even disposable proxies still live in private subnets only β there is no public exposure to relax.
14 Β· π End-to-end composition (VPC β SG β IAM β KMS β Secret β RDS β Proxy)
module "vpc" {
source = "git::https://github.com/microsoftexpert/terraform-aws-vpc?ref=v1.0.0"
name = "casey-data"
vpc_cidr = "10.40.0.0/16"
#... produces private_subnet_ids across >= 2 AZs
}
module "kms" {
source = "git::https://github.com/microsoftexpert/terraform-aws-kms?ref=v1.0.0"
description = "CMK for RDS Proxy credential secret"
alias = "alias/casey-proxy"
}
module "db_secret" {
source = "git::https://github.com/microsoftexpert/terraform-aws-secrets-manager?ref=v1.0.0"
name = "casey/proxy/db-creds"
kms_key_arn = module.kms.arn
# secret_string => {"username":"caseyadmin","password":"..."}
}
module "proxy_role" {
source = "git::https://github.com/microsoftexpert/terraform-aws-iam-role?ref=v1.0.0"
name = "casey-rds-proxy-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "rds.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
inline_policies = {
read-secret = jsonencode({
Version = "2012-10-17"
Statement = [
{ Effect = "Allow", Action = ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"], Resource = module.db_secret.arn },
{ Effect = "Allow", Action = ["kms:Decrypt"], Resource = module.kms.arn },
]
})
}
}
module "proxy_sg" {
source = "git::https://github.com/microsoftexpert/terraform-aws-security-group?ref=v1.0.0"
name = "casey-proxy-sg"
vpc_id = module.vpc.id
ingress_rules = {
pg = {
from_port = 5432
to_port = 5432
ip_protocol = "tcp"
referenced_security_group_id = module.app_sg.id # app tier only
description = "Postgres from application tier"
}
}
}
module "rds" {
source = "git::https://github.com/microsoftexpert/terraform-aws-rds?ref=v1.0.0"
#... a Postgres instance in module.vpc, reachable by module.proxy_sg
}
module "rds_proxy" {
source = "git::https://github.com/microsoftexpert/terraform-aws-rds-proxy?ref=v1.0.0"
name = "casey-core-proxy"
engine_family = "POSTGRESQL"
role_arn = module.proxy_role.arn
vpc_subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.proxy_sg.id]
auth = [{ secret_arn = module.db_secret.arn }] # iam_auth REQUIRED by default
db_instance_identifier = module.rds.id
connection_pool_config = { max_connections_percent = 80 }
tags = {
Environment = "prod"
DataClass = "PII"
Compliance = "privacy-regulation"
}
}
# The application points its connection string at the proxy, not the database:
output "proxy_endpoint" { value = module.rds_proxy.endpoint }| Name | Type | Default | Description |
|---|---|---|---|
name |
string |
β (required) | Proxy identifier. FORCE-NEW. Letter-led, ASCII letters/digits/hyphens |
engine_family |
string |
β (required) | MYSQL / POSTGRESQL / SQLSERVER. FORCE-NEW |
role_arn |
string |
β (required) | IAM role the proxy assumes to read the secret (trusts rds.amazonaws.com) |
vpc_subnet_ids |
list(string) |
β (required) | Proxy subnets, β₯ 2 AZs. FORCE-NEW |
auth |
list(object) |
β (required) | Auth entries β at least one; each references a secret_arn |
vpc_security_group_ids |
list(string) |
[] |
Security groups to attach |
endpoint_network_type |
string |
"IPV4" |
IPV4 / IPV6 / DUAL |
target_connection_network_type |
string |
"IPV4" |
IPV4 / IPV6 |
require_tls |
bool |
true |
Require TLS for client connections (secure default) |
default_auth_scheme |
string |
"NONE" |
NONE / IAM_AUTH default scheme |
idle_client_timeout |
number |
1800 |
Idle seconds before disconnect (1β28800) |
debug_logging |
bool |
false |
Log SQL statement detail (can expose PII) |
connection_pool_config |
object |
{} |
Default target group pool tuning |
db_instance_identifier |
string |
null |
Backing RDS instance to register. FORCE-NEW on registration |
db_cluster_identifier |
string |
null |
Backing Aurora cluster to register. FORCE-NEW on registration |
endpoints |
map(object) |
{} |
Additional proxy endpoints keyed by stable name |
tags |
map(string) |
{} |
Tags merged onto all taggable resources |
timeouts |
object |
{} |
create / update / delete timeouts |
βΉοΈ Full type schemas and per-field descriptions live in
variables.tf;
See the Emits table above. Primary outputs are id and arn; endpoint is the host you put in the application connection string; target carries the registered backing-database details (or null when none is registered); proxy_endpoints / proxy_endpoint_arns / proxy_endpoint_ids are maps keyed by your endpoints keys; tags_all reflects the merged tag set. No secrets are emitted.
- ARN / ID formats.
- Proxy
arn:arn:aws:rds:<region>:<account>:db-proxy:prx-<hash>. The provider'sidforaws_db_proxyis the proxy name. - Proxy endpoint
arn:arn:aws:rds:<region>:<account>:db-proxy-endpoint:prx-endpoint-<hash>; itsidisDB-PROXY-NAME/DB-PROXY-ENDPOINT-NAME. target.rds_resource_idis the backing database's stable resource id (db-XXXX/cluster-XXXX) β use it (not the name) for IAMrds-db:connectpolicy conditions when clients use IAM database auth.rds:namespace. RDS Proxy rides the RDS control plane β every API call, ARN service segment, and IAM action usesrds, not aproxynamespace. Write least-privilege policies accordingly.- FORCE-NEW (immutable) fields.
name,engine_family, andvpc_subnet_idsrecreate the proxy.db_instance_identifier/db_cluster_identifierforce replacement of the target registration (and the two are mutually exclusive β a validation blocks setting both). The module'saws_db_proxy_default_target_groupandaws_db_proxy_targetcarryreplace_triggered_by = [aws_db_proxy.this.id]so they track a proxy replacement cleanly. tagsβtags_allβdefault_tags. The module sets only resource-leveltags(and mergesmerge(var.tags, each.value.tags)onto each extra endpoint). The provider'sdefault_tagsis the caller's concern (never set inside a module). On a key collision, the resource tag wins.tags_all(output) is the computed union AWS actually applied β use it for drift checks and audit.- Credentials never in state. Unlike a database module's master password, the proxy holds no secret material. It references
auth[*].secret_arnand reads{username, password}at runtime viarole_arn. Nothing sensitive is a variable or an output. - Eventual consistency / ordering. Target registration requires the proxy and the backing DB to be available β register after the DB is healthy or you can hit transient
DBProxyTargetNotFound/InvalidDBInstanceStateerrors. Endpoint and target creation is asynchronous;endpointis a stable DNS name that follows failover. - Destroy ordering. Terraform tears down extra endpoints and the target registration before the proxy, then the proxy releases its ENIs from your subnets. ENI cleanup is asynchronous on the AWS side β a security group or subnet that still has proxy-owned ENIs attached can briefly resist deletion until RDS reclaims them. Subnets/SGs are consumed by reference, so the module does not delete them.
- No us-east-1 constraint. RDS Proxy is a regional service β none of the us-east-1 global-service rules (CloudFront / WAF / ACM) apply.
Secure by default; every weakening is an explicit, documented opt-out.
| Posture | Default | How to opt out |
|---|---|---|
| In-transit TLS | require_tls = true |
require_tls = false (discouraged) |
| Client IAM auth | auth[*].iam_auth = "REQUIRED" |
"DISABLED" (falls back to secret-password client auth) |
| Debug (SQL) logging | debug_logging = false |
true (temporary, documented β can expose PII in logs) |
| Idle client timeout | idle_client_timeout = 1800 (30 min) |
tune 1β28800s via the variable |
| Public exposure | private subnets only β no public option | n/a (cannot be relaxed) |
| Credentials in state | never β read at runtime from Secrets Manager | n/a (no plaintext-credential path exists) |
Other principles: exactly four .tf files; single keystone aws_db_proxy.this; child collections via for_each over map(object) / a keyed toset (never count); deeply-typed object schemas with optional defaults; validation {} on every closed value set (engine family, auth scheme, IAM auth, pinning filters, percent ranges, instance/cluster mutual exclusion); no credential or region variables; primary outputs id + arn; tags_all surfaced.
# Validate (no credentials needed)
terraform init -backend=false
terraform validate
terraform fmt -check
# Plan / apply (requires AWS credentials + Region)
# credentials via AWS_PROFILE / SSO / OIDC; Region via the provider block
terraform plan -out tfplan
terraform apply tfplan
β οΈ Pin the module with?ref=v1.0.0β never a branch.plan/applyrequire a valid credential chain (profile / SSO / OIDC web-identity) and a configured Region. The module declares noprovider {}block β supply it (and anyassume_role) at the root.
terraform init -backend=false && terraform validateβ schema and reference integrity.terraform fmt -checkβ canonical formatting.terraform planagainst a sandbox account β confirm secure defaults render (require_tls = true, per-useriam_auth = "REQUIRED",debug_logging = false, 1800s idle timeout) and that omitting both DB identifiers produces no target.- Post-apply smoke test: from a client in an allowed security group, connect to
endpointover TLS and run a trivial query (SELECT 1); confirm pooling under load and that aREAD_ONLYendpoint (if defined) routes to Aurora readers.
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Outputs:
arn = "arn:aws:rds:us-east-2:123456789012:db-proxy:prx-0a1b2c3d4e5f67890"
endpoint = "casey-core-proxy.proxy-abc123.us-east-2.rds.amazonaws.com"
id = "casey-core-proxy"
proxy_endpoints = {
"readonly" = "casey-core-proxy-readonly.endpoint.proxy-abc123.us-east-2.rds.amazonaws.com"
}
"endpoint" = "casey-core-pg.abc123.us-east-2.rds.amazonaws.com"
"port" = 5432
"rds_resource_id" = "db-ABCDEFGHIJKLMNOP"
"type" = "RDS_INSTANCE"
}
| Symptom | Likely cause | Fix |
|---|---|---|
| Tag drift on every plan | default_tags overlaps a key the module also sets |
Drop the duplicate from one side; resource tags win β reconcile in the root module |
AccessDenied on rds:CreateDBProxy |
Identity lacks the rds: actions (RDS Proxy uses rds, not a proxy namespace) |
Attach the Required IAM Permissions |
AccessDenied / iam:PassRole on create |
Terraform identity can't pass role_arn to RDS |
Grant iam:PassRole on the specific role ARN to the deploy identity |
| Proxy creates but stays unavailable / can't reach secret | Proxy role doesn't trust rds.amazonaws.com or lacks secretsmanager:GetSecretValue (or kms:Decrypt) |
Fix the role's trust policy and grant the secret/KMS permissions on each auth[*].secret_arn |
DBProxyTargetNotFound / InvalidDBInstanceState on register |
Backing DB not yet available, or in a different VPC | Register after the DB is healthy; ensure same-VPC reachability |
| Client connection refused / TLS handshake fails | require_tls = true but client not using TLS |
Connect over TLS (or, dev only, set require_tls = false) |
Perpetual diff on vpc_subnet_ids |
A subnet sits in an AZ that doesn't support DB proxies | Restrict vpc_subnet_ids to supported AZs |
Set only one of db_instance_identifier or db_cluster_identifier |
Both target identifiers supplied | Pick instance or cluster β they're mutually exclusive |
READ_ONLY endpoint rejected |
Endpoint target_role = "READ_ONLY" against an RDS instance |
READ_ONLY requires an Aurora cluster target |
| Security group / subnet won't delete on destroy | Proxy ENIs not yet reclaimed by RDS | Wait for RDS to release the ENIs, then retry the destroy |
DBProxyQuotaExceeded |
Region soft limit (default 20 proxies) hit | Raise the quota via Service Quotas |
| Credential-chain errors on plan/apply | No profile/SSO/OIDC resolved, or wrong Region | Set AWS_PROFILE / assume the role; confirm the provider Region |
- Amazon RDS Proxy β User Guide
- Setting up network prerequisites and an IAM role for RDS Proxy
- Configuring IAM authentication for RDS Proxy
- Managing an RDS Proxy
- Terraform
aws_db_proxy - Terraform
aws_db_proxy_default_target_group - Terraform
aws_db_proxy_target - Terraform
aws_db_proxy_endpoint - Module
SCOPE.mdβ boundary, IAM, prerequisites, gotchas - Upstream:
terraform-aws-vpc,terraform-aws-security-group,terraform-aws-iam-role,terraform-aws-secrets-manager,terraform-aws-kms,terraform-aws-rds,terraform-aws-rds-aurora
π§‘ "Infrastructure as Code should be standardized, consistent, and secure."