Timezone

Three components need to agree on the timezone: the OS, PHP, MariaDB. When any of them drift out of sync, "X ago" calculations on the audit log, pair transcript, and Fleet dashboard show the wrong wall-clock offset, and short-TTL rows (2FA codes, password-reset links) can expire early or late by the offset delta.

Before install

Set the OS timezone FIRST, before running install.sh.

timedatectl set-timezone America/Chicago   # or whatever fits the operator

install.sh reads whatever timedatectl show -p Timezone --value returns and pins PHP + MariaDB to the same TZ. If you install with the OS still set to UTC and then change the OS timezone later, PHP and MariaDB will keep pointing at UTC and drift by the offset.

What install.sh writes

Two config files, both readable + editable by root:

File Content Effect
/etc/php.d/99-customsso-timezone.ini date.timezone = <system TZ> PHP scripts (all controllers, sso_entry.php) use this TZ for date(), strtotime(), time() string conversions
/etc/my.cnf.d/customsso.cnf default-time-zone = "SYSTEM" MariaDB NOW(), CURRENT_TIMESTAMP, and TIMESTAMP column reads all follow the OS TZ

Because MariaDB is set to SYSTEM, it automatically tracks any future timedatectl set-timezone change for MariaDB. But PHP-FPM caches its date.timezone at startup — it will NOT pick up an OS TZ change automatically. So retunes need a manual step.

Changing timezone after install

If the operator moves the box or wants a different display TZ:

# 1. Change the OS timezone
sudo timedatectl set-timezone America/New_York

# 2. Update the PHP config to match — swap the value on the date.timezone line
sudo sed -i 's|^date.timezone = .*|date.timezone = America/New_York|' \
    /etc/php.d/99-customsso-timezone.ini

# 3. Reload PHP-FPM so workers pick up the new value
sudo systemctl reload php-fpm

# 4. Restart MariaDB so it re-reads the current OS TZ (default-time-zone=SYSTEM
#    reads once at startup, then caches)
sudo systemctl restart mariadb

Verify all three agree:

date                                                    # OS wall clock
mysql -e "SELECT NOW() AS mysql_now, @@global.time_zone;"
php -r 'echo date("Y-m-d H:i:s T") . "\n";'

All three should print the same time to the second. If they diverge, one of the three steps didn't take.

Diagnosis

Symptom: "X ago" values are consistently off by a whole number of hours matching your offset from UTC.

Quick check:

diff <(mysql -N -e 'SELECT NOW()') <(php -r 'echo date("Y-m-d H:i:s") . "\n";')

Symptom: 2FA codes reject with bad_code even when typed correctly within the 10-minute window.

Root cause: MariaDB and PHP disagree on TZ, so the row's expires_at (written in one TZ, read in the other) either has already elapsed or hasn't started. Fix is the retune ritual above.

Existing timestamp rows after a TZ change

Rows already in the DB were written with whatever TZ was active at write time. Changing TZ does NOT rewrite them. This is usually fine — audit rows are historical and their "X ago" delta just shifts by the retune offset (a one-time cosmetic blip on old rows). Short-TTL rows (pending_2fa, password_resets) purge on their own within minutes to hours, so wait out the retention window and the historical rows disappear.

If you must correct a specific row, do it in MariaDB with an explicit offset:

UPDATE audit SET ts = ts + INTERVAL 5 HOUR WHERE ts BETWEEN '2026-07-01' AND '2026-07-07';

Test on a copy first.