System-Ressourcen-Limits mit ulimit konfigurieren
$
/etc
Linux · ulimit · Ressourcen-Limits · Server-Administration
System-Ressourcen-Limits mit ulimit konfigurieren
Von Soft-Limits über limits.conf bis zum systemd-Override

Wenn PHP-FPM oder Nginx unter Last mit Too many open files abbricht, liegt die Ursache fast immer an einem zu niedrigen ulimit-Wert statt an echtem Ressourcenmangel. Dieser Artikel erklärt den Unterschied zwischen Soft- und Hard-Limits, zeigt, wie man File-Descriptor- und Prozess-Grenzen über limits.conf und systemd-Overrides dauerhaft und korrekt konfiguriert, und wie man den Fehler zuverlässig diagnostiziert.

13 Min. Lesezeit ulimit · limits.conf · systemd · LimitNOFILE PHP-FPM · Nginx · Linux · Docker

1. Was ulimit wirklich begrenzt: Soft- und Hard-Limits

ulimit ist die Schnittstelle der Shell zu den Ressourcengrenzen, die der Linux-Kernel jedem Prozess auferlegt: maximale Anzahl offener Dateien, maximale Anzahl gleichzeitiger Prozesse eines Benutzers, maximale Stack-Größe und mehrere weitere Kontingente. Jede dieser Grenzen existiert in zwei Ausprägungen. Das Soft-Limit ist der aktuell wirksame Wert, den ein Prozess ohne besondere Rechte selbst erhöhen kann, solange er das Hard-Limit nicht überschreitet. Das Hard-Limit ist die absolute Obergrenze, die nur root senken oder anheben darf und die als Deckel für das Soft-Limit fungiert.

Diese Zweiteilung existiert bewusst: Anwendungen sollen sich innerhalb eines sicheren Rahmens selbst an ihren tatsächlichen Bedarf anpassen können, ohne dass jeder Prozess root-Rechte braucht, um mehr Ressourcen anzufordern. In der Praxis bedeutet das, dass ein PHP-FPM-Worker sein Soft-Limit für offene Dateien zur Laufzeit auf bis zu 65536 hochsetzen könnte, sofern das Hard-Limit dies erlaubt, es aber niemals von selbst tut, wenn niemand die Limits explizit konfiguriert hat. Genau dort entsteht die häufigste Fehlerquelle: Der Standardwert vieler Distributionen liegt bei 1024 offenen Dateien pro Prozess, ein Wert, der für einen Webserver mit hoher gleichzeitiger Verbindungsanzahl viel zu niedrig ist.

2. Der ulimit-Befehl: Syntax, Shell-Scope und wichtige Flags

Der Befehl ulimit -a zeigt alle aktuell gesetzten Limits der laufenden Shell an, jeweils mit Soft-Wert, sofern nicht anders angegeben. Mit -S und -H lässt sich gezielt zwischen Soft- und Hard-Limit unterscheiden, etwa ulimit -Sn für das aktuelle Soft-Limit offener Dateien und ulimit -Hn für das entsprechende Hard-Limit. Wichtig ist dabei der Geltungsbereich: ulimit, in einer interaktiven Shell gesetzt, gilt ausschließlich für diese Shell und alle aus ihr gestarteten Kindprozesse, nicht für bereits laufende Prozesse und nicht für Prozesse, die von systemd oder einem Display-Manager unabhängig von der Shell gestartet werden.

Das -n-Flag steuert RLIMIT_NOFILE, also die maximale Anzahl offener File-Descriptors, das -u-Flag steuert RLIMIT_NPROC, die maximale Anzahl Prozesse und Threads eines Benutzers. Beide Werte sind für Webserver-Workloads am relevantesten, weil jede TCP-Verbindung, jede geöffnete Datei und jeder Unix-Socket einen File-Descriptor belegt, und jeder PHP-FPM-Worker sowie jeder Thread eines Prozesses gegen das Prozess-Limit zählt. Ein manuell in der Shell gesetztes Limit überlebt weder einen Neustart des Dienstes noch einen Server-Reboot, weshalb es für produktive Systeme nur als temporärer Test taugt.


# Show all current limits of the running shell
$ ulimit -a
core file size          (blocks, -c) 0
open files                      (-n) 1024
max user processes              (-u) 15938
stack size          (kbytes, -s) 8192

# Inspect soft vs. hard limit for open file descriptors
$ ulimit -Sn
1024
$ ulimit -Hn
1048576

# Raise the soft limit within the hard limit ceiling (session-scoped only)
$ ulimit -n 65536
$ ulimit -Sn
65536

