Making SYS Password Expiration Work Properly in Oracle – And Why You Should Actually Rotate It

Making SYS Password Expiration Work Properly in Oracle – And Why You Should Actually Rotate It

Making SYS Password Expiration Work Properly in Oracle And Why You Should Actually Rotate It

Titleimage

Posted by Gonzalo Fernandez on 2026:02:25 23:19:10

Introduction

Recently, we scheduled a report in one of our Oracle environments to list all accounts whose passwords were about to expire. After the first run, we did what everyone should be doing: we started updating the administrative passwords, including SYS in the database, in OEM, and in the Oracle Wallets.

Then something odd appeared.

Making SYS Password Expiration Work Properly in Oracle And Why You Should Actually Rotate It

For all users except SYS, the password expiration date moved forward as expected. For SYS, the PASSWORD_EXPIRE value stayed stuck on the old date, even though we had clearly changed the password.

After some digging, we realized this wasn’t a new bug at all; it’s been around for years and is quietly present in many environments. It simply hides in plain sight because SYS passwords often never change (even though they absolutely should, given the privileges involved).

This post covers:

  • Why rotating the SYS password matters (especially in Multitenant environments)
  • Best practices for handling SYS credentials securely
  • How to fix the issue where changing SYS’s password does not update its expiration date
  • Practical recommendations you can apply across your environments

1. Why SYS Password Rotation Is Often Ignored (and Why That’s Dangerous)

In pre-Multitenant days (non-CDB), the standard practice for local administration was something like:

sqlplus / as sysdba

Operating system authentication made it easy: no passwords in scripts, no application connections as SYS, and no immediate pressure to rotate the SYS password.

With the introduction of Oracle Multitenant, this model changed:

  • SYS connections are often made directly to individual PDBs.
  • OS authentication doesn’t always apply in the same way at the PDB level.
  • OP$ prefix was deprecated, which allowed OS only authentications.
  • More scripts, tools, and monitoring components end up needing real SYS credentials, not just "/ as sysdba".

The result?
SYS starts to behave more like a “normal” administrative account whose password is stored in places (e.g., wallets, OEM, automation scripts) and that makes it a real security risk if the password never changes.

Given that SYS controls everything, a compromised password is essentially game over for that database.

2. Best Practices for Managing SYS Credentials

Before jumping into the bug and its fix, it helps to frame some baseline good practices around SYS.

2.1 Use Oracle Wallets Instead of Hard-Coding Passwords

If scripts or automation jobs need SYS (or any powerful) credentials:

  • Never hard-code passwords in shell scripts or config files.
  • Use Oracle Wallet (e.g., orapki) to store credentials securely.
  • Configure scripts to connect using wallet aliases instead of clear-text passwords.

This reduces the blast radius if someone gains access to the file system, and makes password rotation easier: change the password, update the wallet, and scripts continue to work.

2.2 Monitor Password Expiration Proactively

It’s a good practice to have:

  • A scheduled report or
  • An alerting query

that lists accounts, especially administrative ones like SYS, SYSTEM, and application owners whose passwords are nearing expiration.

This avoids:

  • Surprise outages from expired passwords
  • Broken automation, monitoring, backups, or Data Guard processes
  • Last-minute emergency changes under pressure

Your SYS account should be included in the same rotation cycle as other administrative users, not treated as a special “never expires” exception.

2.3 Data Guard: Use a Dedicated REDO_TRANSPORT_USER

In Data Guard environments, frequent password rotations can impact redo transport if you’re relying on SYS for shipping archives/logs between primary and standby.

To decouple SYS from redo transport:

  • Create a dedicated user, e.g. REDO_TRANSPORT_USER.
  • Grant only the privileges required for redo transport.
  • Configure Data Guard to use that account instead of SYS.

Benefits:

  • You can rotate SYS independently without breaking redo shipping.
  • You narrow the impact radius of credential changes.
  • It’s easier to align with security policies and audits.

3. The Problem: SYS Password Expiration Date Not Updating

Even with good password policies in place (e.g., a profile with a non- PASSWORD_LIFE_TIME), you might see this behaviour:

  • You assign SYS a profile with expiration rules.
  • The password expiration date is set correctly at first.
  • When you later change the SYS password, the PASSWORD_EXPIRE column in CDB_USERS does not update — it remains on the old date.

This is caused by an internal bug related to how Oracle updates metadata for SYS in USER$. The behaviour is controlled by an internal (underscore) parameter and a specific patch.

Let’s walk through how to identify and fix it.

4. Step-by-Step: Fixing SYS Password Expiration Not Updating

Applies to: Oracle 12.1, 12.2, 18c, 19c (the bug was first fixed in patch 28538439 for 12.1.0.2.180717). 

RENAPS-How to address the issue…

Step 1 – Check SYS’s Password Change and Expiration Dates

First, confirm the current status of SYS in the root container (CDB$ROOT):

set lines 220

col CONTAINER_NAME for a12

col PROFILE for a22

SELECT u.con_id,

       c.name AS container_name,

       u.username,

       u.common,

       u.account_status,

       u.profile,

       TO_CHAR(u.password_change_date, 'DD-MON-RRRR HH24:MI:SS') AS password_change_date,

       TO_CHAR(u.expiry_date,           'DD-MON-RRRR HH24:MI:SS') AS password_expire

FROM   cdb_users     u

JOIN   v$containers  c

       ON u.con_id = c.con_id

WHERE  u.con_id   = 1         -- CDB$ROOT only; remove this to see all containers

AND    u.username = 'SYS';

Example output when no profile-based expiration is applied: 

CON_ID CONTAINER_NA USERNAME COM ACCOUNT_STATUS PROFILE PASSWORD_CHANGE_DATE PASSWORD_EXPIRE

------- ------------ -------- --- -------------- ------- -------------------- --------------

      1 CDB$ROOT     SYS      YES OPEN           DEFAULT 27-FEB-2023 13:33:08

Here, SYS is using the DEFAULT profile, which may not enforce PASSWORD_LIFE_TIME, so no expiration date is shown yet.

Step 2 – Assign a Profile That Enforces Password Expiration

Assign SYS a profile specifically designed for it, with appropriate password lifetime and security rules:

ALTER USER sys PROFILE C##PROFILE_SYS;

Check again:

SELECT u.con_id,

       c.name AS container_name,

       u.username,

       u.common,

       u.account_status,

       u.profile,

       TO_CHAR(u.password_change_date, 'DD-MON-RRRR HH24:MI:SS') AS password_change_date,

       TO_CHAR(u.expiry_date,           'DD-MON-RRRR HH24:MI:SS') AS password_expire

FROM   cdb_users     u

JOIN   v$containers  c

       ON u.con_id = c.con_id

WHERE  u.con_id   = 1         -- CDB$ROOT only; remove this to see all containers

AND    u.username = 'SYS';

Now you should see something like:

 CON_ID CONTAINER_NA USERNAME COM ACCOUNT_STATUS PROFILE        PASSWORD_CHANGE_DATE PASSWORD_EXPIRE

------- ------------ -------- --- -------------- -------------- -------------------- --------------------

      1 CDB$ROOT     SYS      YES OPEN           C##PROFILE_SYS 27-FEB-2023 13:33:08 26-AUG-2023 13:33:08

So far, so good - the profile is applied, and the expiration date is calculated.  26-AUG-2023 13:33:08

Step 3 – Change the SYS Password and Observe the Bug

Now change the SYS password:

ALTER USER sys IDENTIFIED BY ExpirationDateNOTUpdated;

Query again:

SELECT u.con_id,

       c.name AS container_name,

       u.username,

       u.common,

       u.account_status,

       u.profile,

       TO_CHAR(u.password_change_date, 'DD-MON-RRRR HH24:MI:SS') AS password_change_date,

       TO_CHAR(u.expiry_date,           'DD-MON-RRRR HH24:MI:SS') AS password_expire

FROM   cdb_users     u

JOIN   v$containers  c

       ON u.con_id = c.con_id

WHERE  u.con_id   = 1         -- CDB$ROOT only; remove this to see all containers

AND    u.username = 'SYS';

If you’re affected by the bug, you’ll see:

 CON_ID CONTAINER_NA USERNAME COM ACCOUNT_STATUS PROFILE        PASSWORD_CHANGE_DATE PASSWORD_EXPIRE

------- ------------ -------- --- -------------- -------------- -------------------- --------------------

      1 CDB$ROOT     SYS      YES OPEN           C##PROFILE_SYS 27-FEB-2023 13:33:08 26-AUG-2023 13:33:08
  • PASSWORD_CHANGE_DATE and PASSWORD_EXPIRE did not move forward as they should have.  It is still showing 26-AUG-2023 13:33:08

That’s the core issue.

Step 4 – Confirm Patch and Internal Parameter

First, verify that the patch for bug 28538439 is present:

$ opatch lsinventory | grep 28538439

If the patch is not there, you’ll need to work with your Oracle support / patching process to apply the patch to  the affected home.

Next, check the internal parameter controlling updates to SYS’s password metadata:

col VALUE for a22

SELECT x.ksppinm  AS name,

       y.ksppstvl AS value

FROM   sys.x$ksppi x,

       sys.x$ksppcv y

WHERE  x.indx = y.indx

