Od
Databases Azure

Oracle Database Client 23ai on Oracle Linux 9 on Azure User Guide

| Product: Oracle Database Client 23ai on Oracle Linux 9 on Azure

Videos

Overview: Oracle Database Client 23ai on Oracle Linux 9 on Azure: overview (watch on YouTube)
Launch and log in: Oracle Database Client 23ai on Oracle Linux 9 on Azure: launch and log in (watch on YouTube)
Deep Dive: Oracle Database Client 23ai on Oracle Linux 9 on Azure: deep dive (watch on YouTube)

Overview

This guide covers the deployment and use of Oracle Database Client 23ai (23.26) on Oracle Linux 9, cloudimg's virtual machine image on the Azure Marketplace. The image carries the full Oracle Database Client 23ai, Administrator install, so a virtual machine can reach Oracle databases elsewhere on your network with SQL*Plus, SQL*Loader, Data Pump, Recovery Manager and the Oracle Net utilities, and so your own applications can link the Oracle Call Interface (OCI) libraries or the JDBC drivers. No database server runs on the virtual machine.

Everything below was checked on a virtual machine deployed from the public Marketplace image (cloudimg1647283583153:oracle-db-23ai-client-oel9:default:latest, image version 1.0.20260509), with every command run as azureuser or as the oracle user.

What's included

  • Oracle Database Client 23ai, release 23.26.1, Administrator install, in /u01/app/oracle/product/23.0.0/client_1
  • A dedicated oracle OS user that owns the client, with an environment script, /home/oracle/setEnv.sh
  • A system wide profile hook, /etc/profile.d/oracle-client.sh, so every login user (including azureuser) has the client tools on its PATH
  • SQL*Plus, SQL*Loader (sqlldr), Data Pump (expdp, impdp), the original exp and imp, Recovery Manager (rman), tnsping, adrci, tkprof, and the Oracle Wallet tools mkstore and orapki
  • The Oracle Call Interface libraries (libclntsh.so, libocci.so) and the JDBC drivers ojdbc8.jar, ojdbc11.jar and ojdbc17.jar
  • A pre seeded sqlnet.ora and a tnsnames.ora holding a commented example entry
  • The bundled Oracle JDK replaced by the system OpenJDK 8 package, so Java updates arrive with dnf
  • Oracle Linux 9 with the Azure Linux Agent (waagent), Gen2 (UEFI) boot and LVM

Platform: Microsoft Azure (Gen2, Hyper-V) Default user: azureuser Oracle Home: /u01/app/oracle/product/23.0.0/client_1 TNS Admin: /u01/app/oracle/product/23.0.0/client_1/network/admin

Prerequisites

Before deploying this image, ensure you have:

  1. An active Microsoft Azure subscription
  2. Access to the Azure Portal or the Azure CLI
  3. An SSH key pair for Linux virtual machine access
  4. The host name, port and service name of the Oracle Database you want to reach
  5. A network path from the new virtual machine to that database's listener (usually TCP 1521 outbound)

Choose a virtual machine size that suits your workload.

Step 1: Deploy the Virtual Machine

Option A: Azure Portal

  1. Open the Oracle Database Client 23ai (23.26) on Oracle Linux 9 listing on the Azure Marketplace and choose Get it now, then Create
  2. Configure the basics:
  3. Subscription: Select your Azure subscription
  4. Resource Group: Create new or select existing
  5. Virtual Machine Name: Enter a name for your VM
  6. Region: Select your preferred Azure region
  7. Size: Select a size for your workload
  8. Under Administrator Account, select SSH public key and enter your key
  9. Under Inbound Port Rules, allow SSH (port 22), ideally from your own network only
  10. Click Review + Create, then Create

Option B: Azure CLI

The image carries a Marketplace purchase plan, so accept its terms once per subscription, then create the virtual machine. Replace <your-vm-size> with the size you chose:

az vm image terms accept --urn cloudimg1647283583153:oracle-db-23ai-client-oel9:default:latest

az vm create \
  --resource-group myResourceGroup \
  --name my-oracle-client-vm \
  --image cloudimg1647283583153:oracle-db-23ai-client-oel9:default:latest \
  --size <your-vm-size> \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

