From d755b77126682e9075a4ce52361d1939d7941800 Mon Sep 17 00:00:00 2001 From: MajorLinux Date: Fri, 5 Jun 2026 14:22:00 -0400 Subject: [PATCH] troubleshooting: SELinux /etc/localtime mislabel silently breaks timezone New page documenting the majormail (2026-06-05) issue: /etc/localtime shipped labeled etc_t instead of locale_t on the Hetzner image, so SELinux denied systemd-timedated and timedatectl/community.general.timezone reported success while the symlink stayed at UTC. Fix: restorecon before setting TZ. Indexed in index.md (SELinux) + SUMMARY.md. --- 05-troubleshooting/index.md | 1 + ...selinux-localtime-label-breaks-timezone.md | 92 +++++++++++++++++++ SUMMARY.md | 1 + 3 files changed, 94 insertions(+) create mode 100644 05-troubleshooting/selinux-localtime-label-breaks-timezone.md diff --git a/05-troubleshooting/index.md b/05-troubleshooting/index.md index 1388a88..970f13a 100644 --- a/05-troubleshooting/index.md +++ b/05-troubleshooting/index.md @@ -39,6 +39,7 @@ Practical fixes for common Linux, networking, and application problems. ## 🔒 SELinux - [SELinux: Fixing Dovecot Mail Spool Context (/var/vmail)](selinux-dovecot-vmail-context.md) +- [SELinux: Wrong /etc/localtime Label Silently Breaks Timezone Changes](selinux-localtime-label-breaks-timezone.md) ## 💾 Storage - [mdadm RAID Recovery After USB Hub Disconnect](storage/mdadm-usb-hub-disconnect-recovery.md) diff --git a/05-troubleshooting/selinux-localtime-label-breaks-timezone.md b/05-troubleshooting/selinux-localtime-label-breaks-timezone.md new file mode 100644 index 0000000..1961ebf --- /dev/null +++ b/05-troubleshooting/selinux-localtime-label-breaks-timezone.md @@ -0,0 +1,92 @@ +--- +title: "SELinux: Wrong /etc/localtime Label Silently Breaks Timezone Changes" +domain: troubleshooting +category: general +tags: [selinux, timezone, timedatectl, localtime, fedora, ansible, hetzner] +status: published +created: 2026-06-05 +updated: 2026-06-05 +--- +# SELinux: Wrong /etc/localtime Label Silently Breaks Timezone Changes + +`timedatectl set-timezone` (and Ansible's `community.general.timezone`) report **success but the timezone never actually changes** — `date` keeps showing the old zone. The cause is an SELinux mislabel on `/etc/localtime`: it must be `locale_t`, but freshly-provisioned images sometimes ship it as `etc_t`, which makes SELinux deny `systemd-timedated` from rewriting the symlink. + +> Hit on **majormail** (Fedora 44, SELinux Enforcing, Hetzner Cloud image), 2026-06-05. The box stayed on UTC for hours despite the timezone task "succeeding." + +## Symptoms + +- `timedatectl set-timezone America/New_York` exits **0**, but `date` still shows the old zone/offset. +- `timedatectl show -p Timezone --value` reports the **new** zone while `readlink /etc/localtime` still points at the **old** one — an inconsistent split state. +- Ansible `community.general.timezone` reports `changed=false` ("already set") because its idempotence check reads the stale in-memory value from `timedatectl`. +- `journalctl -u systemd-timedated` shows: `Failed to set time zone: Permission denied`. +- A direct `ln -sf … /etc/localtime` **works** — but a brand-new symlink may get the wrong label again, sending you in circles. + +## Why It Happens + +`systemd-timedated` changes the timezone by replacing the `/etc/localtime` symlink. Under SELinux Enforcing, that target must be labeled `locale_t`. If it is `etc_t` (or anything else), timedated is denied (`Permission denied`) and aborts — but `timedatectl`/the Ansible module surface this poorly, so the change looks like it took. The denial may be **dontaudit-suppressed**, so `ausearch -m avc` can come up empty, hiding the real cause. + +## Diagnosis + +```bash +# The split state — these two should agree but won't: +readlink /etc/localtime # e.g. .../Etc/UTC (the truth) +timedatectl show -p Timezone --value # e.g. America/New_York (stale) +date '+%Z %z' # confirms actual zone via the symlink + +# The label — this is the smoking gun: +ls -Z /etc/localtime # WRONG: ...:etc_t:s0 +matchpathcon /etc/localtime # EXPECTED: ...:locale_t:s0 + +# The denial (only if dontaudit is disabled): +journalctl -u systemd-timedated | grep -i 'permission denied' +``` + +## Fix + +Relabel first, *then* set the timezone the normal way: + +```bash +restorecon -v /etc/localtime # etc_t -> locale_t +timedatectl set-timezone America/New_York +# verify all three agree now: +date '+%F %T %Z (%z)'; readlink /etc/localtime; ls -Z /etc/localtime +``` + +If you set the symlink by hand (`ln -sf`) as a stopgap, run `restorecon /etc/localtime` afterward — a manually created symlink can inherit `etc_t` and re-break the next `timedatectl` call. + +Then restart anything that caches the zone at startup so logs/schedules switch over: + +```bash +systemctl restart rsyslog crond +``` + +(`journalctl` renders in local time automatically; rsyslog-written logs like `/var/log/maillog` keep the old zone until rsyslog restarts.) + +## Codify (Ansible) + +Run `restorecon` on `/etc/localtime` **before** the timezone task, so a mislabeled symlink can't silently defeat it: + +```yaml +- name: Ensure correct SELinux label on /etc/localtime + ansible.builtin.command: restorecon -v /etc/localtime + register: localtime_relabel + changed_when: "'Relabeled' in localtime_relabel.stdout" + when: ansible_selinux.status | default('disabled') == 'enabled' + +- name: Set timezone + community.general.timezone: + name: America/New_York +``` + +On majormail this is in `roles/majormail/tasks/main.yml` (MajorAnsible commit `2ff566d`). + +## Key Notes + +- **`timedatectl`/the Ansible module lie here.** Always confirm with `readlink /etc/localtime` + `date`, not just `timedatectl show`. +- **The denial can be invisible.** dontaudit rules may hide the AVC; trust the label mismatch (`ls -Z` vs `matchpathcon`) over an empty `ausearch`. +- **Fresh cloud images are the usual offender** — a clean rebuild/provision is where the wrong label sneaks in. + +## Related + +- [SELinux: Fixing Dovecot Mail Spool Context (/var/vmail)](selinux-dovecot-vmail-context.md) +- [Dovecot IMAP Clients Fail to Sync: vsz_limit OOM from a Bloated Index Log](networking/dovecot-imap-oom-vsz-limit-bloated-index.md) diff --git a/SUMMARY.md b/SUMMARY.md index cc6d664..56f4c5f 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -101,6 +101,7 @@ updated: 2026-05-15T09:00 * [Gitea Actions Runner: Boot Race Condition Fix](05-troubleshooting/gitea-runner-boot-race-network-target.md) * [Cron Heartbeat False Alarm: /var/run Cleared by Reboot](05-troubleshooting/cron-heartbeat-tmpfs-reboot-false-alarm.md) * [SELinux: Fixing Dovecot Mail Spool Context (/var/vmail)](05-troubleshooting/selinux-dovecot-vmail-context.md) + * [SELinux: Wrong /etc/localtime Label Silently Breaks Timezone Changes](05-troubleshooting/selinux-localtime-label-breaks-timezone.md) * [mdadm RAID Recovery After USB Hub Disconnect](05-troubleshooting/storage/mdadm-usb-hub-disconnect-recovery.md) * [Windows OpenSSH Server (sshd) Stops After Reboot](05-troubleshooting/networking/windows-sshd-stops-after-reboot.md) * [Windows OpenSSH: WSL Default Shell Breaks Remote Commands](05-troubleshooting/networking/windows-openssh-wsl-default-shell-breaks-remote-commands.md)