Oracle 26ai — Setting Up Data Guard Per Pluggable Database (DGPDB) From Scratch
Pluggable database level dataguard setup with switchover of only one pdb at a time. Redo flows bidirectionally between two CDB databases.

Level: Advanced DBA
Reading time: ~20 minutes
Introduction
Traditional Oracle Data Guard protects the entire CDB — if you want to failover, everything moves together. But in a multitenant environment with dozens of PDBs serving different business units, you may only want to failover one PDB while the others stay put.
That's exactly what DGPDB — Data Guard per Pluggable Database delivers.
Introduced in Oracle 21c (21.7 RU) and enhanced in Oracle 23ai/26ai, DGPDB lets each PDB act as an independent Data Guard unit. You can switchover ORCLPDB1 to your DR site while PDB2 and PDB3 continue running as-is on the original host — without touching them.
I spent two full days building this from scratch on Oracle 26ai (23.26.1.0.0) running in Docker on a Windows laptop. Here's everything I learned — including the errors that aren't in the official docs.
What Makes DGPDB Different From Traditional Data Guard
Before we touch a keyboard, understand this architectural shift:
Traditional Data Guard:
Primary CDB ──── redo ────► Standby CDB (MOUNTED)
Both PDBs move together on failover
DGPDB:
CDB-A (PRIMARY, OPEN R/W) CDB-B (PRIMARY, OPEN R/W)
SALES_PDB ► Primary SALES_PDB ► Standby (MOUNTED)
HR_PDB ► Primary HR_PDB ► Standby (MOUNTED)
ERP_PDB ► Standby (MOUNTED) ERP_PDB ► Primary
Both CDBs are primary at CDB level. There is no traditional "standby database." Each CDB hosts some PDBs as active (READ WRITE) and some as standby (MOUNTED, receiving redo from the peer CDB).
The DGPDB broker orchestrates everything across this peer-to-peer model. Which brings us to the first critical fact you need to know:
DGPDB is 100% broker-managed. There is no SQL-only alternative. Every operation — configuration, monitoring, switchover, failover — goes through dgmgrl.
Pre-Requisites and Environment
For this guide, I'm using:
Oracle Database 26ai EE (23.26.1.0.0) on Oracle Linux 8
Two separate CDB hosts —
oracle-primary(SID: ORCL) andoracle-standby(SID: ORCL2)Three PDBs configured in a symmetric layout:
ORCLPDB1: primary on ORCL, standby on ORCL2
PDB2: primary on ORCL, standby on ORCL2
PDB3: primary on ORCL2, standby on ORCL
Both hosts must be able to reach each other over TCP (port 1521) by hostname.
Step 1 — Baseline CDB Configuration
Both CDBs need these foundations before DGPDB can work.
Enable Archivelog Mode
SHUTDOWN IMMEDIATE;
STARTUP MOUNT;
ALTER DATABASE ARCHIVELOG;
ALTER DATABASE OPEN;
Set DB_UNIQUE_NAME
Each CDB needs a unique name for the broker to tell them apart.
-- On ORCL (oracle-primary)
ALTER SYSTEM SET DB_UNIQUE_NAME='ORCL_PRI' SCOPE=SPFILE;
-- On ORCL2 (oracle-standby)
ALTER SYSTEM SET DB_UNIQUE_NAME='ORCL2_PRI' SCOPE=SPFILE;
Restart both CDBs after this change.
Set REMOTE_LOGIN_PASSWORDFILE
ALTER SYSTEM SET REMOTE_LOGIN_PASSWORDFILE=EXCLUSIVE SCOPE=SPFILE;
Step 2 — Configure Network Connectivity
TNS Aliases (tnsnames.ora on Both Hosts)
ORCL =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = oracle-primary)(PORT = 1521))
(CONNECT_DATA = (SERVER = DEDICATED)(SID = ORCL)))
ORCL2 =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = oracle-standby)(PORT = 1521))
(CONNECT_DATA = (SERVER = DEDICATED)(SID = ORCL2)))
Static Listener Registration (listener.ora on Both Hosts)
DGPDB broker needs to connect to both databases even when they're in different states. A static listener entry ensures the database is reachable regardless of its open mode:
SID_LIST_LISTENER =
(SID_LIST =
(SID_DESC =
(GLOBAL_DBNAME = ORCL)
(ORACLE_HOME = /opt/oracle/product/26ai/dbhome_1)
(SID_NAME = ORCL))
(SID_DESC =
(GLOBAL_DBNAME = ORCL2)
(ORACLE_HOME = /opt/oracle/product/26ai/dbhome_1)
(SID_NAME = ORCL2)))
Run lsnrctl reload after this change.
Create Oracle Wallet for Passwordless Broker Connections
The broker makes autonomous cross-CDB connections without interactive prompts. An Oracle wallet stores the SYS credentials for this:
# On both hosts, repeat for each
mkdir -p $ORACLE_HOME/wallet
mkstore -wrl $ORACLE_HOME/wallet -create
mkstore -wrl $ORACLE_HOME/wallet -createCredential ORCL sys "Oracle_DG#2024"
mkstore -wrl $ORACLE_HOME/wallet -createCredential ORCL2 sys "Oracle_DG#2024"
Then configure sqlnet.ora on both hosts:
SQLNET.WALLET_OVERRIDE = TRUE
WALLET_LOCATION =
(SOURCE =
(METHOD = FILE)
(METHOD_DATA =
(DIRECTORY = /opt/oracle/product/26ai/dbhome_1/wallet)))
Verify it works:
sqlplus /@ORCL as sysdba # from oracle-primary
sqlplus /@ORCL2 as sysdba # test cross-host connection
Step 3 — Create the DGPDB Broker Configurations
DGPDB uses a pair of linked broker configurations — one per CDB — rather than a single traditional DG configuration.
Create a Configuration on Each CDB
On oracle-primary:
export ORACLE_SID=ORCL
dgmgrl /@ORCL
CREATE CONFIGURATION ORCL_DGPDB
AS PRIMARY DATABASE IS ORCL_PRI
CONNECT IDENTIFIER IS ORCL;
Gotcha:
PRIMARY DATABASE IStakes the DB_UNIQUE_NAME (ORCL_PRI), not the SID (ORCL). Getting this wrong causes ORA-16642.
On oracle-standby:
export ORACLE_SID=ORCL2
dgmgrl /@ORCL2
CREATE CONFIGURATION ORCL2_DGPDB
AS PRIMARY DATABASE IS ORCL2_PRI
CONNECT IDENTIFIER IS ORCL2;
Link the Two Configurations
From oracle-primary's dgmgrl:
ADD CONFIGURATION ORCL2_DGPDB CONNECT IDENTIFIER IS ORCL2;
ENABLE CONFIGURATION ALL;
This tells ORCL_DGPDB about ORCL2_DGPDB and creates the bidirectional link. The broker propagates the awareness to both sides automatically.
From oracle-standby's dgmgrl:
ENABLE CONFIGURATION ALL;
Both configs should show SUCCESS when you run SHOW CONFIGURATION VERBOSE.
Step 4 — PREPARE DATABASE FOR DATA GUARD
This is the step that most guides skip or get wrong. PREPARE DATABASE FOR DATA GUARD is a mandatory prerequisite on both CDBs. It:
Configures the Fast Recovery Area
Enables Force Logging and Flashback
Adds standby redo log groups at CDB level
Sets
DB_LOST_WRITE_PROTECT,STANDBY_FILE_MANAGEMENT, and other critical DG parameters
The command has strict connection requirements:
Must use a bequeath (local) connection — not a TCP wallet connection
Must connect as SYSDBA — not SYSDG
Must include the RESTART keyword — some static parameters require a database restart
# On oracle-primary
export ORACLE_SID=ORCL
dgmgrl / as sysdba
PREPARE DATABASE FOR DATA GUARD
WITH DB_UNIQUE_NAME IS ORCL_PRI
DB_RECOVERY_FILE_DEST IS '/opt/oracle/fast_recovery_area'
DB_RECOVERY_FILE_DEST_SIZE IS 10G
RESTART;
# On oracle-standby
export ORACLE_SID=ORCL2
dgmgrl / as sysdba
PREPARE DATABASE FOR DATA GUARD
WITH DB_UNIQUE_NAME IS ORCL2_PRI
DB_RECOVERY_FILE_DEST IS '/opt/oracle/fast_recovery_area'
DB_RECOVERY_FILE_DEST_SIZE IS 10G
RESTART;
If static parameters need changing, Oracle shuts the database down and restarts it automatically. Let it run. Successful output ends with Flashback Database enabled. Succeeded.
Step 5 — EDIT CONFIGURATION PREPARE DGPDB
This is the most commonly missed step in DGPDB setups — and it's the one that causes ORA-16922: Configuration not ready for Oracle Data Guard per Pluggable Database every time you try ADD PLUGGABLE DATABASE.
This command creates the DGPDB_INT internal account on both CDBs and marks the broker configurations as DGPDB-ready. Run it once, from oracle-primary, using the wallet connection:
export ORACLE_SID=ORCL
dgmgrl /@ORCL
EDIT CONFIGURATION PREPARE DGPDB;
It prompts for a password for the DGPDB_INT account on each CDB:
Enter password for DGPDB_INT account at ORCL_PRI: <set a password>
Enter password for DGPDB_INT account at ORCL2_PRI: <set a password>
Prepared Data Guard for Pluggable Database at ORCL2_PRI.
Prepared Data Guard for Pluggable Database at ORCL_PRI.
Once you see those two Prepared lines, you're ready for ADD PLUGGABLE DATABASE.
Step 6 — Add PDBs to DGPDB Configuration
Now we register each PDB with its standby destination. The PDBFILENAMECONVERT clause maps source datafile paths to destination paths.
ORCLPDB1 and PDB2 (Primary on ORCL, Standby on ORCL2)
export ORACLE_SID=ORCL2
dgmgrl /@ORCL2
ADD PLUGGABLE DATABASE ORCLPDB1 AT ORCL2_PRI
SOURCE IS ORCLPDB1 AT ORCL_PRI
PDBFILENAMECONVERT IS "'/opt/oracle/oradata/ORCL/','/opt/oracle/oradata/ORCL2_PRI/ORCL2_PRI/'";
ADD PLUGGABLE DATABASE PDB2 AT ORCL2_PRI
SOURCE IS PDB2 AT ORCL_PRI
PDBFILENAMECONVERT IS "'/opt/oracle/oradata/ORCL_PRI/','/opt/oracle/oradata/ORCL2_PRI/ORCL2_PRI/'";
PDB3 (Primary on ORCL2, Standby on ORCL)
export ORACLE_SID=ORCL
dgmgrl /@ORCL
ADD PLUGGABLE DATABASE PDB3 AT ORCL_PRI
SOURCE IS PDB3 AT ORCL2_PRI
PDBFILENAMECONVERT IS "'/opt/oracle/oradata/ORCL2_PRI/ORCL2_PRI/','/opt/oracle/oradata/ORCL_PRI/'";
Each command returns: Pluggable Database "<PDB>" added
At this point the broker configuration knows about the PDBs, but the standby datafiles don't exist yet. The next step puts them there.
Step 7 — Copy Datafiles to Standby Location
The broker has registered the PDB relationship but the actual data files need to be physically copied to the standby CDB. This must be done one PDB at a time with the source PDB closed for consistency.
Here's the complete cycle for each PDB:
1. Close the Source PDB
-- On primary CDB (ORCL)
ALTER PLUGGABLE DATABASE ORCLPDB1 CLOSE IMMEDIATE;
2. Copy Datafiles
The recommended method is a direct container-to-container tar pipe. On Linux hosts, run this directly.
# On primary Database host tart the datafiles
tar -cvf /opt/oracle/oradata/ORCL/53646B4FB1A0156FE063020014AC5EDA
#Use scp to copy datafiles to the target host.
#On standby database host , untar all the files to appropriate file path
tar -xvf /opt/oracle/oradata/ORCL2_PRI/ORCL2_PRI
NOTE: Above commands are just for reference, use correct scp commands to get all the datafiles belonging to the PDB to the second host.
3. Fix File Ownership on Destination
chown -R oracle:oinstall \
/opt/oracle/oradata/ORCL2_PRI/ORCL2_PRI/53646B4FB1A0156FE063020014AC5EDA
4. Reopen the Source PDB
ALTER PLUGGABLE DATABASE ORCLPDB1 OPEN;
ALTER PLUGGABLE DATABASE ORCLPDB1 SAVE STATE;
Repeat this cycle for PDB2 and PDB3.
Step 8 — Add PDB-Level Standby Redo Logs
This is another step the documentation buries. EDIT CONFIGURATION PREPARE DGPDB does NOT create standby redo logs inside the target PDB. Without them, apply runs against archived logs only — defeating the purpose of real-time transport.
You must add standby redo logs inside the mounted standby PDB. To do that, connect to CDB$ROOT and switch container:
First, stop apply so Oracle doesn't object:
dgmgrl /@ORCL2
EDIT PLUGGABLE DATABASE ORCLPDB1 AT ORCL2_PRI SET STATE=APPLY-OFF;
Then add the logs:
ALTER SESSION SET CONTAINER = ORCLPDB1;
ALTER DATABASE ADD STANDBY LOGFILE SIZE 200M;
ALTER DATABASE ADD STANDBY LOGFILE SIZE 200M;
ALTER DATABASE ADD STANDBY LOGFILE SIZE 200M;
ALTER DATABASE ADD STANDBY LOGFILE SIZE 200M;
SELECT GROUP#, BYTES/1024/1024 MB, STATUS FROM V$STANDBY_LOG;
Re-enable apply:
EDIT PLUGGABLE DATABASE ORCLPDB1 AT ORCL2_PRI SET STATE=APPLY-ON;
Repeat for each standby PDB.
Step 9 — Enable Redo Transport on Source Side
Enable transport from the primary PDB to the standby:
dgmgrl /@ORCL
EDIT PLUGGABLE DATABASE ORCLPDB1 AT ORCL_PRI SET STATE=TRANSPORT-ON;
Step 10 — Initial Log Switch (One-Time Catch-Up)
At this point the standby has a copy of the datafiles from when the PDB was closed. The redo generated between the close and now is sitting in online redo logs on the primary. Force it to archive and ship:
-- On oracle-primary
ALTER SYSTEM SWITCH LOGFILE;
ALTER SYSTEM SWITCH LOGFILE;
After this, real-time redo transport via LGWR → LNS → RFS → SRL → MRP kicks in automatically. You will not need manual log switches going forward.
Step 11 — Verify the Setup
dgmgrl /@ORCL2
SHOW PLUGGABLE DATABASE ORCLPDB1 AT ORCL2_PRI
Healthy standby output:
Pluggable database - ORCLPDB1 at orcl2_pri
Data Guard Role: Physical Standby
Con_ID: 3
Source: con_id 3 at ORCL_PRI
Transport Lag: 6 seconds (computed 5 seconds ago)
Apply Lag: 6 seconds (computed 5 seconds ago)
Intended State: APPLY-ON
Apply State: Running
Apply Instance: ORCL2
Average Apply Rate: 67 KByte/s
Real Time Query: ON
Pluggable Database Status:
SUCCESS
What you want to see:
Data Guard Role: Physical Standby ✅
Transport Lag: Single-digit seconds ✅
Apply State: Running ✅
Real Time Query: ON ✅ (means PDB can be opened read-only for Active Data Guard queries)
Check the transport and apply processes directly:
-- From CDB$ROOT
SELECT PROCESS, STATUS, CLIENT_PROCESS, SEQUENCE#, BLOCK#
FROM V$MANAGED_STANDBY
ORDER BY PROCESS;
LNS WRITING LNS 18 5862 ← primary shipping redo
RFS IDLE LGWR 18 6624 ← standby receiving
MRP0 WAIT_FOR_LOG N/A 18 0 ← standby apply waiting for next record
This three-process chain confirms real-time redo transport is active.
Step 12 — Perform a PDB Switchover (only one without affecting other PDBs).
This is the moment DGPDB was built for.
dgmgrl /@ORCL2
SWITCHOVER TO PLUGGABLE DATABASE ORCLPDB1 AT ORCL2_PRI;
Performing switchover NOW, please wait...
Switchover succeeded, new primary is "orclpdb1"
Verify the roles reversed:
SHOW PLUGGABLE DATABASE ORCLPDB1 AT ORCL2_PRI
-- Data Guard Role: Primary ← ORCL2 is now primary for ORCLPDB1
SHOW PLUGGABLE DATABASE ORCLPDB1 AT ORCL_PRI
-- Data Guard Role: Physical Standby ← ORCL is now standby for ORCLPDB1
Check that PDB2 and PDB3 were not affected:
SHOW PLUGGABLE DATABASE PDB2 AT ORCL_PRI
-- Data Guard Role: Primary ← unchanged
This is the core value of DGPDB — ORCLPDB1 switched roles completely independently. PDB2 and PDB3 didn't notice.
Switchover back:
SWITCHOVER TO PLUGGABLE DATABASE ORCLPDB1 AT ORCL_PRI;
What DGPDB Does NOT Support
Set expectations correctly before taking this to production:
| Feature | Available? |
|---|---|
| Maximum Performance (ASYNC) | ✅ |
| Maximum Availability / Protection (SYNC) | ❌ |
| Real-time apply from SRLs | ✅ (confirmed) |
| Far Sync instances | ❌ |
| Multiple standby targets per PDB | ❌ (one target CDB only) |
| Rolling upgrades via DBMS_ROLLING | ❌ |
| Snapshot Standby | ❌ |
| GoldenGate downstream capture | ❌ |
| Application Containers | ❌ |
Since only ASYNC transport is supported, there is potential data loss on failover. Mitigate with:
ALTER SYSTEM SET ARCHIVE_LAG_TARGET=30; -- max 30-second lag
Key Takeaways for DBAs
Two primary CDBs — not primary + standby. Both are OPEN READ WRITE.
Broker is mandatory —
dgmgrlfor everything. No manual SQL equivalent exists.Run EDIT CONFIGURATION PREPARE DGPDB — the step the docs bury. Without it, ADD PLUGGABLE DATABASE always fails with ORA-16922.
PREPARE DATABASE needs bequeath + SYSDBA + RESTART —
dgmgrl / as sysdbawith theRESTARTkeyword.Add PDB-level standby redo logs manually —
EDIT CONFIGURATION PREPARE DGPDBdoesn't create them inside the PDB.cmd.exe for Docker tar pipes on Windows — PowerShell corrupts binary streams.
One log switch after setup — clears the backlog from the offline period. After that, real-time transport runs continuously.
V\(DATABASE.DATABASE_ROLE = 'PRIMARY' on both CDBs — always. PDB role is only visible via
SHOW PLUGGABLE DATABASEin dgmgrl orV\)PDBS.OPEN_MODE(MOUNTED = standby PDB).Standby PDB cannot be opened read-write manually — ORA-65339 if you try. Only switchover/failover via broker can change its role.
Switchover is genuinely independent — switching one PDB doesn't affect others. This is the whole point.
Conclusion
DGPDB is a meaningful step forward for Oracle multitenant HA. It's not for every scenario — the lack of SYNC transport and the one-target-per-PDB limitation are real constraints — but for workloads that need independent PDB-level failover without moving the entire CDB, it delivers.
The four steps that consistently trip people up are: the connection type for PREPARE DATABASE, running EDIT CONFIGURATION PREPARE DGPDB, adding PDB-level standby redo logs manually, and the datafile copy sequence. Get those right and the rest follows logically.
Tested on Oracle 26ai (23.26.1.0.0) — Oracle Linux 8 — Docker Desktop on Windows
All switchovers confirmed working including independent PDB-level role reversal




