AWS Database Blog
Migrate self-managed PostgreSQL to Amazon RDS using the RDS console
Running PostgreSQL on Amazon Elastic Compute Cloud (Amazon EC2) gives you full control over the database engine and operating system. But it also means you own every operational task: patching, backups, replication, failover, storage management, and security hardening. This overhead grows with your workload.
Migrating to Amazon Relational Database Service (Amazon RDS) for PostgreSQL or Amazon Aurora PostgreSQL-Compatible Edition offloads these responsibilities to a fully managed service. You get automated backups, point-in-time recovery, automated failover, automatic storage scaling, and built-in security features including encryption, IAM database authentication, and AWS Secrets Manager integration. This frees your team to focus on application development rather than database administration.
In this post, we show you how to migrate a self-managed PostgreSQL database running on Amazon EC2 to Amazon RDS for PostgreSQL or Amazon Aurora PostgreSQL-Compatible Edition. The migration uses the auto-migration capability built into the Amazon RDS console and powered by AWS Database Migration Service (AWS DMS).
Because this is a PostgreSQL-to-PostgreSQL migration, AWS DMS uses its homogeneous data migration capability, which uses PostgreSQL’s own native tools rather than a general-purpose replication engine. This means the migration precisely maps your source schema, data types, table partitions, and secondary objects such as functions and stored procedures to the target. This typically results in a substantially faster migration than heterogeneous DMS migration tasks.
Why the RDS console migration feature?
Several approaches exist for migrating PostgreSQL from EC2 to a managed service:
- pg_dump/pg_restore — Simple but requires full downtime for the duration of the dump and restore. No built-in change data capture (CDC).
- Manual AWS DMS task — Full control over replication settings, selection rules, and transformations, but requires manually configuring replication instances, endpoints, and tasks.
- Third-party tools (such as Bucardo, pgloader) — May suit specific use cases but lack native AWS integration and managed monitoring.
The RDS console approach is ideal when you want the reliability of DMS with minimal setup effort, particularly for straightforward PostgreSQL-to-PostgreSQL migrations that don’t require table filtering or custom transformations.
Migration process overview
AWS DMS homogeneous data migrations use PostgreSQL’s native tooling in a serverless environment and support three replication modes.

- Full load: Best for small databases or when a maintenance window is acceptable. Target is unavailable during restore.
- CDC only: Use when schema/data already exists on target and you only need to replicate ongoing changes. Minimal downtime (brief cutover).
- Full load + CDC: Best for production migrations needing minimal downtime. Performs initial sync through logical replication, then captures ongoing changes until cutover.
Note: Migration duration depends on database size, network throughput, and write activity on the source. The sample database in this walkthrough completes in minutes. For production workloads, we recommend running a test migration to estimate timing and plan your cutover window accordingly.
Prerequisites
Before starting the migration, make sure you have the following:
- Source: PostgreSQL 10.4+ running on Amazon EC2. See supported sources for homogeneous migrations.
- Target: RDS for PostgreSQL or Aurora PostgreSQL (same or higher version). See Creating an RDS DB instance or Creating an Aurora DB cluster.
- IAM role granting DMS access to databases, Secrets Manager, and Amazon CloudWatch. See Creating IAM resources for homogeneous data migrations.
- Secrets Manager: Source and target credentials stored as secrets.
- Networking: Security groups allowing TCP 5432 between EC2 source, and RDS/Aurora target.
- Source database user: Superuser (for CDC) or SELECT-only (for full load). Add the DMS IP address to
pg_hba.conf. - Target database user:
rds_superuserrole with CREATE, SELECT, INSERT, UPDATE, DELETE, TRUNCATE. - Target parameter group:
rds.logical_replication = 1(requires reboot).
For complete setup details, see Setting up for homogeneous data migrations and Using PostgreSQL as a DMS source.
Considerations and limitations
Although the RDS console migration feature simplifies the process significantly, there are important considerations to be aware of before starting your migration.
- Unsupported objects: Tablespaces, roles, extensions, operator classes, and event triggers are not migrated.
- Large objects:
byteacolumns are supported. However,lo_*large objects are not replicated through logical replication. - Sequence values: Sequences might not reflect the latest
nextvalafter migration. RunSELECT setval()post-cutover. - Reboot required: Enabling
rds.logical_replicationon the target requires a reboot. - WAL retention: The source retains the write-ahead log (WAL) until consumed. Monitor disk usage on write-heavy databases.
- DDL changes: Avoid schema changes (DDL) on the source during CDC. They are not auto-replicated and require a migration restart to pick up new tables.
- No selection rules: The RDS console auto-migrate feature migrates all tables. Filtering is not supported.
For the full list of limitations, see Limitations for DMS homogeneous data migrations.
Perform homogeneous migration
The following sections walk you through preparing the source database, storing credentials, configuring the target, running the migration, and verifying the results.
Source database setup
Modify the following settings at the parameter group level, which is mapped to the database:
wal_level– Set tological.max_replication_slots– Set to a value greater than 1.max_wal_senders– Set to a value greater than 1.wal_sender_timeout– Set the value to 0. This parameter ends replication connections that are inactive longer than the specified number of milliseconds. The default is 60,000 milliseconds (60 seconds). Setting the value to 0 disables the timeout mechanism.
After you update the preceding parameters, restart your PostgreSQL source database.
Connect to PostgreSQL on the EC2 instance and create a database called testmigratedb:
Create source secrets on AWS Secrets Manager
AWS DMS uses AWS Secrets Manager to securely store and retrieve database credentials for both the source and target endpoints during migration.
Source secret configuration:
- Secret name:
postgres-ec2-source-secret. - Username:
dms_user. - Password:
[PASSWORD]!. - Server address: EC2 private IP address.
- Database name:
testmigratedb. - Port:
5432(PostgreSQL default).
Target secret: Auto-created with the RDS instance.
Configure target database permissions
AWS DMS requires certain permissions to migrate data to your target RDS for PostgreSQL or Aurora PostgreSQL database.
Connect to your target RDS or Aurora instance and run the following script:
Set rds.logical_replication = 1 in the target DB parameter group and reboot the instance. This is a static parameter required for the publisher-subscriber replication model used by DMS homogeneous migrations.
Execute migration using the RDS console
Complete the following steps:
- Navigate to the RDS console Databases page.
- Select the target RDS or Aurora database instance.
- Choose Data migrations tab → Create data migration.

