How to Secure RDS Database Access in AWS

Complete guide to securing Amazon RDS databases by removing public accessibility, configuring security groups, and implementing VPC-only access.

9 min readUpdated 2026-01-13

Securing your Amazon RDS database is critical for protecting sensitive data. Publicly accessible databases are prime targets for attackers who scan the internet for open database ports. This guide walks you through removing public access, configuring proper security groups, and implementing defense-in-depth for your RDS instances.

This article is part of our comprehensive Cloud Security Tips for 2026 guide covering essential practices for protecting your cloud environment.

Common RDS Security Mistakes

These configurations leave databases vulnerable:

  • Public accessibility enabled - Database has a public IP address
  • 0.0.0.0/0 in security groups - Allows connections from any IP
  • Default security groups - Often overly permissive
  • Database in public subnet - Exposed to internet routing
  • Unencrypted connections - Data transmitted in clear text

Step 1: Remove Public Accessibility

Using AWS Console

  1. Open the RDS Console
  2. Select your database instance
  3. Click Modify
  4. Under Connectivity, find Additional configuration
  5. Set Publicly accessible to No
  6. Click Continue
  7. Choose Apply immediately or schedule for maintenance window
  8. Click Modify DB Instance

Using AWS CLI

# Remove public accessibility
aws rds modify-db-instance \
  --db-instance-identifier my-database \
  --no-publicly-accessible \
  --apply-immediately

# Verify the change
aws rds describe-db-instances \
  --db-instance-identifier my-database \
  --query 'DBInstances[0].PubliclyAccessible'

Note: This change may cause brief connectivity interruption as the public IP is released.


Step 2: Configure Security Groups

Security groups act as virtual firewalls controlling traffic to your database.

Create a Dedicated RDS Security Group

# Create security group
aws ec2 create-security-group \
  --group-name rds-database-sg \
  --description "Security group for RDS databases" \
  --vpc-id vpc-12345678

# Output: sg-0123456789abcdef0

Configure Inbound Rules

Allow only necessary sources to connect:

# Allow from application security group only
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 5432 \
  --source-group sg-app-servers

# Allow from specific CIDR (private subnet only)
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 5432 \
  --cidr 10.0.1.0/24

Apply Security Group to RDS

aws rds modify-db-instance \
  --db-instance-identifier my-database \
  --vpc-security-group-ids sg-0123456789abcdef0 \
  --apply-immediately

Security Group Best Practices

DoDon't
Reference other security groupsUse 0.0.0.0/0 (any IP)
Use specific CIDR rangesAllow all ports
Document rule purposesUse default security groups
Regularly audit rulesLeave unused rules

Step 3: Place RDS in Private Subnet

Ensure your RDS instance is in a subnet without internet gateway routing.

Create DB Subnet Group

# Create subnet group with private subnets only
aws rds create-db-subnet-group \
  --db-subnet-group-name private-db-subnets \
  --db-subnet-group-description "Private subnets for RDS" \
  --subnet-ids subnet-private-1a subnet-private-1b subnet-private-1c

Move Existing RDS to Private Subnet

aws rds modify-db-instance \
  --db-instance-identifier my-database \
  --db-subnet-group-name private-db-subnets \
  --apply-immediately

Important: Changing subnet groups may cause downtime. Schedule during maintenance windows.


Step 4: Enable Encryption

Encryption at Rest

For new databases, enable encryption during creation:

aws rds create-db-instance \
  --db-instance-identifier my-secure-database \
  --db-instance-class db.t3.medium \
  --engine postgres \
  --master-username admin \
  --master-user-password "SecurePassword123!" \
  --storage-encrypted \
  --kms-key-id alias/rds-encryption-key

Encryption in Transit (SSL/TLS)

Force SSL connections via parameter groups:

# Create parameter group
aws rds create-db-parameter-group \
  --db-parameter-group-name secure-postgres-params \
  --db-parameter-group-family postgres15 \
  --description "Secure PostgreSQL parameters"

# Require SSL
aws rds modify-db-parameter-group \
  --db-parameter-group-name secure-postgres-params \
  --parameters "ParameterName=rds.force_ssl,ParameterValue=1,ApplyMethod=pending-reboot"

# Apply to database
aws rds modify-db-instance \
  --db-instance-identifier my-database \
  --db-parameter-group-name secure-postgres-params

Step 5: Set Up Secure Access Patterns

Option A: Bastion Host (SSH Tunnel)

# Create SSH tunnel through bastion
ssh -L 5432:my-database.cluster-xyz.us-east-1.rds.amazonaws.com:5432 \
  [email protected]