The Oracle Client only makes outbound connections, so the virtual machine needs no inbound port other than SSH. Do not open port 1521 inbound on it. Instead, check that its network security group, route table and any peering or VPN let it reach your database's listener.

Step 2: Connect via SSH

Run these on your own workstation. Find the public IP, then connect:

az vm show --resource-group myResourceGroup --name my-oracle-client-vm --show-details --query publicIps -o tsv
ssh azureuser@<vm-ip>

There is no password to enter. If SSH asks for one, the key your client is offering is not the key you supplied at create time.

The first boot update

On its first boot the image brings its operating system packages up to date: root's crontab runs /stage/scripts/initial_boot_update.sh (dnf update -y) once, in the background, and logs to /stage/scripts/initial_boot_update.log. On the virtual machine we checked it applied 841 package changes and took about twenty minutes. Check on it with:

tail -n 3 /stage/scripts/initial_boot_update.log

It has finished when the log ends with:

=== dnf update exit code: 0 ===
=== Initial boot OS update finished ... ===

After a clean run the script removes itself and its crontab entry, so it never runs again.

New SSH connections can be refused while it runs. The update upgrades OpenSSH, and until the SSH service restarts at the end of the update, new connections fail with kex_exchange_identification: read: Connection reset by peer (on the virtual machine we checked, for about eleven minutes, from roughly five minutes after boot). A session that is already open stays connected. If you see that message on a new virtual machine, wait a few minutes and connect again. Do not run dnf yourself until the update has finished, because it holds the package lock.

Step 3: Check the Client

The profile hook puts the client on every user's PATH, so azureuser can check the release straight away:

sqlplus -V

Expected output:

SQL*Plus: Release 23.26.1.0.0 - Production
Version 23.26.1.0.0

The Oracle home, and its Oracle Net configuration, belong to the oracle user. Switch to it and load its environment:

sudo su - oracle
. ./setEnv.sh
env | grep -E "^(ORACLE_|TNS_)" | sort

Expected output:

ORACLE_BASE=/u01/app/oracle
ORACLE_HOME=/u01/app/oracle/product/23.0.0/client_1
TNS_ADMIN=/u01/app/oracle/product/23.0.0/client_1/network/admin

setEnv.sh also sets PATH, LD_LIBRARY_PATH and NLS_LANG=AMERICAN_AMERICA.AL32UTF8.

Step 4: Explore the Client Tooling

List the main command line tools in the Oracle home:

ls $ORACLE_HOME/bin | grep -xE "sqlplus|sqlldr|tnsping|expdp|impdp|exp|imp|rman|adrci|tkprof|mkstore|orapki"

Expected output:

adrci
exp
expdp
imp
impdp
mkstore
orapki
rman
sqlldr
sqlplus
tkprof
tnsping

Each tool reports the same release. For example, sqlldr with no arguments and expdp help=y both print their banner first:

SQL*Loader: Release 23.26.1.0.0 - Production on ...
Export: Release 23.26.1.0.0 - Production on ...

The Oracle home also carries SQLcl ($ORACLE_HOME/bin/sql), but SQLcl needs Java 11 or later and the image's Java is OpenJDK 8, so sql stops with Error: SQLcl requires Java 11 and above to run. Use SQL*Plus.

Step 5: Configure tnsnames.ora

Oracle Net reads its configuration from $TNS_ADMIN. The image's sqlnet.ora looks names up in tnsnames.ora first and then accepts Easy Connect strings:

cat $TNS_ADMIN/sqlnet.ora

Expected output:

NAMES.DIRECTORY_PATH = (TNSNAMES, EZCONNECT)
SQLNET.EXPIRE_TIME = 10
SQLNET.INBOUND_CONNECT_TIMEOUT = 60
SQLNET.OUTBOUND_CONNECT_TIMEOUT = 60

tnsnames.ora holds only a commented example. As the oracle user, add an entry for your database, replacing YOUR_DB_HOSTNAME, the port if it is not 1521, and YOUR_SERVICE_NAME:

