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:
parent
26eb13ab2f
commit
d755b77126
3 changed files with 94 additions and 0 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue