How to Migrate AWS RDS MySQL from Default VPC to Custom VPC: A Step-by-Step Guide


2 views

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:

  1. Creating a read replica in the target VPC
  2. Taking a snapshot and restoring to new VPC
  3. 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:

  1. Test connectivity from application servers
  2. Verify replication if using read replicas
  3. 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:

  1. Create a manual snapshot of your current RDS instance:
  2. aws rds create-db-snapshot \
        --db-instance-identifier original-instance \
        --db-snapshot-identifier migration-snapshot
    
  3. Wait for snapshot completion (check status via CLI):
  4. aws rds describe-db-snapshots \
        --db-snapshot-identifier migration-snapshot \
        --query 'DBSnapshots[0].Status'
    
  5. Restore snapshot to new VPC:
  6. 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