cat >> "$TNS_ADMIN/tnsnames.ora" << 'TNS'
MY_DATABASE =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = YOUR_DB_HOSTNAME)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SERVER = DEDICATED)
      (SERVICE_NAME = YOUR_SERVICE_NAME)
    )
  )
TNS

Then test the alias with tnsping:

tnsping MY_DATABASE

What you can expect:

  • OK (n msec): the host resolves and the listener answered. This is what you want to see.
  • TNS-12545: Connect failed because target host or object does not exist: the host name does not resolve (this is what the unedited placeholder YOUR_DB_HOSTNAME gives). Oracle Net itself is working; fix the host.
  • TNS-12541 or TNS-00511: No listener: the host resolves but nothing listens on that port. Check the listener and the port.
  • TNS-03505: Failed to resolve name: the alias is not in tnsnames.ora. Check its spelling.

Step 6: Connect with SQL*Plus

Connect with the alias you added. SQL*Plus prompts for the password, so it never appears in your shell history:

sqlplus your_user@MY_DATABASE

Or skip tnsnames.ora and use an Easy Connect string, //host:port/service_name:

sqlplus your_user@//YOUR_DB_HOSTNAME:1521/YOUR_SERVICE_NAME

When the connection succeeds, SQL*Plus prints Connected to: and the database's banner, then the SQL> prompt. Type EXIT to leave.

If the host name in an Easy Connect string does not resolve, SQL*Plus says so plainly:

ERROR:
ORA-12262: Cannot connect to database. Could not resolve hostname
YOUR_DB_HOSTNAME in Easy Connect connection string //YOUR_DB_HOSTNAME:1521/YOUR_SERVICE_NAME

Data Pump, SQL*Loader and Recovery Manager take the same connect identifiers, for example expdp your_user@MY_DATABASE schemas=your_schema directory=DATA_PUMP_DIR dumpfile=your_schema.dmp. Data Pump writes its dump files on the database server, in the directory object you name.

Step 7: Drivers for Your Applications

Applications on the virtual machine can use the client's libraries directly:

ls $ORACLE_HOME/jdbc/lib/ojdbc*.jar
ls $ORACLE_HOME/lib/libclntsh.so* $ORACLE_HOME/lib/libocci.so
  • JDBC: ojdbc8.jar (Java 8), ojdbc11.jar (Java 11 and later) and ojdbc17.jar (Java 17 and later)
  • OCI and OCCI: libclntsh.so and libocci.so, with libclntsh.so.23.1 and compatibility links for earlier client releases; LD_LIBRARY_PATH from setEnv.sh points at them

The Oracle home's jdk directory is a link to the system OpenJDK 8, which dnf keeps up to date:

readlink -f $ORACLE_HOME/jdk
$ORACLE_HOME/jdk/bin/java -version

Step 8: Networking and Security

Nothing on the virtual machine listens for database traffic. Check the listeners and the host firewall:

sudo ss -tlnp
sudo firewall-cmd --list-all

sshd listens on port 22 and rpcbind on port 111. firewalld is active, and its public zone allows only the ssh, cockpit and dhcpv6-client services, so port 111 is not reachable from the network. The Cockpit packages are installed but the cockpit.socket unit is not active, so nothing answers on its port.

SSH accepts keys only:

sudo sshd -T | grep -E "^(pubkeyauthentication|passwordauthentication) "
pubkeyauthentication yes
passwordauthentication no

SELinux runs in permissive mode (getenforce prints Permissive).

To keep credentials out of scripts, store them in an Oracle Wallet with mkstore and orapki, both in $ORACLE_HOME/bin.

Server Components

Component Version Notes
Oracle Database Client 23ai (Administrator install) 23.26.1.0.0 /u01/app/oracle/product/23.0.0/client_1
Oracle Linux 9 Built on 9.7; the first boot update moved our test VM to 9.8
OpenJDK 1.8.0 System package, linked as $ORACLE_HOME/jdk
Azure Linux Agent waagent, active
chrony chronyd, active
firewalld Active, public zone

Filesystem Layout

The OS disk uses LVM. The Oracle home sits on the root filesystem under /u01; there is no separate /u01 mount.

