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.
This commit is contained in:
Marcus Summers 2026-06-05 14:22:00 -04:00
parent 26eb13ab2f
commit d755b77126
3 changed files with 94 additions and 0 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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)