Every log-based CDC tool needs the same two things from Postgres: wal_level set to logical, and a user that is allowed to replicate. On your own server that's one line in postgresql.conf and a restart. On a managed service, each provider puts it behind its own setting, and most of them restart your database to apply it.
This guide covers each one: self-hosted Postgres, AWS RDS, Aurora, Google Cloud SQL, Azure, Supabase and Neon. It works for any CDC tool that reads the Postgres WAL, including Debezium, Fivetran, Airbyte and our own Bruin Cloud Streams. We build Bruin, so that's our angle; the steps themselves are the same whichever tool you use.
In short
| Where Postgres runs | The setting | Restart? | Replication user |
|---|---|---|---|
| Self-hosted | wal_level = logical | Yes | ALTER ROLE ... WITH REPLICATION |
| AWS RDS | rds.logical_replication = 1 in a custom parameter group | Yes, reboot | GRANT rds_replication TO ... |
| Amazon Aurora | rds.logical_replication = 1 in a custom cluster parameter group | Yes, reboot the writer | GRANT rds_replication TO ... |
| Google Cloud SQL | Flag cloudsql.logical_decoding = on | Yes | ALTER USER ... WITH REPLICATION |
| Azure flexible server | Server parameter wal_level = logical | Yes | ALTER ROLE ... WITH REPLICATION |
| Supabase | Already on | No | The postgres user, or a new role on Postgres 17+ |
| Neon | Project setting "Logical replication" | Restarts computes, can't be undone | Console-created roles already have it |
What CDC needs from Postgres
Three things, wherever Postgres runs:
wal_level = logical. The default isreplica.logicaladds the information that logical decoding needs, and Postgres only reads this setting at server start.- A user with the
REPLICATIONattribute (or the provider's equivalent role). That user also needs to log in, andSELECTon the tables for the first copy. - A replica identity on every table you replicate. By default that's the primary key. A table with no primary key and the default identity can still publish inserts, but an
UPDATEorDELETEon it raises an error on the source once it's in a publication. Add a primary key, or useREPLICA IDENTITY FULLas a fallback; it records the whole old row, so it writes more WAL.
Each CDC connection also uses a replication slot and a WAL sender. max_replication_slots and max_wal_senders both default to 10, which is plenty for one or two streams.
Self-hosted Postgres
In postgresql.conf:
wal_level = logical
Restart Postgres, then create the CDC user:
create role cdc_user with login replication password 'change-me';
grant select on all tables in schema public to cdc_user;
Allow cdc_user to connect in pg_hba.conf, and reload.
AWS RDS for PostgreSQL
- Create a custom DB parameter group for your Postgres version, or edit the custom one the instance already uses. The default group can't be changed.
- Set
rds.logical_replicationto1. It's a static parameter. - Attach the group to the instance and reboot it. After the reboot,
show wal_level;returnslogical. - Grant the CDC user the replication role:
grant rds_replication to cdc_user;
rds_replication lets the user create, read and drop logical slots and stream from them. AWS notes that turning on logical replication can increase WAL generation, and that slots nobody reads can fill the instance's storage, so watch free storage once a stream is running.
Amazon Aurora PostgreSQL
Same parameter, different group:
- Set
rds.logical_replication = 1in a custom DB cluster parameter group (the default is 0, and default groups can't be edited). - Attach it to the cluster and reboot the writer instance.
grant rds_replication to cdc_user;and connect your CDC tool to the writer endpoint.
Aurora readers share the writer's storage and have no logical slots of their own, so logical decoding from a reader isn't supported on Aurora, even on Postgres 16 and later. One more warning from AWS: changing aurora.enhanced_logical_replication invalidates every logical replication slot on the cluster, so don't flip it under a running stream.
Google Cloud SQL for PostgreSQL
- Set the database flag
cloudsql.logical_decodingtoon: in the console under Edit > Flags, or withgcloud sql instances patch. Careful withgcloud:--database-flagsreplaces every flag already set, so pass your existing flags too. - The instance restarts to apply it. Turning the flag on for a primary turns it on for its read replicas too, which then restart in close succession; Google suggests setting it on each replica first.
- Give the CDC user replication:
create user cdc_user with replication in role cloudsqlsuperuser login password 'change-me';
-- or, for an existing user:
alter user cdc_user with replication;
You don't need cloudsql.enable_pglogical for this. That flag is only for the pglogical extension; plain logical decoding needs cloudsql.logical_decoding alone.
Azure Database for PostgreSQL flexible server
- On the server's Server parameters page, set
wal_leveltological. Microsoft also recommends raisingmax_worker_processesto at least 16. - Save and restart the server.
- Give a dedicated CDC user replication (Microsoft recommends a separate user rather than the admin):
alter role cdc_user with replication;
Two Azure behaviours worth knowing before you go live. The server turns read-only when storage reaches 95% or less than 5 GiB is free. And when storage crosses a threshold, Azure drops inactive logical replication slots on its own, which means your CDC tool has to take a fresh snapshot. On high availability setups, Postgres 16 and earlier don't keep logical slots through a failover unless you use the pg_failover_slots extension; Postgres 17 can sync slots that were created with the failover option.
Supabase
Nothing to turn on: Supabase's Postgres image ships with wal_level = logical. What you do need:
- The direct connection string. Replication doesn't work through Supabase's shared or dedicated pooler. Direct connections use IPv6, so turn on the IPv4 add-on if your CDC tool can't reach IPv6.
- A user. On Postgres 17 and later, create a role with replication while connected as
postgres. On older versions, use thepostgresuser itself.
create role cdc_user with login replication password 'change-me';
Slot limits depend on compute size: 5 replication slots and WAL senders on Nano to Medium, 8 on Large, 24 on XL and 80 from 2XL up.
Neon
- Turn on Logical replication under Settings > Postgres in the console, or with
neon projects update $PROJECT_ID --enable-logical-replication. It setswal_leveltologicalfor every database in the project. - Two things to know first: it can't be turned off again, and turning it on restarts all computes, so open connections drop.
- Roles created in the Console, CLI or API belong to
neon_superuser, which already has replication. Roles created with SQL needalter role cdc_user replication;. - Use the direct connection string: the hostname must not contain
-pooler.
Neon removes replication slots that haven't made progress for about 40 hours, and a connected CDC tool keeps the compute from scaling to zero. Each project gets 10 slots and 10 WAL senders; Neon support can change that.
Check it worked
Run these on the database your CDC tool will read:
-- should return "logical"
show wal_level;
-- should return true for your CDC user
select rolname, rolreplication from pg_roles where rolname = 'cdc_user';
-- tables that can't publish updates or deletes yet
select c.oid::regclass as table_name
from pg_class c
where c.relkind = 'r'
and c.relnamespace::regnamespace::text not in ('pg_catalog', 'information_schema', 'pg_toast')
and (
c.relreplident = 'n'
or (c.relreplident = 'd'
and not exists (select 1 from pg_index i where i.indrelid = c.oid and i.indisprimary))
);
On RDS and Aurora, rolreplication stays false for a user that got rds_replication; check the role membership instead.
Can CDC read from a read replica?
Since PostgreSQL 16, logical decoding works on a standby, which keeps the snapshot and decoding work off the primary. Support depends on where you run:
| Service | Logical decoding on a read replica |
|---|---|
| Self-hosted | Postgres 16+, with hot_standby_feedback = on and a physical slot to the primary |
| AWS RDS | From Postgres 16.1; set rds.logical_replication = 1 on the source and the replica |
| Amazon Aurora | Not supported |
| Google Cloud SQL | From Postgres 16, if the primary has the logical flags set |
| Supabase | From Postgres 17, using the replica's connection string |
ingestr, the engine under Cloud Streams, can read from a hot standby on Postgres 16 and later; the ingestr Postgres docs list what the standby needs.
Before you go live
- Plan the restart. Everywhere except Supabase, turning this on restarts the database. Do it in a maintenance window, with whoever owns the database.
- Skip the pooler. Logical replication needs a direct connection, which matters most on Supabase and Neon.
- Watch the WAL. A replication slot keeps WAL until the stream confirms it, so a stream that stops makes the disk grow. Some providers act on their own: Azure drops inactive slots when storage runs low and Neon removes them after about 40 hours, and either way your CDC tool has to snapshot again. We cover this in how to stop CDC from filling your Postgres disk with WAL.
- Count your slots. One slot and one WAL sender per stream. Most services default to 10; small Supabase plans get 5.
Then start streaming
Once wal_level is logical and the user exists, the database side is done. In Bruin Cloud Streams that's the "once" step: from there, starting a stream is 1 click, and every insert, update and delete keeps flowing into your warehouse. If you'd rather run it yourself, the same stream runs with ingestr:
ingestr ingest \
--source-uri 'postgres+cdc://cdc_user:pass@host:5432/app?dest_schema=analytics' \
--dest-uri 'bigquery://my-project?credentials_path=/path/to/sa.json' \
--stream
More on what lands in the warehouse in Launching Bruin Cloud Streams.
FAQ
Does enabling Postgres logical replication need a restart?
Yes, almost everywhere. wal_level can only be set at server start, so self-hosted Postgres, AWS RDS, Aurora, Cloud SQL and Azure all need a restart. Supabase already has it on. Neon turns it on with a project setting that restarts its computes and can't be undone.
How do I enable logical replication on AWS RDS for PostgreSQL?
Set rds.logical_replication to 1 in a custom DB parameter group, attach the group to the instance and reboot it. Then give your CDC user the rds_replication role with GRANT rds_replication TO your_user.
How do I enable logical replication on Google Cloud SQL for PostgreSQL?
Set the database flag cloudsql.logical_decoding to on and let the instance restart. Then create a user WITH REPLICATION, or run ALTER USER your_user WITH REPLICATION. You don't need the pglogical flag for plain logical decoding.
Is logical replication already on in Supabase?
Yes. Supabase's Postgres image ships with wal_level set to logical. Connect with the direct connection string, not the pooler, because replication doesn't work through the pooler.
Can a CDC tool read from a Postgres read replica?
From PostgreSQL 16, logical decoding works on a standby. RDS supports it from 16.1 and Cloud SQL from version 16, and Supabase lets you subscribe to a read replica from Postgres 17. Aurora doesn't support logical decoding from its readers.
Which tables can't be replicated with CDC?
Tables without a primary key or a replica identity can't publish updates or deletes; Postgres raises an error on the source when they happen. Add a primary key, or set REPLICA IDENTITY FULL as a fallback.
