---
title: 'upsmon unter OpenSUSE Tumbleweed mit aktivem SELinux'
url: 'https://www.mmorath.de/de/wiki/upsmon-unter-opensuse-tumbleweed-mit-altivem-selinux'
markdown: 'https://www.mmorath.de/de/wiki/upsmon-unter-opensuse-tumbleweed-mit-altivem-selinux.md'
lang: de
date: '2026-09-12'
description: 'USV mit NUT unter openSUSE Tumbleweed einrichten — inklusive SELinux Eine USV per USB an den Rechner hängen und dafür sorgen, dass er bei Stromausfall rechtzeitig herunterfährt: klingt nach einer Viertelstunde Arbeit. Unter openSUSE Tumbleweed mit aktivem SELinux waren es bei mir eher drei Stunden,…'
taxonomy:
  category:
    - Wiki
  tag:
    - upsmon
    - usv
    - ups
    - OpenSUSE
    - SELinux
---

# USV mit NUT unter openSUSE Tumbleweed einrichten — inklusive SELinux

Eine USV per USB an den Rechner hängen und dafür sorgen, dass er bei Stromausfall rechtzeitig herunterfährt: klingt nach einer Viertelstunde Arbeit. Unter openSUSE Tumbleweed mit aktivem SELinux waren es bei mir eher drei Stunden, weil gewisse Details nirgends dokumentiert sind und die [von OpenSUSE bereitgestellte Anleitung](https://en.opensuse.org/SDB:Network_UPS_Tools) in die Jahre gekommen ist.

Dieses Martyrium möchte ich euch gerne ersparen. Die Nachfolgende Anleitung stelle ich euch daher zur Verfügung und hoffe, dass sie bei euch auch funktioniert.

Diese Anleitung beschreibt den kompletten Weg für den **Standalone-Betrieb**: Eine USV, ein Rechner, kein Netzwerkzugriff durch andere Systeme. Getestet mit NUT 2.8.5 und einer APC Back-UPS RS 900G, gilt aber unverändert für jede andere USV mit `usbhid-ups`.

## Das Wichtigste vorweg: upsmon kennt keine Prozentschwelle

Der häufigste Irrtum beim Einstieg. `upsmon` reagiert ausschließlich auf zwei Flags, die die USV beziehungsweise der Treiber liefert: `OB` (Netzbetrieb ausgefallen) und `LB` (Akku fast leer). Eine Angabe wie „fahre bei 25 % herunter" gibt es in `upsmon.conf` nicht.

Die Schwelle wird deshalb dort konfiguriert, wo das `LB`-Flag entsteht — im Treiber, also in `ups.conf`.

## Schritt 1: Installation

```bash
sudo zypper install nut
```

Die Konfiguration liegt auf openSUSE unter `/etc/ups/`, nicht unter `/etc/nut/` wie bei Debian-basierten Distributionen. Fast alle Anleitungen im Netz nennen den Debian-Pfad — das ist die erste Stolperfalle.

Als Nächstes prüfen, ob die USV überhaupt erkannt wird:

```bash
lsusb
sudo nut-scanner -U
```

`nut-scanner` schlägt den passenden Treiber vor. Bei USB-USVen ist das praktisch immer `usbhid-ups`.

## Schritt 2: `/etc/ups/ups.conf`

Hier wird der Treiber konfiguriert und die Abschaltschwelle festgelegt:

```ini
maxretry = 3

[usv]
    driver = usbhid-ups
    port = auto
    desc = "APC Back-UPS RS 900G"
    offdelay = 30
    ondelay = 30
    ignorelb
    override.battery.charge.low = 25
    override.battery.runtime.low = 120
```

Der Name in eckigen Klammern ist frei wählbar, wird aber später an mehreren Stellen wieder gebraucht — kurz und ohne Sonderzeichen halten.

Die drei entscheidenden Zeilen:

- **`ignorelb`** weist den Treiber an, das Hardware-`LB`-Flag der USV zu ignorieren und stattdessen selbst zu entscheiden.
- **`override.battery.charge.low = 25`** setzt `LB`, sobald der Ladezustand unter 25 % fällt.
- **`override.battery.runtime.low = 120`** setzt `LB` zusätzlich, sobald weniger als 120 Sekunden Restlaufzeit gemeldet werden.

Beide Bedingungen sind ODER-verknüpft. Die Laufzeitschwelle ist ein wichtiges Sicherheitsnetz: Unter hoher Last können 25 % Restladung deutlich weniger Zeit bedeuten als im Leerlauf.

Ein Warnhinweis zu `ignorelb`: Meldet die USV weder `battery.charge` noch `battery.runtime`, wird `LB` nie ausgelöst und das System fährt **gar nicht** herunter. Nach Schritt 8 unbedingt kontrollieren, dass beide Werte vorhanden sind.

## Schritt 3: `/etc/ups/nut.conf`

```ini
MODE=standalone
```

`standalone` bedeutet: `upsd` läuft, lauscht aber nur auf localhost. Genau richtig, wenn kein weiteres System auf diese USV zugreifen soll.

## Schritt 4: `/etc/ups/upsd.users`

NUT hat eine eigene, interne Benutzerverwaltung. Diese Benutzer haben **nichts** mit Linux-Systembenutzern zu tun — das Passwort wird hier frei gewählt:

```ini
[upsmon]
    password = EinSicheresPasswort
    upsmon primary
```

Zwei Fallen:

- Das Trennzeichen ist ein **Gleichheitszeichen**, kein Doppelpunkt.
- Enthält das Passwort ein `#`, schneidet der Parser ab dort alles ab. Für den ersten Durchlauf ein rein alphanumerisches Passwort verwenden.

`primary` ist auch im Standalone-Betrieb korrekt. Es bedeutet lediglich, dass dieses System die USV direkt am Port hängen hat.

## Schritt 5: `/etc/ups/upsmon.conf`

```
MONITOR usv@localhost 1 upsmon EinSicheresPasswort primary
MINSUPPLIES 1
SHUTDOWNCMD "/usr/sbin/shutdown -h +0"
POWERDOWNFLAG /etc/killpower
NOTIFYFLAG ONBATT SYSLOG+EXEC
NOTIFYFLAG LOWBATT SYSLOG+EXEC
```

Die `MONITOR`-Zeile hat **sechs Felder** und die Reihenfolge ist bindend:

```
MONITOR <name>@<host> <powervalue> <benutzer> <passwort> primary
```

Fehlt ein Feld, interpretiert NUT 2.8 die Zeile als veraltetes Format und verweigert den Start mit `Unable to use old-style MONITOR line without a username`. Benutzername und Passwort müssen exakt dem entsprechen, was in `upsd.users` steht.

`SHUTDOWNCMD` muss in Anführungszeichen stehen, sonst zerlegt der Parser den Befehl an den Leerzeichen.

Die Standardkonfiguration verwendet `WALL` für Benachrichtigungen. Auf einem Desktop mit Wayland-Session erreicht das niemanden und produziert nur Fehler im Journal — deshalb oben `SYSLOG+EXEC`. Wer einen lokalen Mailversand eingerichtet hat, ergänzt sinnvollerweise ein `NOTIFYCMD`, das eine Mail verschickt.

## Schritt 6: Dateirechte

Drei Dateien enthalten Zugangsdaten. Auf openSUSE ist die zuständige Gruppe `upsd` — **nicht** `nut`, wie in vielen Anleitungen zu lesen:

```bash
sudo chown root:upsd /etc/ups/upsd.conf /etc/ups/upsd.users /etc/ups/upsmon.conf
sudo chmod 640 /etc/ups/upsd.conf /etc/ups/upsd.users /etc/ups/upsmon.conf
```

## Schritt 7: Die beiden openSUSE-spezifischen Stolpersteine

Hier verstecken sich die Probleme, die den größten Teil der Zeit kosten. Beide erzeugen ein `Permission denied`, obwohl bei oberflächlicher Prüfung alle Rechte korrekt aussehen.

### 7a: Gruppenmitgliedschaft für den USB-Zugriff

Das NUT-Paket liefert udev-Regeln mit, die den USB-Gerätenoten der Gruppe `daemon` zuweisen:

```
ATTR{idVendor}=="051d", ATTR{idProduct}=="0002", MODE="664", GROUP="daemon"
```

Der Treiber legt beim Start die Root-Rechte ab und wechselt auf den Benutzer `upsd`. Dessen **primäre Gruppe** ist aber `upsd`, nicht `daemon`. Ergebnis: Ein Prozess in Gruppe `upsd` steht vor einem Gerät der Gruppe `daemon` — kein Zugriff.

Die Fehlermeldung lautet:

```
libusb1: Could not open any HID devices: insufficient permissions on everything
No matching HID UPS found
```

Das ist irreführend, weil es nach einem fehlenden Treiber oder einer fehlenden udev-Regel klingt. Die Lösung:

```bash
sudo usermod -aG daemon upsd
id upsd
```

Die Ausgabe muss `453(upsd),2(daemon)` enthalten.

**Wichtig:** Keine eigene udev-Regel schreiben. Die mitgelieferte Regel ist korrekt und deckt alle gängigen Geräte ab. Sie greift bei jedem USB-Port und nach jedem Reboot, weil sie auf Vendor- und Product-ID matcht, nicht auf einen Gerätepfad.

### 7b: SELinux-Kontext für das State-Verzeichnis

Nach 7a öffnet der Treiber die USV erfolgreich, scheitert aber beim nächsten Schritt:

```
Fatal error: unable to create listener socket
bind /var/lib/ups/usbhid-ups-usv failed: Permission denied
```

Die Dateirechte auf `/var/lib/ups` sind korrekt (`upsd:daemon`, `770`), der Prozess läuft als Eigentümer — und trotzdem verweigert das System den Zugriff. Der Grund steht im Audit-Log:

```bash
sudo ausearch -m avc -ts recent
```

```
avc: denied { write } for comm="usbhid-ups"
scontext=system_u:system_r:nut_upsdrvctl_t:s0
tcontext=system_u:object_r:var_lib_t:s0 tclass=dir
```

Die SELinux-Policy kennt für NUT-Laufzeitdaten ausschließlich den Pfad `/run/nut` mit dem Typ `nut_var_run_t`. openSUSE baut NUT aber mit `/var/lib/ups` als State-Verzeichnis. Dieser Pfad kommt in der Policy nicht vor, trägt deshalb den generischen Typ `var_lib_t` — und der Schreibzugriff wird blockiert.

Die saubere Lösung ist eine Dateikontext-Regel:

```bash
sudo semanage fcontext -a -t nut_var_run_t '/var/lib/ups(/.*)?'
sudo restorecon -Rv /var/lib/ups
ls -Zd /var/lib/ups
```

Danach muss dort `nut_var_run_t` stehen. Falls `semanage` fehlt:

```bash
sudo zypper install policycoreutils-python-utils
```

**Bewusst nicht empfohlen:** ein Policy-Modul über `audit2allow`. Das würde dem Treiber Schreibzugriff auf sämtliche `var_lib_t`-Verzeichnisse erlauben — deutlich weitreichender als nötig, wenn das eigentliche Problem nur ein falsch etikettiertes Verzeichnis ist. Ebenso wenig ist `setenforce 0` eine Lösung; als kurzer Diagnoseschritt in Ordnung, danach sofort wieder `setenforce 1`.

Die `semanage`-Regel landet in der lokalen Policy-Datenbank und übersteht Paketupdates und Reboots. Bei einer Neuinstallation muss sie erneut gesetzt werden — ein guter Kandidat für die eigene Ansible-Rolle:

```yaml
- name: SELinux-Kontext für NUT-State-Verzeichnis
  community.general.sefcontext:
    target: '/var/lib/ups(/.*)?'
    setype: nut_var_run_t
    state: present
  notify: restorecon ups
```

## Schritt 8: Dienste starten

NUT 2.8 erzeugt über den `nut-driver-enumerator` für jede Sektion aus `ups.conf` eine eigene systemd-Unit. Nach Änderungen an `ups.conf` muss der Enumerator neu laufen, sonst existiert die Unit nicht oder ist veraltet:

```bash
sudo systemctl restart nut-driver-enumerator.service
systemctl list-units 'nut-driver@*'
```

Dann die Dienste aktivieren:

```bash
sudo systemctl enable --now nut-driver@usv nut-server nut-monitor
```

### Der dritte Stolperstein: nut.target

Und jetzt der Schritt, den fast jede Anleitung auslässt — er fällt erst beim ersten Reboot auf:

```bash
sudo systemctl enable --now nut.target
```

Der Grund: `nut-server.service` und `nut-monitor.service` tragen beide

```
WantedBy=nut.target
```

Sie hängen also ausschließlich an `nut.target` und nicht an `multi-user.target`. Das Paket-Preset aktiviert dieses Target aber nicht. `systemctl enable nut-server nut-monitor` legt daher nur Symlinks unterhalb eines Targets an, das beim Boot nie gezogen wird.

Das Tückische daran: Durch `--now` starten die Dienste sofort, alles läuft, die Einrichtung wirkt abgeschlossen. Erst nach dem nächsten Neustart sind alle drei Units *inactive (dead)* — **nicht** *failed*. Es gibt keinen Fehler im Journal, weil es gar keinen Startversuch gab. Ein Client wie das KDE-Miniprogramm „UPS Monitor" meldet dann nur `Connection failure: Connection refused`.

Kontrolle:

```bash
systemctl is-enabled nut.target
systemctl list-dependencies nut.target
```

In der Abhängigkeitsliste müssen Treiber, Server und Monitor mit gefülltem Punkt erscheinen. Dass `local-fs.target` dort ebenfalls auftaucht, ist gewollt — so ist sichergestellt, dass `/var/lib/ups` bereitsteht, bevor der Treiber startet.

### Hinweise zum Umgang mit den Units

Den Treiber nicht mit `upsdrvctl` starten — auf Systemen mit systemd führt das zu Konflikten mit den Unit-Instanzen. Falls doch einmal manuell nötig, `upsdrvsvcctl` verwenden.

Für Statusabfragen die kanonischen Namen verwenden: `nut-driver@…`, `nut-server`, `nut-monitor`. Die Aliase `upsd.service` und `upsmon.service` existieren zwar und funktionieren für `start`/`stop`, aber `systemctl status` liefert darüber teils irreführende Ausgaben.

## Schritt 9: Kontrolle

```bash
upsc usv
```

Die Ausgabe sollte unter anderem enthalten:

```
battery.charge: 100
battery.charge.low: 25
battery.runtime: 905
battery.runtime.low: 120
driver.flag.ignorelb: enabled
ups.status: OL
```

Entscheidend sind `driver.flag.ignorelb: enabled` sowie die tatsächlich übernommenen Schwellwerte. `ups.status: OL` bedeutet Netzbetrieb.

Und der Monitor:

```bash
systemctl status nut-monitor
```

Hier darf kein `ERR ACCESS-DENIED` mehr auftauchen. Erscheint es doch, stimmen Benutzername oder Passwort zwischen `upsd.users` und der `MONITOR`-Zeile nicht überein. `upsd` liest `upsd.users` nur beim Start — nach einer Korrektur also `nut-server` neu starten, nicht nur `nut-monitor`.

Dass in der Prozessliste zwei `upsmon`-Prozesse erscheinen, ist beabsichtigt: Der root-Prozess löst später den Shutdown aus, die eigentliche Überwachung läuft unprivilegiert als `upsd`.

## Schritt 10: Funktionstest

Den Netzstecker der USV ziehen und beobachten:

```bash
watch -n 2 'upsc usv ups.status battery.charge'
```

Der Status muss binnen weniger Sekunden auf `OB` wechseln. Ein vollständiger Test bis zur Abschaltung dauert je nach Akkukapazität allerdings lange. Wer den kompletten Ablauf inklusive Shutdown verifizieren will, setzt `override.battery.charge.low` vorübergehend auf 95, testet, und stellt anschließend auf den gewünschten Wert zurück.

## Die richtige Schwelle wählen

25 % ist ein brauchbarer Startwert, aber keine allgemeingültige Empfehlung. Entscheidend ist, wie viel Restlaufzeit dahintersteht. Nach ein paar Tagen Betrieb lohnt sich der Blick auf `battery.runtime` unter realistischer Last: Bleiben bei der gewählten Ladeschwelle weniger als etwa zwei Minuten, sollte die Schwelle höher.

Auf einem Rechner mit leistungsstarker Grafikkarte kann der Unterschied zwischen Leerlauf und Volllast Faktor drei oder mehr betragen. Genau deshalb ist die zusätzliche `runtime.low`-Schwelle aus Schritt 2 keine Spielerei, sondern greift in genau diesem Fall.

Ein letzter Blick lohnt sich auf `battery.mfr.date` in der `upsc`-Ausgabe. Bleiakkus in USVen halten typischerweise drei bis fünf Jahre. Steht dort ein Datum jenseits dieser Spanne, ist die gemeldete Restlaufzeit mit Vorsicht zu genießen — unabhängig davon, was die Software anzeigt.

## Zusammenfassung der Fallstricke

| Symptom | Ursache | Lösung |
|---|---|---|
| Konfigurationsdateien nicht auffindbar | openSUSE nutzt `/etc/ups/` statt `/etc/nut/` | Pfad anpassen |
| `chown: ungültige Gruppe 'nut'` | Gruppe heißt auf openSUSE `upsd` | `chown root:upsd` |
| `Unable to use old-style MONITOR line` | `MONITOR`-Zeile hat weniger als sechs Felder | Benutzername ergänzen |
| `insufficient permissions on everything` | `upsd` ist nicht in Gruppe `daemon` | `usermod -aG daemon upsd` |
| `bind ... failed: Permission denied` | SELinux-Kontext `var_lib_t` statt `nut_var_run_t` | `semanage fcontext` + `restorecon` |
| `ERR ACCESS-DENIED` | Passwort stimmt nicht oder `upsd` nicht neu gestartet | `upsd.users` prüfen, `nut-server` neu starten |
| `wall: Kommando nicht gefunden` | `WALL` in den NOTIFYFLAGS | auf `SYSLOG+EXEC` umstellen |
| Nach Reboot alle Dienste *inactive (dead)*, kein Fehler im Journal | `nut.target` ist nicht aktiviert | `systemctl enable --now nut.target` |
| `upsd.conf: invalid directive [...]` | Benutzerdefinitionen stehen in `upsd.conf` statt in `upsd.users` | Blöcke verschieben |

---

## Navigation

- Parent: [Wiki](https://www.mmorath.de/de/wiki.md)