3. Warum PHP- und Webserver-Workloads besonders betroffen sind

PHP-FPM startet pro konfiguriertem Worker einen eigenen Prozess, und jeder dieser Worker hält Datenbankverbindungen, Cache-Verbindungen zu Redis oder Memcached, geöffnete Log-Dateien und die eigentliche Client-Verbindung gleichzeitig offen. Bei pm.max_children = 50 und mehreren offenen Verbindungen pro Worker summiert sich der Bedarf an File-Descriptors schnell auf mehrere Tausend, weit über dem Standardlimit von 1024. Sobald ein Worker sein Limit erreicht, schlägt jede weitere fopen()-, fsockopen()- oder Datenbankverbindung mit einem EMFILE-Fehler fehl, den PHP als Warnung oder Exception meldet.

Aehnlich kritisch ist das Prozess-Limit bei Anwendungen, die intern Threads oder Subprozesse erzeugen, etwa Queue-Worker, die parallele Jobs verarbeiten, oder Build-Pipelines mit vielen gleichzeitigen Kindprozessen. Nginx als vorgeschalteter Reverse Proxy hält zusätzlich eigene File-Descriptors für jede Client-Verbindung, jeden Upstream-Socket zu PHP-FPM und jede geöffnete Log-Datei. Bei hoher gleichzeitiger Nutzerzahl treffen deshalb Nginx und PHP-FPM oft zeitgleich auf ihre jeweiligen Limits, was die Fehlersuche erschwert, wenn nicht beide Dienste unabhängig voneinander geprüft werden.

4. Persistente Limits über /etc/security/limits.conf setzen

Damit ein Limit einen Neustart übersteht, muss es über das PAM-Modul pam_limits konfiguriert werden, dessen zentrale Konfigurationsdatei /etc/security/limits.conf ist. Jede Zeile besteht aus vier Feldern: Domain (Benutzer, Gruppe mit @-Präfix oder * für alle), Typ (soft, hard oder - für beide gleichzeitig), Ressource (nofile, nproc und weitere) und der eigentliche Wert. Diese Datei wirkt jedoch ausschließlich auf Prozesse, die über einen PAM-gesteuerten Login-Pfad gestartet werden, also interaktive SSH-Sessions oder su/login-basierte Prozesse.

Genau diese Einschränkung ist eine häufige Fehlerquelle: Ein Administrator setzt in limits.conf großzügige Werte für den Benutzer www-data, meldet sich zum Testen per SSH als www-data an und sieht dort korrekt das neue Limit. Der eigentliche PHP-FPM-Prozess, von systemd direkt beim Booten gestartet, durchläuft aber niemals den PAM-Login-Pfad und ignoriert limits.conf deshalb vollständig. Zusätzlich muss in /etc/pam.d/common-session die Zeile session required pam_limits.so vorhanden sein, sonst greift die Konfiguration selbst bei echten Logins nicht.


# /etc/security/limits.conf: persistent limits for the www-data user
# domain      type    item    value
www-data      soft    nofile  65536
www-data      hard    nofile  131072
www-data      soft    nproc   4096
www-data      hard    nproc   8192

# Group-wide limit for all members of the docker group
@docker       soft    nofile  65536
@docker       hard    nofile  131072

# Applies only to PAM-driven login paths (ssh, su, login)
# NOT applied to services started directly by systemd

5. systemd-Unit-Overrides: LimitNOFILE und LimitNPROC

Weil die meisten produktiven Dienste heute direkt über systemd gestartet werden und nicht über einen PAM-Login-Pfad, ist limits.conf für sie faktisch wirkungslos. Der korrekte Ort für Ressourcengrenzen von Diensten wie PHP-FPM oder Nginx ist die Unit-Datei selbst, konkret die Direktiven LimitNOFILE und LimitNPROC im [Service]-Abschnitt. Statt die vom Paketmanager verwaltete Unit-Datei direkt zu editieren, was bei einem Paket-Update überschrieben würde, erstellt man mit systemctl edit php8.4-fpm.service ein Override-Fragment, das systemd automatisch zusammen mit der Original-Unit lädt.

Nach jeder Aenderung an einer Unit-Datei oder einem Override muss systemctl daemon-reload ausgeführt werden, damit systemd die neue Konfiguration einliest, gefolgt von einem Neustart des betroffenen Dienstes. Mit systemctl show php8.4-fpm.service -p LimitNOFILE lässt sich prüfen, welcher Wert tatsächlich aktiv ist, und mit cat /proc/$(pgrep -f 'php-fpm: master')/limits sieht man das Limit direkt aus Sicht des laufenden Prozesses, was der zuverlässigste Nachweis ist, dass die Konfiguration tatsächlich greift.


