Most Oracle audit trails are useless, and they fail in one of two ways. Either nobody ever enabled
anything, so when a breach investigation asks “who altered that account,” the answer is a shrug. Or
someone once turned on everything — every statement, every user, every object — and the trail became a
firehose that filled SYSAUX, slowed the database, and got quietly ignored until it was disabled again.
Neither of those is auditing. One is blindness; the other is noise you can’t read.
The skill isn’t auditing more. It’s auditing the few things that actually matter — privilege
abuse, credential attacks, changes to your security configuration — in a trail you can trust and actually
read. Oracle’s Unified Auditing makes that easy, and there’s a deadline attached: as of Oracle
Database 23ai, traditional auditing is desupported. If your process still leans on AUDIT ... writing
to AUD$, it has an expiry date. Here’s how to do auditing right, and what changes underneath you as you
move to 23ai.
The short version. Unified Auditing (the model since 12c) writes every audit record to a single protected, read-only internal table owned by
AUDSYS, and you turn it on through policies, not statement by statement. Keep the baseline Oracle pre-enables —ORA_SECURECONFIG(privileged and structural changes) andORA_LOGON_FAILURES— then add a few narrow policies of your own, not a firehose. 19c ships in mixed mode (traditionalAUD$and unified both active); move to pure unified auditing by relinking the binary (uniaud_on) and restarting. In 23ai, traditional auditing is gone — unified is all there is. Read the trail inUNIFIED_AUDIT_TRAIL; keep it from growing forever withDBMS_AUDIT_MGMT.
What Unified Auditing actually changed
Before 12c, auditing was a scatter of destinations — AUD$ in the database, FGA_LOG$ for fine-grained
policies, OS files, XML files, the SYS operations audit — each configured differently and each with its
own gaps. Unified Auditing collapses all of it into one trail and one way to configure it. Three
properties make it worth adopting on purpose:
- A single, protected trail. Every record lands in a read-only internal table in the
AUDSYSschema. You cannotINSERT,UPDATE, orDELETEit with SQL — not even asSYS. An attacker who compromises a privileged account can’t quietly scrub their tracks the way they could when audit rows lived in a normal table. You read it through theUNIFIED_AUDIT_TRAILview. - Policy-based, not statement-by-statement. You define audit policies — named bundles of actions,
privileges, or roles to watch — and enable them, optionally scoped to specific users. It’s declarative
and far easier to review than a pile of individual
AUDITstatements. - Better performance, by default. Unified records are written in queued (asynchronous) mode: they buffer in the SGA and flush to disk in batches, rather than forcing a write on every audited action. (That buffering has one practical consequence for the impatient — see “reading the trail” below.)
The baseline is already on — don’t turn it off
Here’s the part that surprises people: a modern Oracle database is already auditing the right security-critical actions, because Oracle pre-enables a sensible baseline. Two policies do most of the work:
ORA_SECURECONFIG— audits the actions that change your security posture:CREATE/ALTER/DROP USER,GRANT,REVOKE,ALTER SYSTEM,CREATE/DROPof directories and database links, and more. This is enabled by default from 12.2 onward.ORA_LOGON_FAILURES— audits failed logons, which is exactly the fingerprint of a credential attack or a misconfigured application hammering the listener.
Check what’s actually enabled right now — the first query every audit review should start with:
SELECT policy_name, enabled_option, entity_name
FROM audit_unified_enabled_policies
ORDER BY policy_name;
If ORA_SECURECONFIG isn’t in that list, someone disabled it, and that is your finding — a database
that isn’t recording who changes its own security is a database you can’t investigate. The baseline is
the floor, not the ceiling, but it is a floor most estates already have for free. Don’t rip it out in the
name of “reducing overhead”; the overhead is negligible and the coverage is exactly the high-value set.
Audit the few things that are specific to you
On top of the baseline, add narrow policies for the things that matter in your database — a sensitive table, a powerful privilege, a specific service account. This is where discipline pays: every policy you add is a policy someone has to read, so add few and make them sharp.
-- Watch reads of one sensitive table (and who did them)
CREATE AUDIT POLICY aud_salary_access
ACTIONS SELECT, UPDATE, DELETE ON hr.employee_salary;
AUDIT POLICY aud_salary_access;
-- Watch any use of a skeleton-key system privilege
CREATE AUDIT POLICY aud_any_table
PRIVILEGES SELECT ANY TABLE, DELETE ANY TABLE;
AUDIT POLICY aud_any_table;
Policies can be scoped and conditioned so they capture signal, not volume: AUDIT POLICY aud_x BY app_user limits it to one account, ... WHENEVER NOT SUCCESSFUL records only the failed attempts
(often the interesting ones), and an audit_condition on a policy lets you fire only when a column or
context matches. The instinct to “audit all DML on everything” is the firehose again — resist it. A
handful of targeted policies plus the baseline is a trail a human will actually read during an incident.
Mixed mode, pure mode, and the 23ai cliff
Whether unified auditing is the only audit system running depends on your release, and this trips people up during upgrades.
- 12c–19c ship in mixed mode. Both the old traditional auditing (
AUDIT→AUD$) and unified auditing are active. Mixed mode exists so you can adopt unified auditing gradually without breaking legacy scripts. It also means your audit data is split across two systems — the thing unified auditing was supposed to end. - Pure unified auditing turns traditional auditing off entirely. Moving to it isn’t a
SET— you relink the Oracle binary with unified auditing on and restart the instance:
-- database down, then from $ORACLE_HOME/rdbms/lib:
make -f ins_rdbms.mk uniaud_on ioracle ORACLE_HOME=$ORACLE_HOME
-- restart; it's reversible with uniaud_off if you must go back
- 23ai removes the choice. As of Oracle Database 23ai, traditional auditing is desupported. A
freshly created 23ai database has no traditional audit at all — unified auditing is the audit system.
On an upgrade, any traditional audit settings you were still using are carried forward for
compatibility, but the direction is one-way: new work goes to unified, and the old model is on its way
out. The practical takeaway — if your compliance runbook still says “
AUDITthis, checkAUD$,” it needs rewriting before you reach 23ai, not after.
flowchart TD
A["Auditing review"] --> B{"ORA_SECURECONFIG +<br/>ORA_LOGON_FAILURES enabled?"}
B -- No --> B1["Re-enable them<br/>(that's your finding)"]
B -- Yes --> C{"Anything specific to<br/>audit? sensitive table,<br/>ANY privilege, an account"}
C -- Yes --> C1["CREATE AUDIT POLICY,<br/>narrow + conditioned"]
C -- No --> D
C1 --> D{"Which release?"}
D -- "12c–19c" --> D1["Mixed mode: plan the<br/>relink to pure unified"]
D -- "23ai" --> D2["Traditional desupported —<br/>unified is all there is"]
D1 --> E["Read UNIFIED_AUDIT_TRAIL;<br/>purge with DBMS_AUDIT_MGMT"]
D2 --> E Prove your policies actually capture what you think. The unified-auditing lab stands up an Oracle Database Free container, enables the baseline plus a custom policy on a sensitive table, then triggers the real events — a failed login, a
GRANT, a read of the protected table — flushes the audit buffer, and queriesUNIFIED_AUDIT_TRAILto assert every one was recorded. If a policy silently isn’t capturing, the run fails. It’s the difference between “we have auditing” and “we checked.”
Reading and keeping the trail
A trail you never read is theater. Reading unified auditing is one view — UNIFIED_AUDIT_TRAIL — filtered
by what you care about:
-- failed logons in the last day (credential-attack fingerprint)
SELECT event_timestamp, dbusername, userhost, return_code
FROM unified_audit_trail
WHERE action_name = 'LOGON' AND return_code <> 0
AND event_timestamp > SYSTIMESTAMP - INTERVAL '1' DAY
ORDER BY event_timestamp;
Two operational details keep this working. First, because records are written in queued mode, the
newest ones may still be in the SGA buffer, not yet in the view — force them out with
DBMS_AUDIT_MGMT.FLUSH_UNIFIED_AUDIT_TRAIL before you trust “nothing’s there” (the lab does exactly this
before it checks). Second, the trail grows forever unless you manage it: set a retention window and a
scheduled purge with DBMS_AUDIT_MGMT so the audit table doesn’t quietly consume your tablespace.
-- keep 180 days, then schedule automatic cleanup
BEGIN
DBMS_AUDIT_MGMT.SET_LAST_ARCHIVE_TIMESTAMP(
audit_trail_type => DBMS_AUDIT_MGMT.AUDIT_TRAIL_UNIFIED,
last_archive_time => SYSTIMESTAMP - INTERVAL '180' DAY);
DBMS_AUDIT_MGMT.CREATE_PURGE_JOB(
audit_trail_type => DBMS_AUDIT_MGMT.AUDIT_TRAIL_UNIFIED,
audit_trail_purge_interval => 24,
audit_trail_purge_name => 'unified_purge',
use_last_arch_timestamp => TRUE);
END;
/
What teams get wrong
- Auditing everything. “Audit all actions on all objects” is the firehose that gets muted and then disabled. Enable the baseline, add a few sharp policies, and review them. Coverage you never read is worse than none — it’s cost with no benefit.
- Turning off the baseline “for performance.”
ORA_SECURECONFIGis cheap and its coverage is exactly the security-critical actions. Disabling it is how a database loses the ability to explain who changed its own configuration. - Assuming traditional auditing still works forever. It’s desupported in 23ai. A runbook built on
AUDITstatements andAUD$queries breaks on the upgrade you haven’t scheduled yet. - Living in mixed mode by accident. On 19c, “we use unified auditing” is often only half true — the old system is still running alongside it, splitting your trail. Decide to go pure, and relink.
- Forgetting to purge. The unified trail grows without bound. No retention policy means an audit table
that eventually pressures
SYSAUX/its tablespace — set aDBMS_AUDIT_MGMTpurge job on day one. - Not flushing before you conclude “nothing happened.” Queued-mode records lag. Flush the buffer before you trust an empty result during an investigation.
The one-paragraph version
Auditing isn’t about volume, it’s about signal. Use Unified Auditing — one protected, read-only trail
in AUDSYS, configured by policies. Keep the baseline Oracle already enabled (ORA_SECURECONFIG +
ORA_LOGON_FAILURES) and add a few narrow policies for your sensitive tables and powerful
privileges — never a firehose. Know your mode: 19c runs mixed (traditional and unified together), you
move to pure by relinking uniaud_on and restarting, and 23ai desupports traditional auditing
outright, so rewrite any AUD$-based runbook before you get there. Read it through
UNIFIED_AUDIT_TRAIL (flush the buffer first), and keep it bounded with a DBMS_AUDIT_MGMT purge
job. A handful of policies you actually read beats a trail of everything you don’t.
Frequently asked questions
What is the difference between traditional and Unified Auditing in Oracle?
Traditional auditing (the AUDIT command writing to SYS.AUD$, plus separate destinations for fine-grained auditing, OS files, and SYS operations) is configured statement by statement and scattered across multiple trails. Unified Auditing, the model since Oracle 12c, consolidates everything into a single protected, read-only internal table in the AUDSYS schema and is configured through named policies you enable or disable as a unit. Unified Auditing is easier to manage, harder to tamper with, and higher performance because records are written asynchronously. As of Oracle Database 23ai, traditional auditing is desupported.
Is Unified Auditing enabled by default in Oracle?
Partly. Unified Auditing as a system is available in every 12c and later database, and Oracle pre-enables a baseline of predefined policies — most importantly ORA_SECURECONFIG (security-relevant actions such as CREATE/ALTER USER, GRANT, and ALTER SYSTEM) from 12.2 onward, and ORA_LOGON_FAILURES for failed logons. However, in 12c through 19c the database runs in mixed mode, meaning traditional auditing is still active alongside unified auditing. Running pure unified auditing (unified only) requires relinking the Oracle binary and restarting.
How do I enable pure Unified Auditing?
Pure Unified Auditing is enabled by relinking the Oracle executable with unified auditing turned on, not by a parameter. Shut the database down, then from $ORACLE_HOME/rdbms/lib run: make -f ins_rdbms.mk uniaud_on ioracle ORACLE_HOME=$ORACLE_HOME, and restart the instance. After that, traditional auditing is disabled and only unified auditing records are produced. The change is reversible by relinking with uniaud_off. In Oracle Database 23ai this is moot because traditional auditing is desupported.
Where are Unified Audit records stored and can they be modified?
Unified audit records are stored in a read-only internal table owned by the AUDSYS schema, and you read them through the UNIFIED_AUDIT_TRAIL view. They cannot be changed with ordinary SQL — INSERT, UPDATE, and DELETE against the trail are not permitted, even for SYS. Records are managed only through the DBMS_AUDIT_MGMT package, which is used to archive and purge old records. This protection is a core reason to prefer unified auditing: a compromised privileged account cannot silently erase its own audit trail.
Why do my new audit records not appear in UNIFIED_AUDIT_TRAIL immediately?
Because unified audit records are written in queued (asynchronous) mode by default: they buffer in the SGA and are flushed to the audit table in batches for performance, so the most recent actions can lag before they appear in the view. To force them out immediately — for example during an investigation before concluding that nothing was recorded — run DBMS_AUDIT_MGMT.FLUSH_UNIFIED_AUDIT_TRAIL, then re-query. This buffering is normal and is the tradeoff that keeps auditing cheap.
What audit policies should I actually enable?
Keep the baseline Oracle already enables — ORA_SECURECONFIG for security-configuration changes and ORA_LOGON_FAILURES for failed logons — and add a small number of narrow policies for what matters in your database: access to a specific sensitive table, use of powerful ANY privileges, or activity by a particular service account. Scope and condition them (BY user, WHENEVER NOT SUCCESSFUL, or an audit_condition) so they capture signal rather than volume. Auditing everything produces a trail nobody reads; a handful of sharp policies plus the baseline is what actually helps in an incident.
How do I stop the unified audit trail from growing forever?
Use the DBMS_AUDIT_MGMT package to set a retention window and a scheduled purge for the unified audit trail. Set a last-archive timestamp with DBMS_AUDIT_MGMT.SET_LAST_ARCHIVE_TIMESTAMP and create a recurring cleanup with DBMS_AUDIT_MGMT.CREATE_PURGE_JOB for audit_trail_type AUDIT_TRAIL_UNIFIED. Without a purge policy the audit table grows without bound and eventually pressures its tablespace. Decide a retention period that satisfies your compliance requirement and automate the cleanup from the start.
Is traditional auditing really gone in Oracle 23ai?
Traditional auditing is desupported in Oracle Database 23ai. A newly created 23ai database has no traditional auditing; unified auditing is the audit system. If you upgrade a database that still had traditional audit settings, those settings are carried forward for compatibility, but new auditing work uses unified auditing and the traditional model should not be relied on going forward. Any process, script, or compliance runbook built on the AUDIT command and the AUD$ table should be migrated to unified auditing before adopting 23ai.
Auditing is the third leg of the security-and-ops discipline, alongside patching and hardening — patching closes known vulnerabilities, hardening closes the configuration gaps, and auditing is how you know when either one is being tested. Do it the same way you do the other two: not exhaustively, but deliberately — the few high-value policies, enabled, purged, and actually read. Prove yours capture what you think with the unified-auditing lab, and put the review on the same cadence as your quarterly patch.
Have a question or some feedback?
I write here in a personal capacity and enjoy comparing notes with other Oracle folks. Say hello.
Get in touch