Mount Point Size Description
/ 32 GB Root filesystem (XFS on LVM, rootvg-rootlv), holds /u01
/var/crash 15 GB Kernel crash dumps (XFS on LVM, rootvg-crashlv)
/boot 2 GB Kernel files (XFS)
/boot/efi 100 MB UEFI system partition

The Azure temporary disk is not mounted on this image (see Troubleshooting).

Key Oracle directories:

Directory Purpose
/u01/app/oracle Oracle base
/u01/app/oracle/product/23.0.0/client_1 Oracle home
/u01/app/oracle/product/23.0.0/client_1/bin Client tools
/u01/app/oracle/product/23.0.0/client_1/lib OCI and OCCI libraries
/u01/app/oracle/product/23.0.0/client_1/jdbc/lib JDBC drivers
/u01/app/oracle/product/23.0.0/client_1/network/admin tnsnames.ora and sqlnet.ora

Scripts and Log Files

Path Purpose
/home/oracle/setEnv.sh Sets the Oracle environment for the oracle user
/etc/profile.d/oracle-client.sh Puts the client on every login user's PATH
/stage/scripts/initial_boot_update.sh The one time first boot update; it deletes itself after a clean run
/stage/scripts/initial_boot_update.log Output of the first boot update
/var/log/waagent.log Azure Linux Agent log

On Startup

The Oracle Client is a set of tools and libraries, not a service, so there is nothing to start or stop. On the first boot only, root's crontab runs /stage/scripts/initial_boot_update.sh (dnf update -y), as described in Step 2. After a clean run it removes itself and its crontab entry. To stop it before it runs, remove the @reboot /stage/scripts/initial_boot_update.sh line with sudo crontab -e.

Troubleshooting

SSH says "Connection reset by peer" on a new virtual machine

The first boot update is upgrading OpenSSH (Step 2). Wait a few minutes and connect again; the update logs its progress to /stage/scripts/initial_boot_update.log.

sqlplus: command not found

  1. Log in again, or run . /etc/profile.d/oracle-client.sh, so the profile hook sets your PATH
  2. As the oracle user, load the environment: . ~/setEnv.sh
  3. Check the binary exists: ls /u01/app/oracle/product/23.0.0/client_1/bin/sqlplus

ORA-12154 or TNS-03505: the name cannot be resolved

  1. Check the alias is in tnsnames.ora: cat $TNS_ADMIN/tnsnames.ora
  2. Check TNS_ADMIN points at the network/admin directory: echo $TNS_ADMIN
  3. Test the alias: tnsping MY_DATABASE

ORA-12170: Connect timeout

  1. Check the database host and port are reachable from the virtual machine
  2. Check the database side's network security group allows inbound from this virtual machine
  3. Check route tables, peering or VPN between this virtual machine and the database network

ORA-12541 or TNS-00511: No listener

The host resolves but nothing listens on that port. Check the database listener and the port in your entry.

systemctl reports "degraded"

systemctl --failed lists mnt.mount and temp-disk-dataloss-warning.service. On this image cloud-init is masked (the Azure Linux Agent provisions the virtual machine), so the Azure temporary disk is never formatted for Linux and its /mnt entry fails to mount. Nothing on the image uses the temporary disk. For the same reason sudo cloud-init status prints status: not started; do not run cloud-init status --wait, which waits forever.

The clock shows US Eastern time

The image's time zone is America/New_York. To use UTC, run sudo timedatectl set-timezone UTC.

Security Recommendations

  • Restrict outbound access: allow outbound TCP 1521 (or your listener port) only to the databases this virtual machine needs to reach
  • Keep SSH narrow: allow port 22 only from your own network
  • Protect tnsnames.ora: it names your internal database hosts and services
  • Use a wallet for credentials: keep passwords out of scripts and shell history with Oracle Wallet (mkstore, orapki)
  • Encrypt connections: configure Oracle Net encryption or TLS to your databases
  • Keep the system updated: apply operating system updates with sudo dnf update after the first boot update has finished

Support

If you encounter any issues with this product, contact cloudimg support:

Oracle is a registered trademark of Oracle Corporation. This image is a repackaged upstream distribution provided by cloudimg.