13. August 2026

Fehlende Chrome-Bibliothek auf Debian 12 nachrüsten — ohne Root-Rechte

Nach einem Distributions-Upgrade startet der von Puppeteer mitgelieferte Chromium nicht mehr, weil libXss.so.1 fehlt. Auf einem Managed Server ohne Root kannst du das Paket trotzdem nachrüsten — entpacken statt installieren.

Ein Node-Dienst rendert per Puppeteer Vorschaubilder. Nach einem Distributions-Upgrade rendert er nichts mehr. Die Anfrage kommt an, wird protokolliert, und danach passiert nichts — kein Fehler, kein Timeout, nur Stille. Schuld war eine einzelne Systembibliothek, die das Upgrade entfernt hat, weil sie kein aktuelles Programm mehr braucht. Der mitgelieferte Chromium ist aber von 2020 und braucht sie sehr wohl.

Root hatte ich auf dem Server nicht. Der Weg unten kommt ohne aus.

Getestet mit

Debian 12 (Bookworm), Managed Server ohne Root-Zugang
Chromium 85.0.4182.0 (mitgeliefert mit puppeteer-cluster)
libxss1 1:1.2.3-1 aus dem Bookworm-Pool
Node-Dienst unter PM2, Apache als Reverse Proxy davor

Schritt 1: Die echte Fehlermeldung aus Puppeteer herausholen

Puppeteer verschluckt die Ausgabe von Chromium, solange du sie nicht ausdrücklich durchreichst. Im Protokoll steht deshalb nur der Rahmen:

Unable to launch browser: Failed to launch the browser process!
ERROR:dbus/bus.cc:405 Failed to connect to the bus
WARNING: no ptrace
  at Cluster.<anonymous> (node_modules/puppeteer-cluster/src/Cluster.ts:168:19)

Die dbus-Zeile sieht nach der Ursache aus und ist keine. Headless-Chromium meckert die in praktisch jeder Serverumgebung an, auch wenn alles läuft.

Schneller kommst du an die Wahrheit, wenn du die Programmdatei direkt aufrufst. Den Pfad kennt Puppeteer selbst:

node -e "console.log(require('puppeteer').executablePath())"

Und damit:

/PFAD/AUS/DEM/BEFEHL/OBEN --version

Bei mir kam:

error while loading shared libraries: libXss.so.1: cannot open shared object file: No such file or directory

Wenn du es lieber aus der laufenden Anwendung heraus sehen willst, setz beim Start dumpio. Dann landen stdout und stderr von Chromium in deinem Prozess-Log:

const browser = await puppeteer.launch({
  headless: true,
  dumpio: true,
  args: ['--no-sandbox']
});

Schritt 2: Alle fehlenden Bibliotheken auf einmal auflisten

Chromium meldet immer nur die erste fehlende Bibliothek. Rüstest du die nach, kommt die nächste. Deshalb gleich die vollständige Liste holen:

ldd /PFAD/ZU/chrome | grep "not found"

Bei mir stand dort genau eine Zeile:

libXss.so.1 => not found

Prüf an dieser Stelle auch, ob mehrere Chromium-Instanzen auf dem System liegen. Bei mir waren es zwei — eine für puppeteer-cluster, eine für node-html-to-image. Beide hatten dasselbe Problem, und nur eine zu reparieren hätte die halbe Anwendung weiterhin stillgelegt.

Schritt 3: Das Debian-Paket zur Bibliothek finden

Der Dateiname der Bibliothek ist nicht der Paketname. Liegt die Datei noch irgendwo auf dem System, beantwortet das dpkg:

dpkg -S libXss.so.1

Bei einer restlos entfernten Bibliothek liefert das nichts. Dann suchst du auf packages.debian.org über die Paketinhalte nach dem exakten Dateinamen. apt-file wäre der Weg auf der Kommandozeile, aber dessen Installation braucht wieder Root — auf einem Managed Server ist der Browser hier schneller als die Diskussion mit dem Hoster.

libXss.so.1 gehört zu libxss1, der X11 Screen Saver Extension.

