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:
- Ensure the relevant patch is applied.
- Enable _enable_ptime_update_for_sys = TRUE in the SPFILE and bounce the database.
- 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