A few months ago I read Ludovico Caldara’s article on TLS authentication for Data Guard redo transport and thought, “I really need to try this.”
So I bookmarked it. Which, as we all know, is practically the same as building a lab. Right?
Eventually I found the time. There was one part of his example I wanted to approach differently: the certificates. Ludovico creates a lab CA and signs everything himself. That makes perfect sense when you want a reproducible demo, but it reminded me of something I’d encountered configuring Oracle GoldenGate at work.
I remembered a GoldenGate configuration I worked on for a customer. Their corporate PKI team sent me a .crt and a .p7b. As far as they were concerned, the certificate request was complete. Fair enough. But I still had to figure out what to do with those files on the Oracle side.
I asked Ludovico, Oracle Product Manager for Data Guard, whether OpenSSL-generated certificates could be used for the newer redo authentication feature. He confirmed they could. So this became my test: start with a CSR, pretend the corporate PKI team has returned the signed files, and get the whole thing working in Data Guard. No tutorial on running a Certificate Authority required.
A quick word about ORAPKI
This isn’t an OpenSSL versus orapki competition. You can generate a certificate request with either tool. The interesting difference is where the private key lives and what happens after the signed certificate comes back. orapki creates the key in an Oracle wallet; OpenSSL can keep it as a separate file, which means some extra wallet preparation later.
The lab
The database and Oracle Net tools in this test belong to Oracle AI Database 26ai, release 23.26.3.0.0, with OpenSSL 1.1.1k FIPS on the preparation host. The redo transport TLS authentication capability was introduced with release 23.26.2.0.0. The setup is a single-instance primary and physical standby managed by Data Guard Broker.
Primary: DB_UNIQUE_NAME = DB1015_m8s_iad Host = dbverse.sub08211110580.vcndbverselabs.oraclevcn.comStandby: DB_UNIQUE_NAME = DB1015_gdz_iad Host = dbverse-stby.sub08211110580.vcndbverselabs.oraclevcn.comTCPS listener port = 2484
I used a temporary lab CA to simulate the PKI service. In a real company, the issuing CA and certificate profile are normally managed elsewhere. The test root was trusted by both database hosts after the certificate-chain checks; the lab CA itself is not a production PKI recommendation.
1. Request the certificates
Two routes are possible. They lead to the same corporate PKI handoff, but you should stick to the route you chose for each identity. A signed certificate must match the private key from the original request.
ORAPKI: the key starts inside the wallet
With orapki, you create the wallet, generate the request and export the CSR. The signed certificate then goes back into that same wallet. That’s the documented Oracle workflow. For this experiment, though, I wanted to see what happened when I started with OpenSSL instead.
# Example on the primary, with a new wallet directoryPRIMARY_HOST="dbverse.sub08211110580.vcndbverselabs.oraclevcn.com"ORA_WALLET="$HOME/dg-tls-primary/orapki-wallet"orapki wallet create -wallet "$ORA_WALLET"orapki wallet add -wallet "$ORA_WALLET" \ -dn "CN=$PRIMARY_HOST" -keysize 2048orapki wallet export -wallet "$ORA_WALLET" \ -dn "CN=$PRIMARY_HOST" \ -request "$HOME/dg-tls-primary/primary-orapki.csr"
OpenSSL: the route I used
Here I generated a private key and CSR on the database host. I kept the subject short, using only the CN, in line with Ludovico’s example. I supplied the DNS SAN and intended TLS usages to the simulated issuer separately.
PRIMARY_HOST="dbverse.sub08211110580.vcndbverselabs.oraclevcn.com"WORK="$HOME/dg-tls-primary"umask 077mkdir -p "$WORK"cd "$WORK"# Run only for a new request; do not overwrite an existing key.if [ -e primary.key ] || [ -e primary.csr ]; then echo "Existing key/CSR found. Inspect and reuse; no files overwritten."else openssl genpkey -algorithm RSA \ -pkeyopt rsa_keygen_bits:3072 -out primary.key openssl req -new -sha256 -key primary.key \ -out primary.csr -subj "/CN=${PRIMARY_HOST}"fi# If only one of the two files exists, inspection will fail safely.openssl req -in primary.csr -noout -subject -verify