# /etc/systemd/system/php8.4-fpm.service.d/override.conf
# Created via: systemctl edit php8.4-fpm.service
[Service]
LimitNOFILE=65536
LimitNPROC=4096

# Same pattern for Nginx
# /etc/systemd/system/nginx.service.d/override.conf
[Service]
LimitNOFILE=65536

# Apply the change:
# systemctl daemon-reload
# systemctl restart php8.4-fpm nginx
# systemctl show php8.4-fpm.service -p LimitNOFILE

6. Too many open files diagnostizieren und beheben

Die Fehlermeldung Too many open files beziehungsweise der Kernel-seitige Errno-Code EMFILE bedeutet, dass ein Prozess sein individuelles File-Descriptor-Limit erreicht hat, nicht dass dem System selbst die Ressourcen ausgegangen sind. Der erste Diagnoseschritt ist, die betroffene Prozess-ID zu ermitteln, etwa aus dem PHP-FPM- oder Nginx-Error-Log, und anschließend mit cat /proc/PID/limits das tatsächlich aktive Soft- und Hard-Limit für Max open files auszulesen. Mit ls /proc/PID/fd | wc -l zählt man die aktuell geöffneten File-Descriptors dieses Prozesses und vergleicht den Wert direkt mit dem Limit.

Ergänzend zeigt lsof -p PID im Detail, welche Dateien, Sockets und Pipes ein Prozess offen hält, was bei einem File-Descriptor-Leck häufig verrät, dass eine Datenbankverbindung oder ein Socket nie geschlossen wird. Systemweit zeigt lsof | wc -l die Gesamtzahl offener Deskriptoren aller Prozesse und lässt sich mit dem Kernel-weiten Limit aus cat /proc/sys/fs/file-nr vergleichen. Steigt die Anzahl offener Deskriptoren eines einzelnen Prozesses über die Zeit kontinuierlich an, ohne je zu sinken, liegt fast immer ein Leck im Anwendungscode vor, das durch eine Limit-Erhöhung nur hinausgezögert, nicht behoben wird.


#!/usr/bin/env bash
# Diagnose an EMFILE / Too many open files incident
set -euo pipefail

PID=$(pgrep -f 'php-fpm: pool www' | head -n1)

echo "--- Configured limits for PID $PID ---"
grep -i "open files" "/proc/${PID}/limits"

echo "--- Currently open file descriptors ---"
ls "/proc/${PID}/fd" | wc -l

echo "--- Top 10 open file/socket types ---"
lsof -p "$PID" | awk '{print $5}' | sort | uniq -c | sort -rn | head -10

echo "--- System-wide open file handles vs. kernel limit ---"
cat /proc/sys/fs/file-nr

7. Nginx, PHP-FPM und Container-Workloads richtig dimensionieren

Nginx bringt mit worker_rlimit_nofile eine eigene Konfigurationsdirektive mit, die das Prozesslimit überschreibt, sofern der Wert das systemd-Hard-Limit nicht übersteigt. Diese Direktive ist sinnvoll, weil sie die Grenze direkt im Anwendungskontext dokumentiert, ersetzt aber nicht die systemd-Konfiguration, sondern ergänzt sie: Nginx kann sein Soft-Limit nur bis zum vom Betriebssystem vorgegebenen Hard-Limit anheben. Bei PHP-FPM gilt eine zusätzliche Faustregel: Die Anzahl der Worker aus pm.max_children multipliziert mit den durchschnittlich pro Worker offenen Verbindungen sollte das konfigurierte LimitNOFILE nie ausreizen, sondern mit deutlichem Puffer darunter bleiben.

In containerisierten Umgebungen gelten dieselben Kernel-Mechanismen, aber die Konfiguration erfolgt über die Container-Runtime statt über systemd auf dem Host. Docker setzt ohne explizite Angabe oft ein zu niedriges Default-Limit, weshalb Limits entweder im docker-compose.yml über den ulimits-Schlüssel oder direkt beim docker run-Aufruf mit --ulimit nofile=65536:65536 gesetzt werden sollten. Wichtig ist, dass ein im Container gesetztes Limit niemals das Host-Hard-Limit übersteigen kann, weil Container-Prozesse letztlich Prozesse des Host-Kernels bleiben.


# docker-compose.yml: raise file descriptor and process limits per service
services:
  php-fpm:
    image: mironsoft/php:8.4-fpm
    ulimits:
      nofile:
        soft: 65536
        hard: 131072
      nproc: 4096
    nginx:
      condition: service_healthy

  nginx:
    image: nginx:1.27
    ulimits:
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro

8. Kernel-weite Grenzen: fs.file-max und PAM-Limits

Ueber allen prozessspezifischen ulimit-Werten liegt eine systemweite Obergrenze, die der Kernel-Parameter fs.file-max festlegt: die maximale Anzahl gleichzeitig geöffneter Dateien über alle Prozesse des gesamten Systems hinweg. Dieser Wert ist auf modernen Systemen mit ausreichend RAM meist großzügig voreingestellt, kann aber auf kleineren Cloud-Instanzen oder Containern mit begrenztem Speicher zu niedrig ausfallen. Mit cat /proc/sys/fs/file-max lässt sich der aktuelle Wert prüfen, und mit cat /proc/sys/fs/file-nr sieht man die aktuell genutzte, die zugewiesene und die maximale Anzahl im Vergleich.

Eine dauerhafte Anpassung erfolgt über /etc/sysctl.conf oder eine eigene Datei unter /etc/sysctl.d/, gefolgt von sysctl -p, um die Aenderung ohne Neustart zu aktivieren. Wichtig ist die Rangfolge der Limits: Ein einzelner Prozess kann niemals mehr Dateien öffnen, als sein eigenes RLIMIT_NOFILE erlaubt, selbst wenn fs.file-max theoretisch noch Kapazität hätte, und umgekehrt bremst ein zu niedriges fs.file-max alle Prozesse gemeinsam, unabhängig von deren individuell gesetzten, höheren ulimit-Werten. Beide Ebenen müssen deshalb konsistent aufeinander abgestimmt sein, sonst bleibt eine großzügig gesetzte Prozessgrenze wirkungslos.

9. ulimit-Konfiguration im Vergleich

Je nachdem, wie ein Dienst gestartet wird und welches Werkzeug zur Limit-Konfiguration genutzt wird, entscheidet sich, ob eine Grenze tatsächlich greift oder stillschweigend ignoriert wird. Die folgende Uebersicht fasst die häufigsten Fehlkonfigurationen und die jeweils korrekte Vorgehensweise zusammen.

Szenario Problem Empfohlene Massnahme Effekt
Manuell per ulimit in der Shell Gilt nur für die aktuelle Session limits.conf oder systemd-Override Uebersteht Neustart und Reboot
Dienst wird von systemd gestartet limits.conf wird komplett ignoriert LimitNOFILE in Unit-Override Limit greift direkt beim Prozessstart
pm.max_children überschreitet ulimit -u Worker-Spawn schlägt sporadisch fehl nproc-Limit an Worker-Anzahl anpassen Keine fehlenden Prozesse unter Last
Docker-Container ohne ulimits-Angabe Zu niedriges Default-FD-Limit --ulimit nofile=65536:65536 Konsistentes Limit über alle Container
Monitoring nur CPU und RAM FD-Erschöpfung bleibt unbemerkt /proc/PID/limits als Alert-Metrik EMFILE-Fehler vor Ausfall erkannt

Auffällig ist, dass fast jede Zeile der Tabelle auf denselben Grundfehler zurückführt: ein Limit wird an der falschen Stelle gesetzt und deshalb vom tatsächlich laufenden Prozess niemals gelesen. Wer Limits konsequent über systemd-Overrides für Dienste und limits.conf für interaktive Logins trennt und beide Ebenen regelmäßig mit /proc/PID/limits verifiziert, vermeidet den kompletten Fehlerkatalog aus dieser Uebersicht.

Mironsoft

Server-Administration, Kapazitätsplanung und Magento-Infrastruktur

Ressourcen-Limits einmal richtig konfiguriert?

Wir analysieren eure PHP-FPM- und Nginx-Konfiguration, setzen File-Descriptor- und Prozess-Limits über systemd-Overrides sauber und persistent auf und richten Monitoring ein, damit Too many open files nie wieder unerwartet die Produktion trifft.

Limit-Audit

Soft- und Hard-Limits aller produktiven Dienste prüfen und dokumentieren

systemd-Konfiguration

LimitNOFILE und LimitNPROC als saubere Unit-Overrides einrichten

FD-Monitoring

Frühwarnung vor EMFILE-Fehlern in Grafana und Alertmanager

10. Zusammenfassung

