A complete real-world implementation guide for configuring Oracle Data Guard Physical Standby as part of a cross-cloud database migration. Covers environment preparation, RMAN Active Database Duplication, redo log shipping, Managed Recovery Process, Data Guard Broker configuration, and full validation procedures for Oracle 19c disaster recovery architecture.
This implementation establishes Oracle Data Guard Physical Standby for two databases across cloud platforms, providing high availability and disaster recovery with real-time redo log shipping.
PRIMARYDB → STANDBYDBPRIMARYDB2 → STANDBYDB2Follow all sections in sequence. Each section maps directly to the original implementation. All commands and configurations are preserved exactly as executed in the production migration.
Create the Oracle OS user and all required DBA OS groups on every target server. Oracle Database must run under a dedicated non-root user with specific group memberships — this is a mandatory security requirement from Oracle's installation guidelines. The id oracle command at the end verifies all group assignments are correct.
# Create Oracle OS user useradd oracle passwd oracle # Create required DBA groups groupadd oinstall groupadd dba groupadd oper groupadd backupdba groupadd dgdba groupadd kmdba groupadd racdba # Assign Oracle user to groups usermod -g oinstall oracle usermod -G dba,oper,backupdba,dgdba,kmdba,racdba oracle id oracle
oinstall is the primary group — set with -g-Gdgdba is required specifically for Data Guard operationsDisable the firewall and SELinux on target servers to allow unrestricted Oracle database communication between source and target environments. In production configurations, firewall rules should be added specifically for Oracle ports instead of full disablement — however, for a migration lab this approach eliminates network-related blockers.
systemctl stop firewalld
systemctl disable firewalld
sestatus
# Set SELINUX=disabled in /etc/selinux/config
vi /etc/selinux/configInstall the oracle-database-preinstall-19c RPM package. This package automatically configures all required kernel parameters, OS limits, and system settings for Oracle Database — saving significant manual configuration effort. The package is pulled from Oracle's official YUM repository.
# Import Oracle GPG key rpm --import https://yum.oracle.com/RPM-GPG-KEY-oracle-ol7 # Configure Oracle Linux 7 YUM repository wget https://yum.oracle.com/public-yum-ol7.repo \ -O /etc/yum.repos.d/public-yum-ol7.repo yum-config-manager --enable ol7_latest yum-config-manager --enable ol7_optional_latest yum clean all && yum makecache # Install Oracle 19c pre-install package yum install -y oracle-database-preinstall-19c
shmmax, shmall, semaphores, etc.) automaticallyCreate the required directory tree for Oracle Home, software staging, and database files. The /u01 mount point holds Oracle software and database files, while /u02 is used for archive logs. Correct ownership and permissions are critical — Oracle installer will fail if these are incorrect.
# Oracle Home and software staging mkdir -p /u01/app/oracle/product/19.0.0.0/dbhome_1 mkdir /u01/software chmod 775 -R /u01 chown oracle:oinstall /u01 -R # Secondary disk for archive logs chown oracle:oinstall /u02 -R chmod 775 /u02/ -R
ORACLE_BASE = /u01/app/oracleORACLE_HOME = /u01/app/oracle/product/19.0.0.0/dbhome_1/u02 — separate disk/mount point for archive logs — keeps archive logs off the main OS diskoracle:oinstall — not rootTransfer Oracle 19c software binaries from the source server to all target servers using SCP, then unzip and run the installer. The binary transfer via SCP is the recommended approach for cross-cloud migrations where both environments have network connectivity — it avoids re-downloading from Oracle and ensures identical software versions.
# Transfer Oracle 19c binaries to target servers
scp -v /u01/app/oracle/product/19.0.0.0/LINUX.X64_193000_db_home.zip \
oracle@<target-server-ip-1>:/u01/softwarechmod 775 LINUX.X64_193000_db_home.zip unzip LINUX.X64_193000_db_home.zip \ -d /u01/app/oracle/product/19.0.0.0/dbhome_1/ cd /u01/app/oracle/product/19.0.0.0/dbhome_1/ ./runInstaller
Configure Linux HugePages for optimal Oracle SGA memory management. HugePages pre-allocates large memory pages for Oracle's System Global Area (SGA), eliminating the overhead of standard 4KB page table management and preventing memory from being swapped. The value is calculated based on the total SGA size of all databases running on the server.
# Check current HugePages status grep -i huge /proc/meminfo # Configure in /etc/sysctl.conf vm.nr_hugepages = <calculated_value> # Apply and verify sudo sysctl -p grep HugePages /proc/meminfo reboot
vm.nr_hugepages = Total SGA size (in bytes) ÷ 2,097,152 (2MB page size) + buffergrep HugePages /proc/meminfo — HugePages_Free should match configured valueSet Oracle environment variables in the oracle user's .bash_profile on the target server. These variables tell the Oracle binaries where the Oracle Home is, which database instance to connect to, and configure library paths. Without these, Oracle CLI tools like sqlplus, rman, and lsnrctl will not function.
export ORACLE_BASE=/u01/app/oracle export ORACLE_HOME=/u01/app/oracle/product/19.0.0.0/dbhome_1 export ORACLE_SID=STANDBYDB export PATH=$ORACLE_HOME/bin:$PATH export LD_LIBRARY_PATH=$ORACLE_HOME/lib:/lib:/usr/lib export CLASSPATH=$ORACLE_HOME/jlib:$ORACLE_HOME/rdbms/jlib
Configure the Oracle listener on the target (standby) server. The listener is the network gateway that accepts incoming Oracle Net connections on TCP port 1521. It must be running and correctly configured before the standby database can receive redo data or accept RMAN connections from the primary.
LISTENER =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = <target-server-hostname>)(PORT = 1521))
)
)lsnrctl start
Configure TNS entries on both primary and standby servers to allow bidirectional Oracle Net connectivity. Data Guard requires each server to be able to resolve and connect to the other by service name — the primary sends redo to the standby, and the broker communicates in both directions.
PRIMARYDB =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = <source-server-ip>)(PORT = 1521))
(CONNECT_DATA = (SERVER = DEDICATED)(SERVICE_NAME = PRIMARYDB))
)
STANDBYDB =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = <target-server-ip>)(PORT = 1521))
(CONNECT_DATA = (SERVER = DEDICATED)(SERVICE_NAME = STANDBYDB))
)tnsping PRIMARYDB tnsping STANDBYDB
tnsping commands must succeed from both servers before proceedingCreate the complete directory structure that the standby database will use for control files, data files, redo logs, and archive logs on the target server. RMAN Active Duplication will attempt to write to these paths during the duplication process — they must exist and be writable by the oracle user before RMAN is run.
mkdir -p /u01/app/oracle/admin/PRIMARYDB/adump mkdir -p /u01/PRIMARYDB/controlfiles1 mkdir -p /u01/PRIMARYDB/controlfiles2 mkdir -p /u01/PRIMARYDB/datafiles mkdir -p /u01/PRIMARYDB/redologs1 mkdir -p /u01/PRIMARYDB/redologs2 mkdir -p /u02/archivelogs
Create a minimal initialization parameter file for the standby instance. This file is used only to start the instance in NOMOUNT mode before RMAN duplication begins. RMAN will overwrite the parameter file with the proper SPFILE during duplication. Key parameters establish the Data Guard configuration identifiers and archive log destinations.
db_name=PRIMARYDB db_unique_name=STANDBYDB log_archive_config='DG_CONFIG=(PRIMARYDB,STANDBYDB)' log_archive_dest_1='LOCATION=/u02/archivelogs VALID_FOR=(ALL_LOGFILES,ALL_ROLES) DB_UNIQUE_NAME=STANDBYDB' log_archive_dest_2='SERVICE=PRIMARYDB ASYNC NOAFFIRM VALID_FOR=(ONLINE_LOGFILES,PRIMARY_ROLE) DB_UNIQUE_NAME=PRIMARYDB' fal_server=PRIMARYDB fal_client=STANDBYDB standby_file_management=AUTO control_files='/u01/PRIMARYDB/controlfiles1/control01.ctl','/u01/PRIMARYDB/controlfiles2/control02.ctl'
Start the standby instance in NOMOUNT mode using the parameter file just created. NOMOUNT mode starts the Oracle instance and allocates memory (SGA) without mounting or opening any database. This is the required state for RMAN Active Duplication — the instance must be running but no database files should be mounted yet.
sqlplus / as sysdba startup nomount pfile='/u01/app/oracle/product/19.0.0.0/dbhome_1/dbs/initSTANDBYDB.ora';
Copy the Oracle password file (orapwPRIMARYDB) from the primary server to the standby server. RMAN uses SYS credentials to connect to both the primary and standby during duplication — both databases must have the same SYS password, enforced via matching password files.
scp -v orapwPRIMARYDB \ oracle@<target-server-ip>:/u01/app/oracle/product/19.0.0.0/dbhome_1/dbs
RMAN Active Database Duplication creates the standby database by duplicating the primary database directly over the network — no backup files are required. Oracle streams the data files, control file, and SPFile from the primary directly to the standby in real time. This is the recommended method for cross-cloud migrations with network connectivity between environments.
nohup in background — duplication can take hours for large databasesShell script that sets the Oracle environment and invokes RMAN with the duplicate script. The script includes timestamped log file generation for monitoring and audit purposes.
#!/usr/bin/ksh # RMAN Active Duplicate Script # Source: PRIMARYDB (Source Platform) | Target: STANDBYDB (Target Platform) export ORACLE_BASE=/u01/app/oracle export ORACLE_HOME=/u01/app/oracle/product/19.0.0.0/dbhome_1 export ORACLE_SID=STANDBYDB export PATH=$ORACLE_HOME/bin:$PATH LOG_DIR=/u02/rmanduplicate BAK_DATE=`date '+%d%b%Y_%H_%M'` LOG_FILE=${LOG_DIR}/rman_dup_STANDBYDB_${BAK_DATE}.log RCV_FILE=${LOG_DIR}/rman_duplicate_script.rcv rman target sys/<db_password>@PRIMARYDB auxiliary sys/<db_password>@STANDBYDB \ cmdfile=$RCV_FILE \ log=$LOG_FILE
The RMAN command script that performs the active duplication. Allocates 6 parallel channels on both target and auxiliary to maximize transfer throughput. The DUPLICATE TARGET DATABASE FOR STANDBY FROM ACTIVE DATABASE NOFILENAMECHECK command with SPFILE overrides sets all required Data Guard parameters in the new standby SPFILE during duplication.
RUN {
ALLOCATE CHANNEL t1 DEVICE TYPE DISK;
ALLOCATE CHANNEL t2 DEVICE TYPE DISK;
ALLOCATE CHANNEL t3 DEVICE TYPE DISK;
ALLOCATE CHANNEL t4 DEVICE TYPE DISK;
ALLOCATE CHANNEL t5 DEVICE TYPE DISK;
ALLOCATE CHANNEL t6 DEVICE TYPE DISK;
ALLOCATE AUXILIARY CHANNEL c1 DEVICE TYPE DISK;
ALLOCATE AUXILIARY CHANNEL c2 DEVICE TYPE DISK;
ALLOCATE AUXILIARY CHANNEL c3 DEVICE TYPE DISK;
ALLOCATE AUXILIARY CHANNEL c4 DEVICE TYPE DISK;
ALLOCATE AUXILIARY CHANNEL c5 DEVICE TYPE DISK;
ALLOCATE AUXILIARY CHANNEL c6 DEVICE TYPE DISK;
DUPLICATE TARGET DATABASE
FOR STANDBY
FROM ACTIVE DATABASE NOFILENAMECHECK
SPFILE
SET db_unique_name='STANDBYDB'
SET db_name='PRIMARYDB'
SET control_files='/u01/PRIMARYDB/controlfiles1/control01.ctl',
'/u01/PRIMARYDB/controlfiles2/control02.ctl'
SET log_archive_config='DG_CONFIG=(PRIMARYDB,STANDBYDB)'
SET log_archive_dest_1='LOCATION=/u02/archivelogs VALID_FOR=(ALL_LOGFILES,ALL_ROLES) DB_UNIQUE_NAME=STANDBYDB'
SET log_archive_dest_2='SERVICE=PRIMARYDB ASYNC NOAFFIRM VALID_FOR=(ONLINE_LOGFILES,PRIMARY_ROLE) DB_UNIQUE_NAME=PRIMARYDB'
SET fal_server='PRIMARYDB'
SET fal_client='STANDBYDB'
SET STANDBY_FILE_MANAGEMENT='AUTO'
SET audit_file_dest='/u01/app/oracle/admin/PRIMARYDB/adump';
RELEASE CHANNEL t1;
RELEASE CHANNEL t2;
RELEASE CHANNEL t3;
RELEASE CHANNEL t4;
RELEASE CHANNEL t5;
RELEASE CHANNEL t6;
RELEASE CHANNEL c1;
RELEASE CHANNEL c2;
RELEASE CHANNEL c3;
RELEASE CHANNEL c4;
RELEASE CHANNEL c5;
RELEASE CHANNEL c6;
}Execute the duplication shell script using nohup so it continues running even if the SSH session disconnects. Monitor progress via the RMAN log file and watch the data files directory grow as files are copied from the primary.
# Run in background (survives SSH disconnects) nohup ./rman_duplicate_STANDBYDB.sh & # Monitor RMAN log in real time tail -100f /u02/rmanduplicate/rman_dup_STANDBYDB_<date>.log # Monitor datafile copy progress du -sh /u01/PRIMARYDB/datafiles/
FINISHED status or any ORA- errorsdu -sh /u01/PRIMARYDB/datafiles/ gives a real-time view of how much data has been copiedEnable Data Guard redo log shipping on the primary database by setting log_archive_config and log_archive_dest_3 to ship redo logs to the standby. This is what makes the standby database receive real-time changes from the primary — the foundation of Data Guard continuous protection.
ALTER SYSTEM SET log_archive_config='DG_CONFIG=(PRIMARYDB,STANDBYDB)' SCOPE=BOTH SID='*'; ALTER SYSTEM SET log_archive_dest_3='SERVICE=STANDBYDB ASYNC VALID_FOR=(ONLINE_LOGFILES,PRIMARY_ROLE) DB_UNIQUE_NAME=STANDBYDB' SCOPE=BOTH SID='*'; ALTER SYSTEM SET log_archive_dest_state_3=ENABLE SCOPE=BOTH SID='*'; SHOW PARAMETER log_archive_config;
Configure Standby Redo Log (SRL) groups on the standby database. SRLs are the buffer where the standby receives redo data before applying it. They must match the primary redo log size (200M in this implementation) and there should be one more SRL group than the number of online redo log groups on the primary.
-- Set to MANUAL before making changes ALTER SYSTEM SET standby_file_management=MANUAL; -- Drop existing standby redo log groups ALTER DATABASE DROP STANDBY LOGFILE GROUP 4; ALTER DATABASE DROP STANDBY LOGFILE GROUP 5; ALTER DATABASE DROP STANDBY LOGFILE GROUP 6; ALTER DATABASE DROP STANDBY LOGFILE GROUP 7; -- Recreate matching primary redo log size (200M) ALTER DATABASE ADD STANDBY LOGFILE GROUP 4 ('/u01/PRIMARYDB/redologs1/srl04_01.log', '/u01/PRIMARYDB/redologs2/srl04_02.log') SIZE 200M; ALTER DATABASE ADD STANDBY LOGFILE GROUP 5 ('/u01/PRIMARYDB/redologs1/srl05_01.log', '/u01/PRIMARYDB/redologs2/srl05_02.log') SIZE 200M; ALTER DATABASE ADD STANDBY LOGFILE GROUP 6 ('/u01/PRIMARYDB/redologs1/srl06_01.log', '/u01/PRIMARYDB/redologs2/srl06_02.log') SIZE 200M; ALTER DATABASE ADD STANDBY LOGFILE GROUP 7 ('/u01/PRIMARYDB/redologs1/srl07_01.log', '/u01/PRIMARYDB/redologs2/srl07_02.log') SIZE 200M; -- Re-enable AUTO after changes ALTER SYSTEM SET standby_file_management=AUTO; SELECT * FROM v$logfile;
redologs1 and redologs2) for redundancystandby_file_management=MANUAL before dropping/adding — return to AUTO afterv$logfile with type STANDBYStart the Managed Recovery Process (MRP) on the standby database. MRP is the background process that continuously receives redo data shipped from the primary and applies it to the standby, keeping the databases synchronized. The USING CURRENT LOGFILE option enables Real-Time Apply — redo is applied as it arrives in the standby redo logs without waiting for archiving.
-- Start real-time redo apply ALTER DATABASE RECOVER MANAGED STANDBY DATABASE USING CURRENT LOGFILE DISCONNECT; -- Verify database role and status SELECT name, database_role, protection_mode, protection_level, open_mode, switchover_status FROM v$database;
DISCONNECT runs MRP as a background process — SQL*Plus session is freeddatabase_role: PHYSICAL STANDBYopen_mode: MOUNTED (or READ ONLY if opened for reporting)switchover_status: NOT ALLOWED (while MRP is running)Verify redo log sequence numbers between primary and standby to confirm synchronization. The Difference column should be 0 or very close to 0 for a healthy Data Guard configuration. A persistent gap indicates redo shipping or apply issues that require investigation.
SELECT
ARCH.THREAD# AS "Thread",
ARCH.SEQUENCE# AS "Last Sequence Received",
APPL.SEQUENCE# AS "Last Sequence Applied",
(ARCH.SEQUENCE# - APPL.SEQUENCE#) AS "Difference"
FROM
(SELECT THREAD#, SEQUENCE# FROM V$ARCHIVED_LOG
WHERE (THREAD#, FIRST_TIME) IN
(SELECT THREAD#, MAX(FIRST_TIME)
FROM V$ARCHIVED_LOG GROUP BY THREAD#)) ARCH,
(SELECT THREAD#, SEQUENCE# FROM V$LOG_HISTORY
WHERE (THREAD#, FIRST_TIME) IN
(SELECT THREAD#, MAX(FIRST_TIME)
FROM V$LOG_HISTORY GROUP BY THREAD#)) APPL
WHERE ARCH.THREAD# = APPL.THREAD#
ORDER BY 1;Force a log switch on the primary and verify that the new sequence is received and applied on the standby. This is the most direct validation that the entire redo shipping pipeline is working end-to-end.
ALTER SYSTEM SWITCH LOGFILE; SELECT thread#, MAX(sequence#) AS "Last Standby Seq Generated" FROM v$archived_log val, v$database vdb WHERE val.resetlogs_change# = vdb.resetlogs_change# GROUP BY thread# ORDER BY thread#;
Monitor all Data Guard health metrics using Oracle dynamic performance views. These queries should be part of every DBA's ongoing operational runbook for a Data Guard environment.
-- Transport and apply lag SELECT name, value, unit FROM v$dataguard_stats WHERE name IN ('transport lag','apply lag'); -- MRP process status SELECT process, status, thread#, sequence# FROM v$managed_standby; -- Archive gap check SELECT * FROM v$archive_gap; -- Database block corruption check SELECT * FROM v$database_block_corruption; -- Datafile status SELECT file#, name, status FROM v$datafile; -- Standby redo log groups SELECT group#, thread#, bytes/1024/1024 MB, status FROM v$standby_log;
transport lag and apply lag — should both show +00 00:00:00 (zero lag)process = MRP0, status = APPLYING_LOGv$archive_gap — should return no rowsv$database_block_corruption — should return no rowsv$datafile should have status ONLINEEnable the Data Guard Broker on both databases. The Broker provides centralized management, automated switchover/failover, and a single interface (dgmgrl) to manage the entire Data Guard configuration. Setting dg_broker_start=TRUE starts the DMON background process on each instance.
SHOW PARAMETER dg_broker_start; ALTER SYSTEM SET dg_broker_start=TRUE SCOPE=BOTH; SHOW PARAMETER broker_config_file;
Before creating the Broker configuration, clear the manual LOG_ARCHIVE_DEST_2 and LOG_ARCHIVE_DEST_3 settings on the standby. The Broker will manage redo shipping configuration automatically — having both manual and Broker-managed settings conflicts and causes errors.
ALTER SYSTEM SET LOG_ARCHIVE_DEST_2='' SCOPE=BOTH; ALTER SYSTEM SET LOG_ARCHIVE_DEST_3='' SCOPE=BOTH; SHOW PARAMETER LOG_ARCHIVE_DEST;
Create the Data Guard Broker configuration using dgmgrl. This registers the primary database, adds the standby database, and then enables the complete configuration. Once enabled, the Broker takes over management of redo shipping and monitors both databases continuously.
dgmgrl / as sysdba CREATE CONFIGURATION DG_PRIMARYDB AS PRIMARY DATABASE IS PRIMARYDB CONNECT IDENTIFIER IS PRIMARYDB; ADD DATABASE STANDBYDB AS CONNECT IDENTIFIER IS STANDBYDB MAINTAINED AS PHYSICAL; ENABLE CONFIGURATION; SHOW CONFIGURATION; SHOW DATABASE VERBOSE PRIMARYDB; SHOW DATABASE VERBOSE STANDBYDB;
SHOW CONFIGURATION should display SUCCESS statusSHOW DATABASE outputSHOW DATABASE VERBOSE$ORACLE_BASE/diag/rdbms/ for troubleshootingPerform a final end-to-end validation after Broker setup. Force a log switch on the primary and verify the sequence number advances identically on the standby. The sequence difference should be zero, confirming the Broker-managed Data Guard configuration is fully operational.
ALTER SYSTEM SWITCH LOGFILE; SELECT thread#, MAX(sequence#) AS "Last Seq Generated" FROM v$archived_log val, v$database vdb WHERE val.resetlogs_change# = vdb.resetlogs_change# GROUP BY thread# ORDER BY thread#;
SELECT
ARCH.THREAD# AS "Thread",
ARCH.SEQUENCE# AS "Last Sequence Received",
APPL.SEQUENCE# AS "Last Sequence Applied",
(ARCH.SEQUENCE# - APPL.SEQUENCE#) AS "Difference"
FROM
(SELECT THREAD#, SEQUENCE# FROM V$ARCHIVED_LOG
WHERE (THREAD#, FIRST_TIME) IN
(SELECT THREAD#, MAX(FIRST_TIME)
FROM V$ARCHIVED_LOG GROUP BY THREAD#)) ARCH,
(SELECT THREAD#, SEQUENCE# FROM V$LOG_HISTORY
WHERE (THREAD#, FIRST_TIME) IN
(SELECT THREAD#, MAX(FIRST_TIME)
FROM V$LOG_HISTORY GROUP BY THREAD#)) APPL
WHERE ARCH.THREAD# = APPL.THREAD#
ORDER BY 1;The cross-cloud Oracle Data Guard configuration was completed successfully across all implementation phases. All validation checks passed with zero transport and apply lag.
PRIMARYDB → STANDBYDB and PRIMARYDB2 → STANDBYDB2 on the target cloud platform.log_archive_dest_3. MRP running continuously with USING CURRENT LOGFILE for zero-delay real-time apply.DG_PRIMARYDB configuration. Both databases show SUCCESS status. Centralized monitoring and automated failover capability enabled.