For the standby I repeated the process with a different key and hostname. The CA does not need the database private key to sign the CSR; it needs the request and whatever additional identity/profile information its policy requires.
2. The PKI team replies. Now what?
Now imagine the PKI ticket is closed and these files appear in your received directory. It’s pretty close to what I was handed during that GoldenGate work:
dg-tls-primary/ primary.key # Already on the database host primary.csr # Sent to PKI received/ primary.crt # Signed database certificate primary-chain.p7b # PKCS#7 certificate bundle ca.crt # Trusted test CA certificate (lab case)
A .crt or .cer is usually a public certificate. A .p7b is a collection of certificates, often the issuing chain, not a private-key container. It may contain the leaf, intermediates and/or root; don’t assume the order or contents from the filename.
First I inspected the issued certificate and unpacked the PKCS#7 bundle using OpenSSL. The example P7B was DER encoded; a PEM-encoded bundle needs -inform PEM instead.
cd "$HOME/dg-tls-primary"mkdir -p received/extractedopenssl x509 -in received/primary.crt \ -noout -subject -issuer -dates -ext subjectAltNameopenssl pkcs7 -inform DER \ -in received/primary-chain.p7b \ -print_certs -out received/returned-certs.pemawk '/-----BEGIN CERTIFICATE-----/ { n++ file=sprintf("received/extracted/cert-%02d.pem",n)}file != "" { print > file }/-----END CERTIFICATE-----/ { close(file); file="" }' received/returned-certs.pemfor cert in received/extracted/*.pem; do openssl x509 -in "$cert" -noout -subject -issuer -datesdone

For this lab the CA signed the database certificate directly, so there were no intermediates. Once I’d verified the lab CA certificate, preparing the PEM chain was simple:
# Lab only: a verified, one-level test CAcp received/ca.crt received/ca-chain.pemopenssl verify -CAfile received/ca.crt \ -verify_hostname "dbverse.sub08211110580.vcndbverselabs.oraclevcn.com" \ received/primary.crt
With a real corporate PKI, get the root from an approved source and assemble the correct intermediate chain. The files sent back by the PKI service are not automatically your trust policy. I also checked that the public key in the issued certificate matched the private key used for my CSR:
openssl pkey -in primary.key -pubout -outform DER \ | openssl dgst -sha256openssl x509 -in received/primary.crt -pubkey -noout \ | openssl pkey -pubin -outform DER \ | openssl dgst -sha256
Both fingerprints should be identical. If they aren’t, wallet creation is not going to fix the mismatch.
3. From signed files to an Oracle wallet
If ORAPKI generated the CSR
The key is already inside the original Oracle wallet. Import the approved CA chain (intermediate certificates where applicable), then the signed user certificate. There is no reason to generate another key.
ORA_WALLET="$HOME/dg-tls-primary/orapki-wallet"cd "$HOME/dg-tls-primary"# One-level lab CA; import intermediates separately if present.orapki wallet add -wallet "$ORA_WALLET" \ -trusted_cert -cert received/ca.crtorapki wallet add -wallet "$ORA_WALLET" \ -user_cert -cert received/primary.crtorapki wallet create -wallet "$ORA_WALLET" -auto_loginorapki wallet display -wallet "$ORA_WALLET" -complete
If OpenSSL generated the CSR
In my lab, I started with primary.key, the signed primary.crt, and the CA chain. OpenSSL combined them into a password-protected PKCS#12 file:
cd "$HOME/dg-tls-primary"STAGING="$HOME/dg-tls-primary/openssl-p12"umask 077mkdir -p "$STAGING"openssl pkcs12 -export \ -inkey primary.key \ -in received/primary.crt \ -certfile received/ca-chain.pem \ -name dg-primary \ -out "$STAGING/ewallet.p12"openssl pkcs12 -in "$STAGING/ewallet.p12" -info -noout
So far, so good. OpenSSL could read the file, and orapki wallet display recognized both the database certificate and the trusted CA. I enabled auto-login and got the reassuring message that the operation had completed successfully.

Unfortunately, the listener had other opinions.
4. The wallet that looked fine, until it didn’t
The original wallet still prompted for a password when I ran orapki wallet display, even after auto-login creation. Then the listener failed as it tried to initialize TCPS:
ORA-29113: Cannot access attributes from a PKCS #12 key bag.TNS-00566: SSL protocol adapter failed to open wallet

At first I suspected auto-login. OpenSSL could read the PKCS#12 and decrypt the private key, while ORAPKI could list the database certificate and the trusted CA. So the contents were there, but something still wasn’t behaving as expected. I couldn’t tell whether the problem was auto-login itself or how Oracle was processing the original wallet and its key bag.
Instead of generating another certificate, I created a new Oracle wallet, imported the same OpenSSL PKCS#12, and enabled auto-login again:
SOURCE="$HOME/dg-tls-primary/openssl-p12/ewallet.p12"NATIVE="$HOME/dg-tls-primary/wallet-native-test"# Use a fresh directory. The commands prompt for passwords.orapki wallet create -wallet "$NATIVE"orapki wallet import_pkcs12 \ -wallet "$NATIVE" \ -pkcs12file "$SOURCE"orapki wallet create -wallet "$NATIVE" -auto_loginorapki wallet display -wallet "$NATIVE" -complete


This time, the wallet display didn’t ask for a password. And after deploying the resulting wallet into the configured listener wallet location, the listener started on TCPS port 2484.
Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(...)(PORT=1521)))Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(...)(PORT=2484)))The command completed successfully
Same certificate. Same private key. Different wallet preparation. That is what resolved the listener startup failure in my 23.26.3.0.0 lab. I haven’t isolated the exact internal difference, so I’m not claiming that OpenSSL-created PKCS#12 files are generally incompatible or that auto-login was definitively the root cause.
One operational detail: in my OCI database environment, the listener wallet was under /opt/oracle/dcs/commonstore/tcps_wallet/. I backed up the existing primary wallet before replacing it; there wasn’t an equivalent wallet to back up on the standby. That path belongs to the DCS-managed area in this deployment, so don’t assume it’s a universal TCPS wallet location or copy files into a managed directory without checking its lifecycle and support implications.
5. Getting Data Guard onto certificates
A running TCPS listener was only the first milestone. We still needed both database hosts to trust each other’s certificates and Oracle to associate each sending database with an external identity.
After the wallet issue was sorted out, I configured TCPS on both hosts: a listener endpoint on port 2484, certificate-aware client/server settings, and a TNS alias for each database. Ludovico covers the overall Data Guard procedure in his article, so I’ll concentrate here on the settings I actually used and what I checked afterward.
Here’s the primary configuration. The standby uses its own hostname, service and wallet, but follows the same pattern.
# listener.ora Network Configuration File: /u01/app/oracle/product/23.0.0/dbhome_1/network/admin/listener.ora# Generated by Oracle configuration tools.#listener.oraNetworkConfigurationFile:/u01/app/oracle/product/23.0.0/dbhome_1/network/admin/listener.ora#GeneratedbyOracleconfigurationtools.SID_LIST_LISTENER = (SID_LIST = (SID_DESC = (GLOBAL_DBNAME = DB1015_m8s_iad.sub08211110580.vcndbverselabs.oraclevcn.com) (ORACLE_HOME = /u01/app/oracle/product/23.0.0/dbhome_1) (SID_NAME = DB1015) ) )WALLET_LOCATION = (SOURCE = (METHOD = FILE) (METHOD_DATA = (DIRECTORY = /opt/oracle/dcs/commonstore/tcps_wallet) ) )LISTENER = (DESCRIPTION_LIST = (DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = dbverse.sub08211110580.vcndbverselabs.oraclevcn.com)(PORT = 1521)) (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1521)) (ADDRESS = (PROTOCOL = TCPS)(HOST = dbverse.sub08211110580.vcndbverselabs.oraclevcn.com)(PORT = 2484)) ) )
These are the lines I added or adjusted in sqlnet.ora for the test:
Two notes about this exact tested configuration: SSL_CLIENT_AUTHENTICATION=TRUE enables mutual TLS, although Oracle 26ai prefers the newer TLS_CLIENT_AUTHENTICATION parameter name. I kept the old spelling here because that’s what I tested; I haven’t repeated this lab using the newer spelling. I also retained SQLNET.FALLBACK_AUTHENTICATION=TRUE, as in Ludovico’s example. However, Oracle’s Net Services documentation describes that parameter as Kerberos authentication fallback. I haven’t established that it is needed for TLS redo authentication, and I’d like to clarify that separately rather than present it as a minimum requirement.
WALLET_LOCATION=(SOURCE=(METHOD=FILE)(METHOD_DATA=(DIRECTORY=/opt/oracle/dcs/commonstore/tcps_wallet)))
SSL_CLIENT_AUTHENTICATION=TRUE
SQLNET.AUTHENTICATION_SERVICES = (BEQ,TCPS)
SQLNET.FALLBACK_AUTHENTICATION=TRUE
I also added separate TCPS aliases to tnsnames.ora:
DB1015_M8S_IAD_TCPS=(DESCRIPTION=(SDU=65535)(SEND_BUF_SIZE=10485760)(RECV_BUF_SIZE=10485760)(ADDRESS=(PROTOCOL=TCPS)(HOST=dbverse.sub08211110580.vcndbverselabs.oraclevcn.com)(PORT=2484))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=DB1015_m8s_iad.sub08211110580.vcndbverselabs.oraclevcn.com)))DB1015_GDZ_IAD_TCPS=(DESCRIPTION=(SDU=65535)(SEND_BUF_SIZE=10485760)(RECV_BUF_SIZE=10485760)(ADDRESS=(PROTOCOL=TCPS)(HOST=dbverse-stby.sub08211110580.vcndbverselabs.oraclevcn.com)(PORT=2484))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=DB1015_gdz_iad.sub08211110580.vcndbverselabs.oraclevcn.com)))
Both hosts need entries that resolve correctly in both directions. I found that out the annoying way when local TCPS worked but connections between the hosts initially failed certificate validation. In that test I’d created separate lab CAs, so fixing the trust setup was part of getting the two sides to talk.
I restarted the listeners and checked that the TCPS endpoints appeared in lsnrctl status.
lsnrctl stoplsnrctl startlsnrctl status
The database instances also needed to pick up the updated wallet and Oracle Net settings in this setup.
I followed Ludovico’s procedure and restarted both instances after the wallet and Oracle Net changes. Oracle’s 26ai TLS setup instructions also describe restarting server and listener processes to pick up certain configuration changes. That doesn’t mean every certificate renewal always needs a database restart. This is a service interruption: I used STARTUP FORCE in the lab, but that performs an abort-style shutdown before starting the instance. For a managed production environment, plan a controlled restart using the appropriate database lifecycle tools instead.
sqlplus / as sysdba <<EOFstartup forceEOF
After restarting the relevant services in the lab, I checked the TNS aliases in both directions and used an actual SQL connection over TCPS. tnsping is a useful first check, but it doesn’t prove the database authenticated the client using its certificate.
sqlplus /@DB1015_M8S_IAD_TCPS as sysoperSELECT SYS_CONTEXT('USERENV','NETWORK_PROTOCOL') AS protocol, SYS_CONTEXT('USERENV','AUTHENTICATION_METHOD') AS auth_method, SYS_CONTEXT('USERENV','AUTHENTICATED_IDENTITY') AS identityFROM dual;

Before changing Broker, I checked the existing DGConnectIdentifier properties and validated the new TCPS aliases from both database hosts. Only after those connection tests succeeded did I switch the properties to the TCPS identifiers.
DGMGRL> show configurationConfiguration - db1015_dgconf Protection Mode: MaxPerformance Members: DB1015_m8s_iad - Primary database DB1015_gdz_iad - Physical standby databaseFast-Start Failover: DisabledConfiguration Status:SUCCESS (status updated 36 seconds ago)DGMGRL> show database DB1015_m8s_iad DGConnectIdentifier DGConnectIdentifier = 'DB1015_m8s_iad'DGMGRL> show database DB1015_gdz_iad DGConnectIdentifier DGConnectIdentifier = 'DB1015_gdz_iad'DGMGRL> validate DGConnectIdentifier DB1015_m8s_iad_tcpsAt instance 'DB1015' of member 'DB1015_m8s_iad' 'db1015_m8s_iad_tcps' translates to: (DESCRIPTION=(SDU=65535)(SEND_BUF_SIZE=10485760)(RECV_BUF_SIZE=10485760)(ADDRESS=(PROTOCOL=TCPS)(HOST=dbverse.sub08211110580.vcndbverselabs.oraclevcn.com)(PORT=2484))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=DB1015_m8s_iad.sub08211110580.vcndbverselabs.oraclevcn.com))) Environment Variables: TNS_ADMIN: ORACLE_HOME: /u01/app/oracle/product/23.0.0/dbhome_1 ORACLE_BASE: Initialization Parameters: LOCAL_LISTENER: Connection Test Result: Connected to instance 'DB1015' at member 'DB1015_m8s_iad'At instance 'DB1015' of member 'DB1015_gdz_iad' 'db1015_m8s_iad_tcps' translates to: (DESCRIPTION=(SDU=65535)(SEND_BUF_SIZE=10485760)(RECV_BUF_SIZE=10485760)(ADDRESS=(PROTOCOL=TCPS)(HOST=dbverse.sub08211110580.vcndbverselabs.oraclevcn.com)(PORT=2484))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=DB1015_m8s_iad.sub08211110580.vcndbverselabs.oraclevcn.com))) Environment Variables: TNS_ADMIN: ORACLE_HOME: /u01/app/oracle/product/23.0.0/dbhome_1 ORACLE_BASE: Initialization Parameters: LOCAL_LISTENER: LISTENER_DB1015 Connection Test Result: Connected to instance 'DB1015' at member 'DB1015_m8s_iad'DGMGRL> validate DGConnectIdentifier DB1015_gdz_iad_tcpsAt instance 'DB1015' of member 'DB1015_m8s_iad' 'db1015_gdz_iad_tcps' translates to: (DESCRIPTION=(SDU=65535)(SEND_BUF_SIZE=10485760)(RECV_BUF_SIZE=10485760)(ADDRESS=(PROTOCOL=TCPS)(HOST=dbverse-stby.sub08211110580.vcndbverselabs.oraclevcn.com)(PORT=2484))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=DB1015_gdz_iad.sub08211110580.vcndbverselabs.oraclevcn.com))) Environment Variables: TNS_ADMIN: ORACLE_HOME: /u01/app/oracle/product/23.0.0/dbhome_1 ORACLE_BASE: Initialization Parameters: LOCAL_LISTENER: Connection Test Result: Connected to instance 'DB1015' at member 'DB1015_gdz_iad'At instance 'DB1015' of member 'DB1015_gdz_iad' 'db1015_gdz_iad_tcps' translates to: (DESCRIPTION=(SDU=65535)(SEND_BUF_SIZE=10485760)(RECV_BUF_SIZE=10485760)(ADDRESS=(PROTOCOL=TCPS)(HOST=dbverse-stby.sub08211110580.vcndbverselabs.oraclevcn.com)(PORT=2484))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=DB1015_gdz_iad.sub08211110580.vcndbverselabs.oraclevcn.com))) Environment Variables: TNS_ADMIN: ORACLE_HOME: /u01/app/oracle/product/23.0.0/dbhome_1 ORACLE_BASE: Initialization Parameters: LOCAL_LISTENER: LISTENER_DB1015 Connection Test Result: Connected to instance 'DB1015' at member 'DB1015_gdz_iad'
DGMGRL> edit database DB1015_m8s_iad set property DGConnectIdentifier = DB1015_m8s_iad_tcps;Property "dgconnectidentifier" updated for member "db1015_m8s_iad".DGMGRL> show database DB1015_m8s_iad DGConnectIdentifier DGConnectIdentifier = 'db1015_m8s_iad_tcps'DGMGRL> edit database DB1015_gdz_iad set property DGConnectIdentifier = DB1015_gdz_iad_tcps;Property "dgconnectidentifier" updated for member "db1015_gdz_iad".DGMGRL> show database DB1015_gdz_iad DGConnectIdentifier DGConnectIdentifier = 'db1015_gdz_iad_tcps'DGMGRL> validate network configuration for all;Validating at "DB1015_m8s_iad" Instance "DB1015": successfully connected to instance "DB1015" on member "DB1015_gdz_iad"Validating at "DB1015_gdz_iad" Instance "DB1015": successfully connected to instance "DB1015" on member "DB1015_m8s_iad"Oracle Clusterware is not configured on database "DB1015_m8s_iad".Connecting to database "DB1015_m8s_iad" using static connect identifier "DB1015_M8S_IAD_DGMGRL" ...Succeeded.The static connect identifier allows for a connection to database "DB1015_m8s_iad".Oracle Clusterware is not configured on database "DB1015_gdz_iad".Connecting to database "DB1015_gdz_iad" using static connect identifier "DB1015_GDZ_IAD_DGMGRL" ...Succeeded.The static connect identifier allows for a connection to database "DB1015_gdz_iad".DGMGRL> show configuration;Configuration - db1015_dgconf Protection Mode: MaxPerformance Members: DB1015_m8s_iad - Primary database DB1015_gdz_iad - Physical standby databaseFast-Start Failover: DisabledConfiguration Status:SUCCESS (status updated 22 seconds ago)
Mapping the certificate identities
Oracle AI Database 26ai requires password-file format 12.2 for externally authenticated administrative users. Mine was in format 12, so I converted it before creating the certificate-mapped users. One warning about the command I actually ran: using the same path for FILE and INPUT_FILE with FORCE=Y replaces the existing password file. I didn’t make a separate backup first in this lab. Not a step I’d recommend repeating! For a real environment, back up the file or generate a new output file, validate it, and plan the replacement. ASM and managed database password files need their own supported procedure.
# Safer example for an OS-file-backed password file only.# This creates a new file; it does NOT replace the active one.PWFILE="$ORACLE_HOME/dbs/orapw$ORACLE_SID"PWFILE_NEW="${PWFILE}.12_2"# Keep a restorable copy before any migration.cp -p "$PWFILE" "${PWFILE}.backup.$(date +%Y%m%d%H%M%S)"orapwd FILE="$PWFILE_NEW" INPUT_FILE="$PWFILE" FORMAT=12.2# Arrange a controlled replacement appropriate for your platform.# Recheck after activation, not merely after generating PWFILE_NEW:# SQL> SELECT file_name, format FROM v$passwordfile_info;
In my test the active password file subsequently reported format 12.2. The example above deliberately creates a separate output instead of repeating my in-place FORCE=Y replacement. Check administrative access and the actual password-file location before and after any change.
I used one certificate identity for each database. The external name must match the certificate’s complete subject DN; in this lab the subject contains only a CN. The users and grants were created for the participating databases and verified in the password-file view.
-- In the CDB root, with appropriate administrative privileges:CREATE USER C##REDO_DB1015_M8S_IAD IDENTIFIED EXTERNALLY AS 'CN=dbverse.sub08211110580.vcndbverselabs.oraclevcn.com' CONTAINER=ALL;GRANT SYSOPER TO C##REDO_DB1015_M8S_IAD CONTAINER=ALL;CREATE USER C##REDO_DB1015_GDZ_IAD IDENTIFIED EXTERNALLY AS 'CN=dbverse-stby.sub08211110580.vcndbverselabs.oraclevcn.com' CONTAINER=ALL;GRANT SYSOPER TO C##REDO_DB1015_GDZ_IAD CONTAINER=ALL;
The sender needs to present its own identity, not whichever identity happens to belong to the current primary role. That matters when roles change.
-- On DB1015_m8s_iad:ALTER SYSTEM SET redo_transport_user=C##REDO_DB1015_M8S_IAD;-- On DB1015_gdz_iad:ALTER SYSTEM SET redo_transport_user=C##REDO_DB1015_GDZ_IAD;
For the Broker-managed configuration, I validated the TCPS connect descriptors before updating the relevant DGConnectIdentifier settings, then checked that Broker and redo transport remained healthy. The exact service names and listener registration depend on the environment; I used my existing database services rather than blindly copying a template.
Evidence from the receiving side
I also inspected the RFS processes on the receiving database, which is a more relevant check for redo authentication than another TCP or TCPS ping:
SELECT dg.name AS dg_process, dg.role, s.username, s.external_name, s.machineFROM v$dataguard_process dgJOIN v$process p ON p.spid = dg.pidJOIN v$session s ON s.paddr = p.addrWHERE LOWER(dg.name) LIKE '%rfs%';
In my RFS snapshot, RFS ping showed the sending database’s certificate subject in EXTERNAL_NAME: CN=dbverse.sub08211110580.vcndbverselabs.oraclevcn.com
6. One more test: switchover
Everything was looking good, but I wanted to make sure it wasn’t a setup that worked only while the original primary kept its role. So I validated both databases with Broker and switched over to DB1015_gdz_iad. No certificate recreation, no wallet replacement.
DGMGRL> VALIDATE DATABASE DB1015_m8s_iad;DGMGRL> VALIDATE DATABASE DB1015_gdz_iad;DGMGRL> SWITCHOVER TO DB1015_gdz_iad;
SHOW CONFIGURATION returned SUCCESS, with DB1015_gdz_iad as the new primary and DB1015_m8s_iad as physical standby.
DGMGRL> switchover to DB1015_gdz_iad;2026-10-28T22:47:43.149+00:00Performing switchover NOW, please wait...2026-10-28T22:47:43.174+00:00Operation requires a connection to database "DB1015_gdz_iad"Connecting ...Connected to "DB1015_gdz_iad"Connected as SYSDBA.2026-10-28T22:47:43.268+00:00Continuing with the switchover...2026-10-28T22:47:50.093+00:00New primary database "DB1015_gdz_iad" is opening...2026-10-28T22:47:50.093+00:00Operation requires start up of instance "DB1015" on database "DB1015_m8s_iad"Starting instance "DB1015"...Connected to an idle instance.ORACLE instance started.Connected to "DB1015_m8s_iad"Database mounted.Database opened.2026-10-28T22:48:08.308+00:00Switchover succeeded, new primary is "db1015_gdz_iad"2026-10-28T22:48:08.320+00:00Switchover processing complete, broker ready.DGMGRL> show configurationConfiguration - db1015_dgconf Protection Mode: MaxPerformance Members: DB1015_gdz_iad - Primary database DB1015_m8s_iad - Physical standby databaseFast-Start Failover: DisabledConfiguration Status:SUCCESS (status updated 26 seconds ago)
Broker completed the switchover, opened DB1015_gdz_iad as the new primary, and subsequently showed SUCCESS with the roles reversed. No certificate recreation, no wallet replacement. But I wanted one more piece of evidence from the receiving side after the roles changed.
One last check: RFS after switchover
After the switchover, DB1015_gdz_iad became the new primary, and DB1015_m8s_iad became the physical standby. Broker looked good, but there’s one more useful check: the certificate identity RFS sees in the reverse direction. On the new standby, run:
SET LINESIZE 250COLUMN USERNAME FORMAT A25COLUMN EXTERNAL_NAME FORMAT A85SELECT dg.name AS dg_process, dg.role, dg.action, s.username, s.external_name, s.machineFROM v$dataguard_process dgJOIN v$process p ON p.spid = dg.pidJOIN v$session s ON s.paddr = p.addrWHERE LOWER(dg.name) LIKE '%rfs%';

The field to inspect is EXTERNAL_NAME. On the new standby, compare it with the certificate subject of the new primary, CN=dbverse-stby.sub08211110580.vcndbverselabs.oraclevcn.com. This provides another identity check in the reverse direction, alongside the TCPS SQL connection and Broker evidence. The original screenshot is linked above so the output can be inspected at full resolution.
Final thoughts
So, did the OpenSSL experiment work? Yes, although not quite as I’d expected. OpenSSL generated the CSR and PKCS#12 without trouble, but my first wallet didn’t behave as expected when I enabled auto-login, and the listener failed with ORA-29113. Importing that same PKCS#12 into an Oracle-created wallet and enabling auto-login there resolved it. I still don’t know which internal difference mattered, so I can’t call it a general OpenSSL limitation. For Oracle wallet management, I’d reach for orapki next time.
But replacing orapki wasn’t really the point. I wanted to reproduce a familiar situation: the corporate PKI team signs your request, sends back a .crt and a .p7b, and you’re left to make them work with Oracle. In this lab, we got from those files to certificate-authenticated SQL connections, a working TCPS setup, and a successful Data Guard switchover.
Not bad for something that spent months in my bookmarks.
References
Oracle AI Database 26ai, sqlnet.ora Parameters (including authentication fallback and the newer TLS parameter names)
Ludovico Caldara, Configure Oracle Data Guard Redo Transport with TLS Authentication
Oracle AI Database 26ai, Redo Transport Services
Oracle AI Database 26ai, Managing Wallets and Certificates





Leave a Reply