- Configure the source (EC2 PostgreSQL).


- Configure the target (RDS PostgreSQL).

- Select the migration type (Full load or Full load and CDC).


- View migration settings.

- Choose Migrate and verify the success notification or review any error messages with reasons.


- Monitor progress through CloudWatch logs.
- Verify completion.

Verify migration
After the migration completes, connect to the target RDS instance and confirm that all tables, row counts, and data match the source.
postgres=> \c testmigratedb
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, compression: off)
You are now connected to database "testmigratedb" as user "postgres".
testmigratedb=> \dt
List of relations
Schema | Name | Type | Owner
--------+------------------+-------+----------
public | homogeneoustable | table | postgres
public | migratetable | table | postgres
(2 rows)
testmigratedb=> SELECT COUNT(*) FROM migratetable;
count
-------
4
(1 row)
testmigratedb=> SELECT COUNT(*) FROM homogeneoustable;
count
-------
3
(1 row)
Verify CDC and full load replication
On the source PostgreSQL 10.0 instance:
testmigrated=# INSERT INTO migratetable (name, email) VALUES ('Charlie Davis', 'charlie.davis@example.com');
INSERT 0 1
testmigrated=# INSERT INTO homogeneoustable (product_name, price, quantity) VALUES ('Monitor', 349.99, 20);
INSERT 0 1
testmigrated=# select * from homogeneoustable;
id | product_name | price | quantity | created_at
----+--------------+--------+----------+----------------------------
1 | Laptop | 999.99 | 10 | 2026-02-25 00:00:08.865164
2 | Mouse | 29.99 | 50 | 2026-02-25 00:00:08.865164
3 | Keyboard | 79.99 | 30 | 2026-02-25 00:00:08.865164
4 | Monitor | 349.99 | 20 | 2026-02-25 16:58:40.810747
(4 rows)
testmigrated=# select * from migratetable;
id | name | email | created_at
----+---------------+---------------------------+----------------------------
1 | John Doe | john.doe@example.com | 2026-02-25 08:00:01.745175
2 | Jane Smith | jane.smith@example.com | 2026-02-25 00:00:01.745175
3 | Bob Johnson | bob.johnson@example.com | 2026-02-25 08:00:01.745175
4 | Alice Brown | alice.brown@example.com | 2026-02-25 02:00:12.029536
5 | Charlie Davis | charlie.davis@example.com | 2026-02-25 16:58:24.821607
(5 rows)
# Connect to EC2 PostgreSQL
psql -h <your-endpoint> -U postgres -d postgres
Password for user postgres:
psql (15.15)
Type "help" for help.
postgres=# \c testmigratedb
You are now connected to database "testmigratedb" as user "postgres".
testmigratedb=#
testmigratedb=# SELECT COUNT(*) FROM migratetable;
SELECT COUNT(*) FROM homogeneoustable;
count
-------
5
(1 row)
count
-------
4
(1 row)
On the target RDS for PostgreSQL instance:
[testmigrated]=> select * from homogeneoustable;
id | product_name | price | quantity | created_at
----+--------------+--------+----------+----------------------------
1 | Laptop | 999.99 | 10 | 2026-02-25 08:00:08.865164
2 | Mouse | 29.99 | 50 | 2026-02-25 00:00:08.865164
3 | Keyboard | 79.99 | 30 | 2026-02-25 00:00:08.865164
4 | Monitor | 349.99 | 20 | 2026-02-25 16:58:40.810747
(4 rows)
[testmigrated]=> select * from migratetable;
id | name | email | created_at
----+---------------+---------------------------+----------------------------
1 | John Doe | john.doe@example.com | 2026-02-25 00:00:01.745175
2 | Jane Smith | jane.smith@example.com | 2026-02-25 00:00:01.745175
3 | Bob Johnson | bob.johnson@example.com | 2026-02-25 00:00:01.745175
4 | Alice Brown | alice.brown@example.com | 2026-02-25 12:07:12.920536
5 | Charlie Davis | charlie.davis@example.com | 2026-02-25 16:58:26.821667
(5 rows)
[testmigrated]=> SELECT COUNT(*) FROM migratetable;
count
-------
5
(1 row)
[testmigrated]=> SELECT COUNT(*) FROM homogeneoustable;
count
-------
4
(1 row)
After you insert more data on the source EC2 instance, the count on the target increases.
testmigrated=> SELECT COUNT(*) FROM migratatable;
count
-------
6
(1 row)
testmigrated=> SELECT COUNT(*) FROM homogeneoustable;
count
-------
5
(1 row)
The expected result is that the row count on the target reflects the newly inserted rows from the source, which confirms that CDC replication is working:
Post-migration optimization
After the migration is complete and traffic has been switched to the target, run the following maintenance operations for optimal query performance:
Note: REINDEX DATABASE and VACUUM FULL are blocking operations. Schedule them during a low-traffic maintenance window. For Aurora PostgreSQL, consider using pg_repack as a non-blocking alternative.
Post-migration security hardening
After the migration is complete and the application is running successfully on the target, follow these security best practices to harden your environment.
- Disable or remove the DMS user: The
dms_useraccount was created with elevated replication privileges for the purpose of migration. After migration is complete, disable or remove this account: - Rotate the RDS master password: Immediately after migration, rotate the RDS master password:
- Navigate to Secrets Manager → Your Secret → Rotation.
- Turn on automatic rotation with a schedule (such as every 30 days).
- Enforce SSL/TLS connections: Make sure all connections to your RDS instance use SSL/TLS encryption.
- Turn on encryption: Encryption at rest should be turned on at instance creation. Verify under RDS → Configuration → Storage encrypted = Yes.
Troubleshooting
The following are common errors you might encounter during the migration process, and their resolutions.
PostgreSQL-specific errors
Error 1: Permission denied for replication
Error message:
ERROR: permission denied to create publication
Reason: The DMS database user lacks the REPLICATION role privilege, which is required to create a logical replication publication on the source.
Resolution:
Error 2: WAL level not set to logical
Error message:
ERROR: logical decoding requires wal_level >= logical
Reason: The source PostgreSQL instance has wal_level set to replica or minimal instead of logical, which is required for CDC-based migrations using logical replication.
Resolution:
Error 3: Publication does not exist
Error message:
ERROR: publication "dms_publication" does not exist
Reason: The logical replication publication was not created on the source database before starting the DMS migration task. DMS homogeneous migrations require an active publication to subscribe to.
Resolution:
Error 4: Insufficient replication slots
Error message:
ERROR: all replication slots are in use
Reason: The max_replication_slots parameter is set too low, and all available slots are consumed by existing replication connections or prior migration attempts that were not cleaned up.
Resolution:
Error 5: Connection refused
Error message:
ERROR: could not connect to server: Connection refused
Reason: The source PostgreSQL instance is not configured to accept connections from the DMS replication server, typically a missing entry in pg_hba.conf or a security group rule blocking port 5432.
Resolution:
Comprehensive DMS user privileges
Clean up
To avoid ongoing charges, delete the resources created during this walkthrough:
- Delete the DMS migration task — In the RDS console, select the database and navigate to the Data migrations tab, select your migration, and choose Delete.
- Drop the logical replication publication and slot on the source:
- Delete Secrets Manager secrets — In the Secrets Manager console, delete
postgres-ec2-source-secretand the target secret (if manually created). Choose Delete immediately or set a recovery window. - Delete the target RDS/Aurora instance — In the RDS console, select the target DB instance, choose Actions → Delete. Deselect Create final snapshot if this was a test.
- Delete the DMS IAM role — In the IAM console, delete the role created for DMS access.
- Clean up security groups — Remove any inbound rules added specifically for this migration.
- Terminate the source EC2 instance (if no longer needed) — In the EC2 console, select the instance and choose Instance state → Terminate.
Conclusion
In this post, we demonstrated how to migrate a self-managed PostgreSQL database on Amazon EC2 to Amazon RDS for PostgreSQL or Amazon Aurora PostgreSQL using AWS DMS homogeneous data migrations. We covered how DMS uses PostgreSQL’s native tools — pg_dump, pg_restore, and built-in logical replication to migrate schema objects, data, and ongoing changes with minimal downtime.
We also walked through the key prerequisites including WAL configuration for CDC, superuser permissions for replication, and the publisher-subscriber model that keeps your source database available throughout the migration. With these native AWS migration tools, you can move to a fully managed PostgreSQL service while reducing operational overhead and maintaining data consistency during cutover.
If you have questions about PostgreSQL migrations using DMS, leave them in the comments.