# Connect via tunnel
psql -h localhost -p 5432 -U admin -d mydb

Option B: AWS Systems Manager Session Manager

# Start port forwarding session
aws ssm start-session \
  --target i-0123456789abcdef0 \
  --document-name AWS-StartPortForwardingSessionToRemoteHost \
  --parameters '{
    "host":["my-database.cluster-xyz.us-east-1.rds.amazonaws.com"],
    "portNumber":["5432"],
    "localPortNumber":["5432"]
  }'

Option C: AWS Client VPN

For frequent access, set up Client VPN to connect directly to your VPC.


Step 6: Enable IAM Database Authentication

Use AWS IAM instead of passwords for authentication:

# Enable IAM auth
aws rds modify-db-instance \
  --db-instance-identifier my-database \
  --enable-iam-database-authentication \
  --apply-immediately

# Create database user for IAM auth (run in database)
# PostgreSQL:
CREATE USER iam_user WITH LOGIN;
GRANT rds_iam TO iam_user;

# Generate auth token
aws rds generate-db-auth-token \
  --hostname my-database.cluster-xyz.us-east-1.rds.amazonaws.com \
  --port 5432 \
  --region us-east-1 \
  --username iam_user

# Connect using token
PGPASSWORD=$(aws rds generate-db-auth-token ...) psql -h hostname -U iam_user

Step 7: Enable Logging and Monitoring

# Enable enhanced monitoring
aws rds modify-db-instance \
  --db-instance-identifier my-database \
  --monitoring-interval 60 \
  --monitoring-role-arn arn:aws:iam::123456789012:role/rds-monitoring-role

# Enable Performance Insights
aws rds modify-db-instance \
  --db-instance-identifier my-database \
  --enable-performance-insights \
  --performance-insights-retention-period 7

# Enable audit logging (PostgreSQL)
aws rds modify-db-parameter-group \
  --db-parameter-group-name secure-postgres-params \
  --parameters "ParameterName=log_statement,ParameterValue=all,ApplyMethod=immediate"

Export logs to CloudWatch:

aws rds modify-db-instance \
  --db-instance-identifier my-database \
  --cloudwatch-logs-export-configuration '{"EnableLogTypes":["postgresql","upgrade"]}'

Security Audit Checklist

Run this audit regularly:

# Check all RDS instances for public accessibility
aws rds describe-db-instances \
  --query 'DBInstances[*].[DBInstanceIdentifier,PubliclyAccessible,Endpoint.Address]' \
  --output table

# Find security groups with 0.0.0.0/0
aws ec2 describe-security-groups \
  --query 'SecurityGroups[?IpPermissions[?IpRanges[?CidrIp==`0.0.0.0/0`]]].[GroupId,GroupName]' \
  --output table

# Check encryption status
aws rds describe-db-instances \
  --query 'DBInstances[*].[DBInstanceIdentifier,StorageEncrypted]' \
  --output table

Terraform Example

Infrastructure-as-code for secure RDS:

resource "aws_db_instance" "secure_database" {
  identifier           = "secure-database"
  engine              = "postgres"
  engine_version      = "15.4"
  instance_class      = "db.t3.medium"
  allocated_storage   = 100

  # Security settings
  publicly_accessible    = false
  storage_encrypted      = true
  kms_key_id            = aws_kms_key.rds.arn

  # Network
  db_subnet_group_name   = aws_db_subnet_group.private.name
  vpc_security_group_ids = [aws_security_group.rds.id]

  # Authentication
  iam_database_authentication_enabled = true

  # Monitoring
  enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"]
  monitoring_interval             = 60
  monitoring_role_arn            = aws_iam_role.rds_monitoring.arn

  # Maintenance
  backup_retention_period = 30
  deletion_protection     = true
}

Best Practices Summary

ControlRecommendation
Public AccessAlways disable publicly accessible
Security GroupsReference source security groups, not IP ranges
SubnetsUse private subnets only
EncryptionEnable at-rest and in-transit encryption
AuthenticationUse IAM auth for AWS workloads
SecretsStore credentials in Secrets Manager
MonitoringEnable enhanced monitoring and logging

Frequently Asked Questions

Find answers to common questions

Check the Publicly Accessible setting in the RDS console under the database's Connectivity & security tab. Using AWS CLI, run aws rds describe-db-instances and look for the PubliclyAccessible field. If true, the database has a public IP. Additionally, check if security groups allow 0.0.0.0/0 (all IP addresses) on the database port. Both conditions make your database reachable from the internet.

Need Professional Help?

Our team of experts can help you implement and configure these solutions for your organization.