When you create an RDS instance without specifying a VPC, AWS automatically places it in the default VPC. This becomes problematic when:
- Your existing EC2 instances reside in custom VPCs
- You need to apply specific security group rules
- You want to maintain network isolation standards
AWS RDS doesn't allow direct VPC modification for an existing instance. The only supported methods are:
- Creating a read replica in the target VPC
- Taking a snapshot and restoring to new VPC
- Using AWS Database Migration Service (DMS)
Here's the most reliable method using snapshots:
# Create final snapshot of current RDS
aws rds create-db-snapshot \
--db-instance-identifier original-instance \
--db-snapshot-identifier migration-snapshot
# Wait for snapshot completion (check AWS Console)
aws rds wait db-snapshot-completed \
--db-snapshot-identifier migration-snapshot
# Restore to new VPC
aws rds restore-db-instance-from-db-snapshot \
--db-instance-identifier new-instance \
--db-snapshot-identifier migration-snapshot \
--db-subnet-group-name custom-subnet-group \
--vpc-security-group-ids sg-12345678
Prepare these before migration:
- Create DB subnet group in target VPC
- Configure route tables and NACLs
- Set up proper security groups
Example security group rule for MySQL access:
aws ec2 authorize-security-group-ingress \
--group-id sg-12345678 \
--protocol tcp \
--port 3306 \
--cidr 10.0.1.0/24
After migration, update your application configuration to use the new endpoint. The RDS DNS name will change. Consider using Route 53 for seamless transition:
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890 \
--change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "database.example.com",
"Type": "CNAME",
"TTL": 300,
"ResourceRecords": [{ "Value": "new-instance.xyz.us-west-2.rds.amazonaws.com" }]
}
}]
}'
After migration completes:
- Test connectivity from application servers
- Verify replication if using read replicas
- Check performance metrics in CloudWatch
Use this MySQL connection test:
mysql -h new-instance.xyz.us-west-2.rds.amazonaws.com \
-u admin \
-p \
-e "SHOW DATABASES;"
Once confirmed working:
# Delete old instance (after taking final backup)
aws rds delete-db-instance \
--db-instance-identifier original-instance \
--skip-final-snapshot \
--delete-automated-backups
When you create an Amazon RDS MySQL instance without specifying a VPC, AWS automatically places it in the default VPC. This becomes problematic when:
- You need to connect from EC2 instances in custom VPCs
- You want to apply existing security groups
- Your network architecture requires specific subnet configurations
AWS doesn't allow modifying the VPC of an existing RDS instance through the console or API. This is because VPC membership affects fundamental networking properties like:
-
- ENI (Elastic Network Interface) configuration
- Subnet associations
- Security group attachments
- Route table dependencies
Here's the proven workflow to move your RDS instance:
- Create a manual snapshot of your current RDS instance:
- Wait for snapshot completion (check status via CLI):
- Restore snapshot to new VPC:
aws rds create-db-snapshot \
--db-instance-identifier original-instance \
--db-snapshot-identifier migration-snapshot
aws rds describe-db-snapshots \
--db-snapshot-identifier migration-snapshot \
--query 'DBSnapshots[0].Status'
aws rds restore-db-instance-from-db-snapshot \
--db-instance-identifier new-instance \
--db-snapshot-identifier migration-snapshot \
--db-subnet-group-name custom-subnet-group \
--vpc-security-group-ids sg-12345678
When restoring, pay special attention to:
- Subnet Group: Must belong to target VPC
- Security Groups: Must exist in target VPC
- Parameter Groups: Can be reused if compatible
For minimal downtime, implement this Python script using Boto3:
import boto3
import time
def migrate_rds_vpc(source_instance, target_vpc):
rds = boto3.client('rds')
# Create snapshot
snapshot_id = f"{source_instance}-migration-{int(time.time())}"
rds.create_db_snapshot(
DBInstanceIdentifier=source_instance,
DBSnapshotIdentifier=snapshot_id
)
# Wait for snapshot
waiter = rds.get_waiter('db_snapshot_completed')
waiter.wait(DBSnapshotIdentifier=snapshot_id)
# Get target VPC components
ec2 = boto3.client('ec2')
subnets = ec2.describe_subnets(Filters=[
{'Name': 'vpc-id', 'Values': [target_vpc]},
{'Name': 'default-for-az', 'Values': ['true']}
])
# Restore instance
response = rds.restore_db_instance_from_db_snapshot(
DBInstanceIdentifier=f"{source_instance}-new",
DBSnapshotIdentifier=snapshot_id,
DBSubnetGroupName='custom-subnet-group',
VpcSecurityGroupIds=['sg-12345678']
)
return response['DBInstance']['DBInstanceIdentifier']
- Update all application connection strings
- Configure DNS CNAME records if using custom endpoints
- Set up CloudWatch alarms for the new instance
- Delete old instance after verification (don't forget final snapshot)
For larger databases where snapshot time is prohibitive:
# Create cross-VPC read replica
aws rds create-db-instance-read-replica \
--db-instance-identifier replica-instance \
--source-db-instance-identifier original-instance \
--db-subnet-group-name target-subnet-group
# Promote to standalone
aws rds promote-read-replica \
--db-instance-identifier replica-instance