AND    x.ksppinm LIKE '%_enable_ptime%';

Example output before the fix:

NAME                            VALUE

------------------------------- --------------------------------

_enable_ptime_update_for_sys    FALSE

If it’s FALSE, the database is not updating the internal PTIME for SYS when the password changes.

Step 5 – Enable Updates for SYS Password Metadata

Warning: This is an underscore (internal) parameter. Always follow your change management process, test in non-production first, and align with Oracle Support and your patching standards.

Enable the parameter in the SPFILE and restart the instance:

ALTER SYSTEM SET "_enable_ptime_update_for_sys" = TRUE SCOPE=SPFILE;

Bounce the database, then confirm the value:

col VALUE for a22

SELECT x.ksppinm  AS name,

       y.ksppstvl AS value

FROM   sys.x$ksppi x,

       sys.x$ksppcv y

WHERE  x.indx = y.indx

AND    x.ksppinm LIKE '%_enable_ptime%';

You should now see:

NAME                            VALUE

------------------------------- ----------------------

_enable_ptime_update_for_sys    TRUE

Step 6 – Change SYS Password Again and Validate

Now, change the SYS password once more:

ALTER USER sys IDENTIFIED BY ExpirationDateUpdated;

Re-run the verification query:

SELECT u.con_id,

       c.name AS container_name,

       u.username,

       u.common,

       u.account_status,

       u.profile,

       TO_CHAR(u.password_change_date, 'DD-MON-RRRR HH24:MI:SS') AS password_change_date,

       TO_CHAR(u.expiry_date,           'DD-MON-RRRR HH24:MI:SS') AS password_expire

FROM   cdb_users     u

JOIN   v$containers  c

       ON u.con_id = c.con_id

WHERE  u.con_id   = 1         -- CDB$ROOT only; remove this to see all containers

AND    u.username = 'SYS';

Expected output after the fix: 

CON_ID CONTAINER_NA USERNAME COM ACCOUNT_STATUS PROFILE        PASSWORD_CHANGE_DATE PASSWORD_EXPIRE

------- ------------ -------- --- -------------- -------------- -------------------- --------------------

      1 CDB$ROOT     SYS      YES OPEN           C##PROFILE_SYS 17-DEC-2025 00:25:28 15-JUN-2026 00:25:28

Now both PASSWORD_CHANGE_DATE (17-DEC-2025 00:25:28 )and PASSWORD_EXPIRE (15-JUN-2026 00:25:28)  correctly reflect the last password change and the profile’s expiration policy.

5. Impact and Versions

  • Bug 28538439 – “PTIME FOR SYS NOT GETTING UPDATED AFTER APPLYING JULY PSU (Patch)” affects:
  • 12.1
  • 12.2
  • 18c
  • 19c
  • The initial fix for this bug was released in patch 12.1.0.2.180717, with equivalent fixes/backports available for later releases.

If you’re running any of these versions and you see SYS’s expiration date not moving when you change the password, you are very likely hitting this issue and should:

  1. Ensure the relevant patch is applied.
  2. Enable _enable_ptime_update_for_sys = TRUE in the SPFILE and bounce the database.
  3. Test password changes again.

6. Practical Checklist and Recommendations

To wrap up, here’s a practical checklist you can use in your environment:

6.1 Security and Governance

  • Include SYS in your standard password rotation policy.
  • Store SYS credentials in Oracle Wallets, not in scripts.
  • Maintain regular reports/alerts on accounts nearing expiration, including SYS, SYSTEM, and key application owners.
  • Document each password change for auditability (who, when, where, why).

6.2 Data Guard and High Availability

  • Use a dedicated REDO_TRANSPORT_USER instead of SYS for redo transport.
  • Test SYS password changes in Data Guard environments to ensure no impact on log shipping.

6.3 Bug Fix and Technical Configuration

  • Check whether patch 28538439 (or its equivalent) is applied.
  • Verify _enable_ptime_update_for_sys — set it to TRUE (SPFILE) and restart according to your change process.
  • Confirm that PASSWORD_CHANGE_DATE and PASSWORD_EXPIRE update correctly for SYS after the next rotation.

7. Final Thoughts

The goal of this post is not just to fix a quirky Oracle bug, but to encourage you to treat SYS like any other high-privilege account:

  • Rotate its password on a defined schedule.
  • Protect its credentials using wallets and good operational hygiene.
  • Monitor and report on its password expiration, rather than assuming “SYS is special.”

With the patch and parameter correctly set, Oracle will finally keep the metadata in sync, and you’ll have a reliable picture of when SYS credentials actually change — an important step toward strong, auditable security in your databases.

Posted by Gonzalo Fernandez on 2026:02:25 23:19:10

Return to Blog