Die wichtigste Erkenntnis rund um System-Ressourcen-Limits mit ulimit: Soft- und Hard-Limits sind zwei getrennte Werte, und wo ein Limit gesetzt wird, entscheidet, ob es beim tatsächlichen Prozessstart überhaupt wirkt. ulimit in der Shell gesetzt gilt nur für die aktuelle Session, /etc/security/limits.conf nur für PAM-gesteuerte Logins, und Dienste, die direkt von systemd gestartet werden, ignorieren beide vollständig, wenn kein LimitNOFILE oder LimitNPROC in der Unit-Datei gesetzt ist. Genau diese Verwechslung ist die häufigste Ursache dafür, dass ein scheinbar korrekt gesetztes Limit in der Produktion trotzdem nicht ankommt.

Die Fehlermeldung Too many open files lässt sich mit /proc/PID/limits, lsof -p PID und einem Vergleich gegen fs.file-max in wenigen Minuten sicher diagnostizieren, statt raten zu müssen, ob es sich um einen Kapazitäts- oder einen Konfigurationsfehler handelt. Wer systemd-Overrides für alle produktiven Dienste konsequent nutzt, PHP-FPM- und Nginx-Limits an die tatsächliche Worker-Anzahl anpasst und File-Descriptor-Nutzung als eigene Monitoring-Metrik neben CPU und RAM etabliert, verhindert, dass diese Fehlermeldung unter Last überhaupt erst auftritt.

System-Ressourcen-Limits mit ulimit, das Wichtigste auf einen Blick

Soft vs. Hard

Das Soft-Limit ist der aktive Wert, das Hard-Limit die Obergrenze. Prozesse dürfen das Soft-Limit selbst bis zum Hard-Limit erhöhen.

limits.conf vs. systemd

limits.conf wirkt nur bei PAM-Logins. Von systemd gestartete Dienste brauchen zwingend LimitNOFILE und LimitNPROC im Unit-Override.

Too many open files

Mit /proc/PID/limits und lsof -p PID in Minuten diagnostizierbar. Ein ständig steigender FD-Zähler deutet auf ein Leck im Code hin.

Kernel-weites Limit

fs.file-max begrenzt alle Prozesse gemeinsam und muss zum Summenbedarf aller Dienste konsistent passen.

11. FAQ: System-Ressourcen-Limits mit ulimit

1Was ist der Unterschied zwischen Soft- und Hard-Limit?
Das Soft-Limit ist der aktive Wert, den ein Prozess selbst bis zum Hard-Limit erhöhen kann. Das Hard-Limit ist die absolute, nur von root änderbare Obergrenze.
2Wie zeige ich alle aktuellen ulimit-Werte an?
Mit ulimit -a für die Uebersicht, mit ulimit -Sn und ulimit -Hn gezielt getrennt für Soft- und Hard-Limit offener Dateien.
3Warum wirkt sich ulimit -n auf PHP-FPM und Nginx aus?
Jede Verbindung, Datei und jeder Socket belegt einen File-Descriptor. Bei hoher Last erreichen Worker schnell das Standardlimit von 1024 und schlagen mit EMFILE fehl.
4Wie setze ich Limits dauerhaft für einen Benutzer?
Ueber /etc/security/limits.conf. Wirkt aber nur bei PAM-gesteuerten Logins, nicht bei direkt von systemd gestarteten Diensten.
5Warum greift limits.conf bei systemd-Services oft nicht?
systemd startet Dienste beim Booten ohne den PAM-Login-Pfad zu durchlaufen. Limits müssen deshalb direkt in der Unit-Datei gesetzt werden.
6Wie setze ich Limits für einen systemd-Service korrekt?
Mit systemctl edit servicename ein Override mit LimitNOFILE und LimitNPROC anlegen, dann systemctl daemon-reload und den Dienst neu starten.
7Wie erkenne ich Too many open files als Ursache?
Ueber die Fehlermeldung und den Errno-Code EMFILE. Mit /proc/PID/limits das Limit und mit ls /proc/PID/fd | wc -l die aktuell offenen Deskriptoren prüfen.
8Welche Rolle spielt fs.file-max im Vergleich zu ulimit -n?
fs.file-max ist ein systemweites Limit über alle Prozesse, ulimit -n gilt pro Prozess. Beide müssen konsistent zueinander passen.
9Wie setze ich Ulimits in Docker-Containern?
Ueber den ulimits-Schlüssel in docker-compose.yml oder --ulimit nofile=65536:65536 bei docker run. Kann das Host-Hard-Limit nie übersteigen.
10Was ist der Unterschied zwischen ulimit -u und pm.max_children?
ulimit -u begrenzt Prozesse und Threads auf Kernel-Ebene. pm.max_children steuert die PHP-FPM-Worker-Anzahl. Liegt Letzteres darüber, schlägt das Worker-Spawnen fehl.