---
cuppy_hub: aws
cuppy_path: guides/rds/connect-securely.md
cuppy_section: docs
cuppy_doc: EZT7-PVEX
cuppy_doc_uuid: cbfc20c6-17b0-464a-95ad-f4db048aff9a
cuppy_permalink: https://cuppy.io/aws/d/EZT7-PVEX
cuppy_version: 4
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/aws/docs/guides/rds/connect-securely
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 20e8519904acd3ad2e11038b8bf22cf1d4791aa13e74aaca2a7f657b71279d04
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# Connect an application to RDS or Aurora securely

**Get the network right first: the database's security group must admit the app's security group on the engine port.** Then pick credentials: IAM database authentication (a 15-minute token, no stored password) or a Secrets Manager secret. Put RDS Proxy in between when many short-lived clients connect, as Lambda functions do.

## 1. Network path

- **App in the same VPC (the normal case):** the database is *not* publicly accessible. Its security group allows the port (5432 PostgreSQL, 3306 MySQL) from the app's security group (`reference/vpc/security-groups-and-nacls.md`).
- **From outside the VPC:** this requires **Public accessibility = Yes**, subnets routed to an internet gateway, and a security group rule for your IP. Prefer a bastion or Session Manager port forwarding instead (UNVERIFIED alternative; not in the collected RDS pages).
- RDS doesn't answer ping. Test with `nc -zv <endpoint> <port>`. Addresses in `169.254.0.0/16` can't connect.
- A new instance is unreachable while `creating`, which can take up to 20 minutes.

## 2a. IAM database authentication (MariaDB, MySQL, PostgreSQL)

1. Enable IAM authentication on the instance or cluster.
2. Create the database user: on PostgreSQL, `CREATE USER app_user; GRANT rds_iam TO app_user;`. With `rds_iam`, IAM auth takes precedence over a password. MySQL needs the AWS authentication plugin (UNVERIFIED syntax in the collected sources).
3. Allow the caller `rds-db:connect` on `arn:aws:rds-db:<region>:<account>:dbuser:<DbiResourceId>/app_user`:
   - **DbiResourceId** (`db-…`) is the instance's resource ID. Find it with `aws rds describe-db-instances --query "DBInstances[*].[DBInstanceIdentifier,DbiResourceId]"`.
   - Aurora uses the **DbClusterResourceId**.
   - Through RDS Proxy, use the **proxy's** ID (`prx-…`).
4. Generate a token and use it as the password over TLS:

```ts
import { Signer } from "@aws-sdk/rds-signer";
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";

// Option A: IAM database authentication — a 15-minute token used as the password.
const signer = new Signer({
  hostname: "<instance-or-proxy endpoint>", // the RDS or RDS Proxy endpoint, not a custom DNS name
  port: 5432,
  username: "app_user",                     // a DB user granted rds_iam (PostgreSQL) or the AWS auth plugin (MySQL)
  region: "<region>",
});
const token = await signer.getAuthToken();
// pass { host, port, user: "app_user", password: token, ssl: { rejectUnauthorized: true } } to your driver

// Option B: credentials from Secrets Manager (cache them; don't call per query).
const sm = new SecretsManagerClient({ region: "<region>" });
const secret = await sm.send(new GetSecretValueCommand({ SecretId: "<secret name or ARN>" }));
const { username, password } = JSON.parse(secret.SecretString ?? "{}") as { username: string; password: string };
console.log(token.length > 0, username, password ? "***" : "");
```

Limits worth knowing:

- Tokens last **15 minutes** and are needed only at connect time: an open connection stays open.
- Tokens are about 1 KB or more, so the driver mustn't truncate them.
- You can't generate a token against a **custom Route 53 name**: use the RDS endpoint.
- Some global condition keys (`aws:SourceIp`, `aws:SourceVpc`, `aws:SourceVpce`) aren't supported for `rds-db:connect`.
- IAM auth uses 300–1000 MiB of extra database memory.
- Token generation and IAM logins aren't logged in CloudTrail or CloudWatch.
- PostgreSQL can't combine IAM auth with Kerberos.

## 2b. Secrets Manager

Store `username` and `password` in a secret. Grant the app `secretsmanager:GetSecretValue` on that secret, plus `kms:Decrypt` if it uses a customer managed key (`reference/kms/key-policies-and-errors.md`). Cache the value, and reconnect with a fresh read when authentication fails after a rotation. Turning rotation on (RDS-managed master passwords or a Lambda function) and its failure strings: `guides/secrets-manager/rotate-secrets.md`.

## 3. RDS Proxy (pooling)

Use it when many clients open short connections, such as Lambda bursts and serverless apps:

- It pools and reuses connections, and queues or throttles new ones beyond its limits instead of overwhelming the database.
- It keeps application connections open across failover to a standby.
- Clients can be required to use IAM auth to the proxy. The proxy reaches the database with IAM auth or Secrets Manager secrets (up to 200).
- The proxy is **in the database's VPC and never public**. It isn't supported with dedicated-tenancy VPCs or VPC Encryption Controls in enforce mode.
- **Pinning:** statements over 16 KB, and some session-state changes, pin a client to one database connection, which reduces pooling.
- Don't modify or delete the `rdsproxyadmin` database user. Parameter group changes need a database reboot before the proxy sees them.

Lambda-specific wiring: `patterns/lambda-to-rds.md`.

## Check

```
nc -zv <endpoint> 5432
aws rds describe-db-instances --db-instance-identifier <id> \
  --query 'DBInstances[0].[DBInstanceStatus,PubliclyAccessible,VpcSecurityGroups,IAMDatabaseAuthenticationEnabled]'
```

Errors by message: `reference/rds/errors.md`.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 131 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/aws/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
