A production-ready guide to installing Oracle Database 19c SE2 with two independent databases, dedicated listeners, ARCHIVELOG, RMAN backup strategy, and systemd auto-start. Built from real deployment experience.
- Two independent databases — PROD1 and PROD2 on separate mount points, fully isolated data and FRA
- Dedicated listeners —
LISTENER_PROD1on1521,LISTENER_PROD2on15210, each registered independently - ARCHIVELOG mode + FORCE LOGGING — both instances ready for RMAN and standby
- Fast Recovery Area — per-database FRA with configurable size
- Systemd integration — listeners and databases start/stop automatically on boot via two service units
- RMAN backup strategy — incremental level 0 + archivelog backup with obsolete cleanup
- Hardened OS — SELinux enforcing, firewalld with only required ports open
- Silent install — fully scriptable, no GUI required
dbhost1.example.com
│
├── Oracle Home (shared)
│ └── /u01/app/oracle/product/19c/dbhome_1
│
├── PROD1
│ ├── Data: /u02/oradata/PROD1
│ ├── FRA: /u02/fra/PROD1
│ └── Listener: LISTENER_PROD1 → TCP 1521
│
└── PROD2
├── Data: /u03/oradata/PROD2
├── FRA: /u03/fra/PROD2
└── Listener: LISTENER_PROD2 → TCP 15210
| Approach | Trade-off |
|---|---|
Single LISTENER on 1521, both DBs registered |
Cannot independently stop/start listener per database; harder to isolate connections |
| Dedicated listener per database | ✅ Independent control, cleaner separation, easier firewall rules per service |
oracle19c-two-instances/
├── config/
│ ├── listener.ora # Dual-listener network config
│ └── tnsnames.ora # Local TNS aliases for both instances
├── scripts/
│ ├── 01_os_prep.sh # Hostname, hosts file, DNF packages
│ ├── 02_user_dirs.sh # Mount points and ownership
│ ├── 03_env_profile.sh # oracle user bash_profile
│ ├── 04_install_sw.sh # Silent Oracle software install
│ ├── 05_patch_ru.sh # OPatch replacement and RU apply
│ ├── 07_create_db.sh # DBCA silent DB creation (both instances)
│ ├── 08_archivelog.sql # Enable ARCHIVELOG + FRA (run per instance)
│ ├── 09_systemd_setup.sh # Install and enable systemd service units
│ ├── 10_firewall.sh # Firewalld rules
│ └── 11_validate.sh # Post-install validation
├── rman/
│ └── backup_level0.rman # RMAN incremental level 0 + archivelog script
├── systemd/
│ ├── oracle-listeners.service # Systemd unit — both listeners
│ └── oracle-databases.service # Systemd unit — dbstart / dbshut
└── README.md
- Oracle Linux 8 (minimal install)
- Oracle Database 19c software ZIP (
LINUX.X64_193000_db_home.zip) - OPatch 12.2.0.1.x or later ZIP
- Desired Release Update (RU) patch ZIP
- Root or sudo access
/u01,/u02,/u03mount points available (separate LUNs or partitions recommended)
sudo hostnamectl set-hostname dbhost1.example.com
echo "192.0.2.10 dbhost1.example.com dbhost1" | sudo tee -a /etc/hosts
sudo dnf update -y
sudo dnf install -y oracle-database-preinstall-19c unzip taroracle-database-preinstall-19c automatically:
- Creates the
oracleuser andoinstall/dbagroups - Sets kernel parameters (
shmall,shmmax,semaphores, etc.) - Configures
/etc/security/limits.conffor Oracle
sudo mkdir -p /u01/app/oracle/product/19c/dbhome_1
sudo mkdir -p /u01/app/oraInventory
sudo mkdir -p /u02/oradata/PROD1 /u02/fra/PROD1
sudo mkdir -p /u03/oradata/PROD2 /u03/fra/PROD2
sudo chown -R oracle:oinstall /u01 /u02 /u03
sudo chmod -R 775 /u01 /u02 /u03Add to /home/oracle/.bash_profile (or run scripts/03_env_profile.sh):
export ORACLE_BASE=/u01/app/oracle
export ORACLE_HOME=/u01/app/oracle/product/19c/dbhome_1
export ORA_INVENTORY=/u01/app/oraInventory
export PATH=$ORACLE_HOME/bin:$PATH
export ORACLE_HOSTNAME=dbhost1.example.comsource /home/oracle/.bash_profilesu - oracle
cd $ORACLE_HOME
unzip -oq /path/to/LINUX.X64_193000_db_home.zip
export CV_ASSUME_DISTID=OEL7.6
./runInstaller -ignorePrereq \
-waitforcompletion \
-silent \
-responseFile $ORACLE_HOME/install/response/db_install.rsp \
oracle.install.option=INSTALL_DB_SWONLY \
ORACLE_HOSTNAME=${ORACLE_HOSTNAME} \
UNIX_GROUP_NAME=oinstall \
INVENTORY_LOCATION=${ORA_INVENTORY} \
ORACLE_HOME=${ORACLE_HOME} \
ORACLE_BASE=${ORACLE_BASE} \
oracle.install.db.InstallEdition=SE2 \
oracle.install.db.OSDBA_GROUP=dba \
oracle.install.db.OSBACKUPDBA_GROUP=dba \
oracle.install.db.OSDGDBA_GROUP=dba \
oracle.install.db.OSKMDBA_GROUP=dba \
oracle.install.db.OSRACDBA_GROUP=dba \
SECURITY_UPDATES_VIA_MYORACLESUPPORT=false \
DECLINE_SECURITY_UPDATES=trueRun root scripts when prompted:
sudo /u01/app/oraInventory/orainstRoot.sh
sudo $ORACLE_HOME/root.shsu - oracle
# Replace OPatch
rm -rf $ORACLE_HOME/OPatch
unzip -oq /path/to/p6880880_190000_Linux-x86-64.zip -d $ORACLE_HOME
export PATH=$ORACLE_HOME/OPatch:$PATH
# Apply RU
unzip -oq /path/to/pNNNNNNNN_190000_Linux-x86-64.zip -d /tmp/ru
opatch apply -silent /tmp/ru/NNNNNNNNNVerify:
opatch lspatchesCopy config/listener.ora and config/tnsnames.ora to $ORACLE_HOME/network/admin/ or create them directly:
listener.ora
LISTENER_PROD1 =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = dbhost1.example.com)(PORT = 1521))
)
)
LISTENER_PROD2 =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = dbhost1.example.com)(PORT = 15210))
)
)tnsnames.ora
PROD1_LOCAL =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = dbhost1.example.com)(PORT = 1521))
(CONNECT_DATA = (SERVICE_NAME = PROD1))
)
PROD2_LOCAL =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = dbhost1.example.com)(PORT = 15210))
(CONNECT_DATA = (SERVICE_NAME = PROD2))
)Start listeners:
lsnrctl start LISTENER_PROD1
lsnrctl start LISTENER_PROD2PROD1:
su - oracle
dbca -silent -createDatabase \
-templateName General_Purpose.dbc \
-gdbname PROD1 -sid PROD1 \
-datafileDestination /u02/oradata \
-characterSet AL32UTF8 \
-sysPassword 'StrongPass_1234' \
-systemPassword 'StrongPass_1234' \
-emConfiguration NONE
export ORACLE_SID=PROD1
sqlplus / as sysdba <<'SQL'
ALTER SYSTEM SET local_listener='PROD1_LOCAL' SCOPE=BOTH;
SHUTDOWN IMMEDIATE;
STARTUP;
SQL
lsnrctl status LISTENER_PROD1PROD2 (repeat with PROD2 parameters):
dbca -silent -createDatabase \
-templateName General_Purpose.dbc \
-gdbname PROD2 -sid PROD2 \
-datafileDestination /u03/oradata \
-characterSet AL32UTF8 \
-sysPassword 'StrongPass_1234' \
-systemPassword 'StrongPass_1234' \
-emConfiguration NONE
export ORACLE_SID=PROD2
sqlplus / as sysdba <<'SQL'
ALTER SYSTEM SET local_listener='PROD2_LOCAL' SCOPE=BOTH;
SHUTDOWN IMMEDIATE;
STARTUP;
SQL
lsnrctl status LISTENER_PROD2Run for each instance (change ORACLE_SID, path, and size accordingly):
su - oracle
export ORACLE_SID=PROD1
sqlplus / as sysdba <<'SQL'
ALTER SYSTEM SET db_recovery_file_dest_size = 100G SCOPE=BOTH;
ALTER SYSTEM SET db_recovery_file_dest = '/u02/fra/PROD1' SCOPE=BOTH;
SHUTDOWN IMMEDIATE;
STARTUP MOUNT;
ALTER DATABASE ARCHIVELOG;
ALTER DATABASE OPEN;
ALTER DATABASE FORCE LOGGING;
ARCHIVE LOG LIST;
SHOW PARAMETER db_recovery_file_dest;
SQLRepeat for PROD2 with /u03/fra/PROD2.
Add both databases to /etc/oratab:
PROD1:/u01/app/oracle/product/19c/dbhome_1:Y
PROD2:/u01/app/oracle/product/19c/dbhome_1:YCopy service units from systemd/ or install them directly:
sudo cp systemd/oracle-listeners.service /etc/systemd/system/
sudo cp systemd/oracle-databases.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable oracle-listeners.service oracle-databases.service
sudo systemctl start oracle-listeners.service oracle-databases.service
sudo systemctl status oracle-listeners.service oracle-databases.servicesudo firewall-cmd --permanent --add-port=1521/tcp
sudo firewall-cmd --permanent --add-port=15210/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-ports
# → 1521/tcp 15210/tcpsu - oracle
lsnrctl status LISTENER_PROD1
lsnrctl status LISTENER_PROD2
tnsping PROD1_LOCAL
tnsping PROD2_LOCAL
export ORACLE_SID=PROD1
sqlplus / as sysdba <<'SQL'
SELECT name, open_mode, log_mode FROM v$database;
SQL
export ORACLE_SID=PROD2
sqlplus / as sysdba <<'SQL'
SELECT name, open_mode, log_mode FROM v$database;
SQLExpected output for each database:
NAME OPEN_MODE LOG_MODE
------- -------------------- ------------
PROD1 READ WRITE ARCHIVELOG
Use rman/backup_level0.rman or run directly:
su - oracle
export ORACLE_SID=PROD1
rman target / <<'EOF'
RUN {
CONFIGURE CONTROLFILE AUTOBACKUP ON;
CONFIGURE RETENTION POLICY TO RECOVERY WINDOW OF 7 DAYS;
BACKUP INCREMENTAL LEVEL 0 DATABASE PLUS ARCHIVELOG;
DELETE NOPROMPT OBSOLETE;
}
EOFRepeat for PROD2. Schedule via cron:
# Crontab entry for oracle user — level 0 every Sunday at 01:00
0 1 * * 0 ORACLE_SID=PROD1 $ORACLE_HOME/bin/rman target / @/home/oracle/rman/backup_level0.rman >> /home/oracle/rman/logs/PROD1_$(date +\%F).log 2>&1
0 2 * * 0 ORACLE_SID=PROD2 $ORACLE_HOME/bin/rman target / @/home/oracle/rman/backup_level0.rman >> /home/oracle/rman/logs/PROD2_$(date +\%F).log 2>&1- SELinux set to
enforcing— verify withgetenforce - Firewalld active with only ports 1521 and 15210 open
- ARCHIVELOG mode confirmed on both instances
- RMAN backups scheduled and tested
- Release Updates applied quarterly
-
SYSandSYSTEMpasswords changed from defaults - Listener passwords set if required by policy
-
oratabentries set toYfor both instances (enablesdbstart/dbshut) - Systemd services enabled:
oracle-listenersandoracle-databases
# Check listener log
cat $ORACLE_BASE/diag/tnslsnr/$(hostname)/listener_prod1/alert/log.xml | tail -50
# Verify port not in use
ss -tlnp | grep -E '1521|15210'
# Try manual start with debug
lsnrctl start LISTENER_PROD1
lsnrctl status LISTENER_PROD1# Verify local_listener parameter
sqlplus / as sysdba <<'SQL'
SHOW PARAMETER local_listener;
-- Should show PROD1_LOCAL or PROD2_LOCAL
ALTER SYSTEM REGISTER;
SQL
lsnrctl status LISTENER_PROD1
# Service "PROD1" should appearjournalctl -u oracle-databases.service -n 50
# Verify oratab entries
grep -E 'PROD1|PROD2' /etc/oratab
# Both lines must end with :Y
# Test dbstart manually as oracle user
$ORACLE_HOME/bin/dbstart $ORACLE_HOMEexport ORACLE_SID=PROD1
rman target / <<'EOF'
CROSSCHECK ARCHIVELOG ALL;
DELETE NOPROMPT ARCHIVELOG ALL COMPLETED BEFORE 'SYSDATE-2';
EOF
# Check FRA usage
sqlplus / as sysdba <<'SQL'
SELECT * FROM v$recovery_area_usage;
SQL- Change default passwords —
SYSandSYSTEMuse placeholder passwords in this guide; replace before going live - Dedicated OS mount points — separate
/u02and/u03prevent one database from consuming all disk space - SELinux enforcing — do not disable; Oracle 19c runs correctly with enforcing mode on Oracle Linux 8
- Minimal firewall exposure — only listener ports are open; no EM Express, no HTTP/HTTPS unless required
- Listener without
EXTPROC— the listener config in this guide does not expose external procedure calls
MIT — free to use, modify, and distribute.
Przemysław Pradela
Built through real production deployment on Oracle Linux 8.
Contributions and issue reports welcome.