Schritt 4: Das Paket entpacken statt installieren

Hier liegt der eigentliche Trick. Du brauchst das Paket nicht zu installieren — du brauchst nur die Datei daraus. dpkg-deb -x entpackt ein .deb in ein beliebiges Verzeichnis und rührt das System nicht an. Kein Root nötig.

mkdir -p ~/lib-extra && cd ~/lib-extra
curl -fLO https://deb.debian.org/debian/pool/main/libx/libxss/libxss1_1.2.3-1_amd64.deb
dpkg-deb -x libxss1_1.2.3-1_amd64.deb extracted
cp -av $(find extracted -name "libXss.so*") ~/lib-extra/

Den genauen Dateinamen des Pakets findest du auf packages.debian.org beim Paket unter „Download“. Die Epoche aus der Versionsnummer (1:) taucht im Dateinamen nicht auf — 1:1.2.3-1 wird zu libxss1_1.2.3-1_amd64.deb.

Jetzt der Test, bevor du irgendetwas dauerhaft einstellst:

LD_LIBRARY_PATH="$HOME/lib-extra" /PFAD/ZU/chrome --version
Chromium 85.0.4182.0

Und ein echter Seitenaufbau:

LD_LIBRARY_PATH="$HOME/lib-extra" /PFAD/ZU/chrome --headless --no-sandbox --dump-dom about:blank
<html><head></head><body></body></html>

Schritt 5: Die Bibliothek an den Dienst binden

LD_LIBRARY_PATH in deiner Login-Shell hilft dem Dienst nicht. Der läuft unter einem Prozessmanager und sieht deine .bashrc nie. Die Variable gehört deshalb in die Prozesskonfiguration.

Unter PM2 in die ecosystem.config.js:

module.exports = {
  apps: [{
    name: 'DIENSTNAME',
    script: './dist/index.js',
    cwd: '/PFAD/ZUR/ANWENDUNG',
    exec_mode: 'fork',
    instances: 1,
    env: {
      NODE_ENV: 'production',
      LD_LIBRARY_PATH: '/PFAD/ZU/HOME/lib-extra'
    }
  }]
};

Unter systemd wäre es Environment=LD_LIBRARY_PATH=/PFAD/ZU/HOME/lib-extra in der Unit.

Schreib den Pfad absolut hin. $HOME und ~ werden an dieser Stelle nicht aufgelöst, und der Fehler fällt dir erst auf, wenn der Dienst wieder hängt.

Danach neu einlesen:

pm2 delete DIENSTNAME
pm2 start ecosystem.config.js
pm2 save

Der Paketserver war seit fünf Jahren abgeschaltet

Auf dem System lag Linuxbrew. Das war mein erster Gedanke: Paketverwaltung im Home-Verzeichnis, genau für Fälle ohne Root gebaut, brew install libxss und fertig.

Jeder Download brach ab. Erst Zertifikatsfehler, dann bei genauerem Hinsehen ein sauberer HTTP-Status:

curl -I https://linuxbrew.bintray.com/
HTTP/1.1 410 Gone

410 heißt nicht „gerade kaputt“, sondern „hier ist dauerhaft nichts mehr“. Homebrew hat seine Pakete früher über Bintray ausgeliefert, und Bintray wurde 2021 abgeschaltet. Die Installation auf dem Server war älter und zeigte weiterhin dorthin. Ein Paketmanager, der aussieht, als würde er funktionieren, aber auf einen Server zeigt, den es seit fünf Jahren nicht mehr gibt.

Seitdem prüfe ich bei einem Werkzeug, das ich auf einem alten System zum ersten Mal benutze, zuerst mit einem einzelnen curl -I, ob seine Quelle überhaupt noch antwortet. Das kostet zehn Sekunden und hätte mir hier gut eine halbe Stunde erspart.

Der Umweg hatte trotzdem etwas Gutes. Ohne ihn hätte ich dpkg-deb -x nicht gesucht, und das ist die robustere Lösung: keine zweite Paketverwaltung, keine fremde Quelle, nur eine Datei aus dem offiziellen Debian-Paket.

Kontrolle

Erstens: keine offenen Abhängigkeiten mehr. Der Befehl gibt nichts aus, wenn alles da ist.

LD_LIBRARY_PATH="$HOME/lib-extra" ldd /PFAD/ZU/chrome | grep "not found"

Zweitens: Chromium startet und liefert eine Seite zurück.

LD_LIBRARY_PATH="$HOME/lib-extra" /PFAD/ZU/chrome --headless --no-sandbox --dump-dom about:blank

Erwartete Ausgabe:

<html><head></head><body></body></html>

Drittens: Der Dienst sieht die Variable auch wirklich. Nicht die Shell fragen, sondern den laufenden Prozess:

tr '' 'n' < /proc/$(pgrep -f dist/index.js | head -1)/environ | grep LD_LIBRARY_PATH

Steht dort dein Pfad, ist die Konfiguration angekommen. Steht dort nichts, läuft der Dienst noch mit der alten Umgebung und muss neu gestartet werden.

Viertens: eine echte Anfrage durchspielen und nachsehen, ob eine neue Bilddatei entstanden ist. Prüf danach, ob Chromium-Prozesse übrig bleiben:

pgrep -fa chrome

Nach Abschluss der Anfrage sollte hier nichts mehr stehen. Bleiben Prozesse liegen, schließt die Anwendung den Browser nicht sauber — das ist ein eigenes Problem und frisst dir über Wochen den Arbeitsspeicher.

Häufige Fehler

Die dbus-Zeile im Log — ist das die Ursache?
Nein. Failed to connect to the bus steht in fast jedem Headless-Chromium-Log auf einem Server ohne Desktop-Sitzung, auch wenn alles sauber läuft. Die verwertbare Zeile ist error while loading shared libraries. Ruf die Programmdatei direkt auf, statt das Log zu lesen.

Ich habe die Bibliothek kopiert, Chromium startet trotzdem nicht.
Meistens fehlt die nächste. Lauf ldd | grep "not found" nach jedem Durchgang erneut, bis die Ausgabe leer bleibt. Bleibt sie leer und es startet immer noch nicht, zeigt LD_LIBRARY_PATH vermutlich auf ein anderes Verzeichnis als das, in dem die Datei tatsächlich liegt.

Im Terminal läuft es, als Dienst nicht.
Dann steht die Variable in deiner Shell, aber nicht in der Umgebung des Prozesses. Prozessmanager erben deine Login-Shell nicht. Setz sie in der Prozesskonfiguration und prüf sie über /proc/PID/environ nach.

Warum nicht einfach dpkg -i?
Weil das Root braucht, nach /usr schreibt und beim nächsten Distributions-Upgrade wieder aufgeräumt wird. Auf einem Managed Server hast du beides nicht in der Hand. Das entpackte Paket im Home-Verzeichnis überlebt Systemupdates, weil es niemand für Systembestand hält.

Wie lange hält das?
Bis zum Serverwechsel. Die Datei liegt außerhalb der Paketverwaltung, bekommt also keine Sicherheitsupdates und wandert bei einem Umzug nicht automatisch mit. Das ist ein Hotfix, kein Zustand. Dauerhaft hilft nur, Puppeteer und den mitgelieferten Chromium auf einen aktuellen Stand zu bringen — npx puppeteer browsers install chrome holt einen passenden Build — oder den Dienst in einen Container zu packen, in dem die Bibliotheken mitgeliefert werden. Beides ist Arbeit an einer Anwendung, die seit Jahren niemand angefasst hat. Genau deshalb schiebt man es auf.

Der Dienst startete bei mir gar nicht erst.
Dann steckt das Problem vor dem Browser. In demselben Fall lief davor eine Neustartschleife des Prozessmanagers, ausgelöst von zwei parallel installierten PM2-Versionen: PM2 startet den Dienst endlos neu und schreibt nichts ins Log.

Falls du nicht weiterkommst

Ich mache das beruflich. Schreib mir kurz, worum es geht — ich sag dir ehrlich, ob ich helfen kann.
Zu purin.at
← Alle Beiträge