ulimit und Ressourcenlimits in Bash-Skripten steuern
AI generated
$_
#!/
Bash · Linux · Ressourcenlimits
ulimit und Ressourcenlimits in Bash
Soft- und Hard-Limits gezielt für Deployment-Skripte steuern

ulimit steuert, wie viele Dateien, Prozesse und wie viel virtuellen Speicher ein Prozess nutzen darf, mit einem klaren Zusammenspiel aus Soft- und Hard-Limit. Wer die typische Ursache von too-many-open-files-Fehlern in Deployment-Skripten kennt, setzt Limits gezielt für die Skript-Laufzeit oder dauerhaft über systemd, statt Fehler erst in der Produktion zu entdecken.

17 Min. Lesezeit ulimit -n · -u · -v Soft/Hard-Limits · systemd

1. Was ulimit ist: Soft-Limits, Hard-Limits und ihr Zusammenspiel

ulimit ist ein in Bash eingebautes Kommando, das die vom Kernel pro Prozess durchgesetzten Ressourcengrenzen liest und setzt, etwa die maximale Anzahl offener Dateien oder die maximale Prozessanzahl eines Benutzers. Jede dieser Grenzen existiert in Bash immer als Paar: ein Soft-Limit, das der aktuell wirksame, durchgesetzte Wert ist, und ein Hard-Limit, das die absolute Obergrenze markiert, bis zu der ein unprivilegierter Prozess das Soft-Limit selbst anheben darf.

Ein Prozess ohne besondere Rechte kann sein eigenes Soft-Limit jederzeit senken und bis maximal zum Hard-Limit wieder erhöhen, niemals aber über das Hard-Limit hinaus, und das Hard-Limit selbst kann ohne Root-Rechte nur gesenkt, nie erhöht werden. Dieses Zusammenspiel erklärt, warum ein Deployment-Skript, das versucht, ulimit -n 100000 zu setzen, mit einer Fehlermeldung scheitert, wenn das systemweite Hard-Limit niedriger liegt, selbst wenn das Skript mit vollen Root-Rechten läuft, solange kein systemweites Limit angehoben wurde.

2. ulimit -n: offene Dateideskriptoren und die Ursache von too many open files

ulimit -n begrenzt die Anzahl der Dateideskriptoren, die ein einzelner Prozess gleichzeitig offen halten darf, wobei jede geöffnete Datei, jeder Socket und jede Pipe jeweils einen Deskriptor beansprucht. Überschreitet ein Prozess dieses Limit, verweigert der Kernel jeden weiteren open()- oder socket()-Aufruf mit dem Fehler EMFILE, der sich in Anwendungslogs typischerweise als "Too many open files" zeigt.

In Deployment-Skripten trifft dieser Fehler besonders häufig Webserver oder Datenbankprozesse, die viele gleichzeitige Verbindungen offen halten und dabei an ein zu niedriges, oft aus einer veralteten Distributions-Voreinstellung von 1024 stammendes Soft-Limit stoßen. Die kurzfristige Lösung im Skript selbst ist ulimit -n 65536 vor dem eigentlichen Start des Dienstes, vorausgesetzt das Hard-Limit erlaubt diesen Wert, andernfalls muss zuerst das Hard-Limit systemweit angehoben werden.


# Check current limits before starting the service
echo "Soft limit (open files): $(ulimit -Sn)"
echo "Hard limit (open files): $(ulimit -Hn)"

# Raise the soft limit up to the hard limit for this process
ulimit -n 65536 || {
  echo "Konnte Dateideskriptor-Limit nicht erhoehen" >&2
  exit 1
}

exec ./my-webserver

3. ulimit -u: die maximale Prozessanzahl pro Nutzer begrenzen

ulimit -u legt fest, wie viele Prozesse und Threads ein Benutzer gleichzeitig starten darf, wobei Threads unter Linux aus Kernel-Sicht als eigene Einträge zählen, was dieses Limit bei stark multithreaded Anwendungen deutlich schneller erreicht, als der reine Prozessname vermuten lässt. Wird das Limit erreicht, schlägt jeder weitere fork()-Aufruf fehl, ein Deployment-Skript kann dann selbst keine weiteren Hintergrundprozesse mehr starten.

Besonders tückisch ist dieses Limit, wenn es unabhängig für einen Service-Account gesetzt wird, der mehrere parallele Worker-Prozesse startet: Ein zu niedriges ulimit -u führt dann nicht zu einem sofortigen, klaren Fehler beim Deployment selbst, sondern zu einem scheinbar zufälligen Scheitern einzelner Worker, sobald die Gesamtzahl aller laufenden Threads des Accounts das Limit überschreitet, oft erst unter Produktionslast sichtbar.


echo "Aktuelles Prozesslimit: $(ulimit -u)"

if (( $(ulimit -u) < 4096 )); then
  echo "WARNUNG: Prozesslimit unter 4096, Worker koennten scheitern" >&2
fi

4. ulimit -v: virtuellen Speicher pro Prozess begrenzen

ulimit -v begrenzt den maximal reservierbaren virtuellen Speicher eines Prozesses in Kilobyte, nicht den tatsächlich genutzten physischen Speicher. Da moderne Anwendungen, insbesondere solche mit Java- oder Node.js-Laufzeit, häufig deutlich mehr virtuellen Adressraum reservieren als sie tatsächlich physisch nutzen, führt ein zu knapp bemessenes ulimit -v oft zu einem Absturz der Anwendung schon beim Start, lange bevor echter Speicherdruck entsteht.

Für Deployment-Skripte, die einen Prozess bewusst vor exzessivem Speicherverbrauch schützen wollen, ist ulimit -v deshalb meist das falsche Werkzeug, weil es virtuellen statt physischen Speicher begrenzt. Ein präziseres Werkzeug für dieses Ziel ist eine cgroup-basierte Speichergrenze über systemd, während ulimit -v in der Praxis vor allem dort sinnvoll bleibt, wo ein bewusst sehr restriktives Sandboxing für kleine, bekannte Skripte gewünscht ist.


# Deliberately restrictive sandbox for a small trusted script
( ulimit -v 262144  # 256 MB virtual memory
  exec ./small-batch-job.sh )

5. Limits nur für die Skript-Laufzeit setzen: das Subshell-Pattern

ulimit-Aufrufe innerhalb eines laufenden Bash-Skripts wirken standardmäßig auf den aktuellen Prozess und werden an alle folgenden Kindprozesse vererbt, bleiben aber vollständig lokal auf die aktuelle Shell-Sitzung begrenzt und verschwinden, sobald das Skript beendet ist. Wer ein Limit nur für einen einzelnen Teilschritt eines größeren Skripts ändern will, sollte diesen Teilschritt deshalb bewusst in eine Subshell mit runden Klammern auslagern.

Innerhalb der Subshell gesetzte Limits gelten ausschließlich für diese Subshell und die von ihr gestarteten Kindprozesse, das umgebende Skript und alle danach folgenden Schritte behalten ihre ursprünglichen Limits unverändert. Dieses Muster ist besonders nützlich, wenn ein Deployment-Skript einen einzelnen, potenziell speicherhungrigen Build-Schritt bewusst begrenzen will, ohne die Limits für den Rest der Deployment-Pipeline zu beeinflussen.


echo "Vor der Subshell: $(ulimit -v)"

(
  ulimit -v 1048576   # nur in dieser Subshell: 1 GB
  ./memory-intensive-build.sh
)

echo "Nach der Subshell: $(ulimit -v)"   # unveraendert

6. Limits dauerhaft setzen: limits.conf im Vergleich zu systemd-Unit-Dateien

Ein im Skript per ulimit gesetztes Limit gilt immer nur für die aktuelle Prozessbaum-Instanz und geht mit dem Skriptende verloren. Für dauerhafte, systemweite oder benutzerspezifische Grenzen, die bei jedem neuen Login oder Dienststart automatisch gelten sollen, ist /etc/security/limits.conf der klassische Ort, an dem PAM-basierte Logins ihre Limits beziehen, konfigurierbar pro Benutzer, Gruppe oder mit einem Wildcard-Eintrag für alle.

Für Dienste, die über systemd gestartet werden, greift limits.conf jedoch häufig nicht, weil systemd-Dienste PAM-Login-Sessions umgehen. Der zuverlässigere Weg ist hier LimitNOFILE, LimitNPROC oder LimitAS direkt in der systemd-Unit-Datei des Dienstes zu setzen, weil systemd diese Direktiven unabhängig von limits.conf beim Start des Dienstes selbst durchsetzt und damit konsistent für jeden Neustart gilt, egal wer den Dienst auslöst.

7. Typische Probleme in Deployment-Skripten durch zu niedrige Limits

Das häufigste Symptom eines zu niedrigen Limits in produktiven Deployment-Skripten ist ein Dienst, der lokal beim Entwickeln problemlos startet, in der Produktionsumgebung aber sporadisch mit "Too many open files" oder "Resource temporarily unavailable" abstürzt, weil die Produktionsumgebung eine restriktivere Standardkonfiguration mitbringt als die lokale Entwicklungsmaschine, auf der oft großzügigere Distributions-Defaults gelten.

Ein zweites häufiges Problem entsteht, wenn ein Deployment-Skript Limits per ulimit im eigenen Prozess erhöht, den eigentlichen Dienst aber über systemd oder einen Prozessmanager startet, der eine eigene, unabhängige Umgebung mit eigenen Limits aufbaut. Die im Deployment-Skript gesetzten ulimit-Werte werden dann schlicht ignoriert, weil sie nur für den kurzlebigen Deployment-Prozess selbst galten, nicht für den tatsächlich langlebigen Dienstprozess.

8. Limits prüfen und diagnostizieren: ulimit -a und /proc/[pid]/limits

ulimit -a gibt eine vollständige Übersicht aller aktuell wirksamen Limits der laufenden Shell aus, praktisch für eine schnelle manuelle Prüfung während der Entwicklung eines Deployment-Skripts. Für die Diagnose eines bereits laufenden, fremden Prozesses ist dagegen /proc/[pid]/limits die richtige Quelle, weil sie sowohl Soft- als auch Hard-Limits für jede Ressource getrennt ausgibt, unabhängig davon, in welcher Shell oder ohne Shell der Prozess ursprünglich gestartet wurde.

Ein Diagnose-Skript, das prüfen soll, ob ein bereits laufender Produktionsdienst tatsächlich mit den erwarteten Limits gestartet wurde, liest deshalb /proc/[pid]/limits statt sich auf die Limits der eigenen, diagnostizierenden Shell zu verlassen, die typischerweise ganz andere Werte hat als der überwachte Zieldienst.


# Full overview in the current shell
ulimit -a

# Limits of a specific running process
pid=$(pgrep -f "my-webserver" | head -n1)
grep -E "open files|processes" "/proc/$pid/limits"

9. Best Practices für produktive Deployment-Skripte

Ein sauberes Deployment-Skript prüft benötigte Limits explizit, bevor der eigentliche Dienst gestartet wird, und bricht mit einer klaren Fehlermeldung ab, statt den Dienst mit unzureichenden Limits blind zu starten und den Fehler erst im Betrieb sichtbar werden zu lassen. Diese Prüfung sollte immer sowohl das Soft- als auch das Hard-Limit einbeziehen, weil nur das Hard-Limit verlässlich zeigt, wie weit sich das Soft-Limit ohne zusätzliche Rechte überhaupt anheben lässt.

Für langlebige, dauerhaft laufende Dienste gehören Limits konsequent in die systemd-Unit-Datei statt in ein einmalig ausgeführtes Deployment-Skript, weil systemd diese Limits bei jedem Neustart des Dienstes zuverlässig neu durchsetzt, unabhängig davon, wann und wie das Deployment-Skript zuletzt gelaufen ist. ulimit im Skript bleibt damit vor allem für kurzlebige Build- und Batch-Schritte das richtige Werkzeug.

Limit Flag Typisches Problem Empfehlung
Offene Dateien ulimit -n Too many open files bei vielen Verbindungen Vor Dienststart prüfen, LimitNOFILE in systemd setzen
Prozesse/Threads ulimit -u fork() schlägt fehl bei vielen Workern LimitNPROC in systemd, nicht nur im Deployment-Skript
Virtueller Speicher ulimit -v Absturz bei Java/Node trotz genug RAM cgroup-Limits statt ulimit -v für physischen Speicher
Nur für Laufzeit Subshell ( ) Globale Limits versehentlich verändert Kritische Schritte immer in Subshell kapseln

Mironsoft

Shell-Automatisierung, DevOps-Tooling und Deployment-Infrastruktur

Shell-Skripte, die in der Produktion zuverlässig laufen?

Wir analysieren bestehende Bash-Skripte, erkennen fragile Muster und ersetzen sie durch robuste Bash-Patterns: mit vollständiger Fehlerbehandlung, Logging und sicherer Parallelisierung für euren Deployment-Stack.

Code-Review

ShellCheck-Analyse und manuelle Prüfung auf kritische Bash-Pattern-Verstöße.

Refactoring

Fehlerbehandlung, Logging und sichere Dateioperationen nachrüsten.

CI-Integration

ShellCheck und BATS in Pipelines integrieren und Regressionstests aufbauen.

10. Zusammenfassung

ulimit in Bash: Das Wichtigste auf einen Blick

Soft vs. Hard

Soft-Limit ist der aktuell wirksame Wert, Hard-Limit die Obergrenze, bis zu der ein Prozess selbst erhöhen darf.

ulimit -n

Häufigste Fehlerquelle in Deployment-Skripten, Standardwert 1024 reicht für viele gleichzeitige Verbindungen nicht.

Subshell für Scope

Limits nur für einen Teilschritt setzen, ohne den Rest des Skripts zu beeinflussen, mit runden Klammern kapseln.

Dauerhaft via systemd

Für laufende Dienste gehören Limits in LimitNOFILE/LimitNPROC der Unit-Datei, nicht ins Deployment-Skript.

11. FAQ: ulimit in Bash: Das Wichtigste auf einen Blick

1Was ist der Unterschied zwischen Soft-Limit und Hard-Limit bei ulimit?
Das Soft-Limit ist der aktuell durchgesetzte Wert, das Hard-Limit die absolute Obergrenze. Ein unprivilegierter Prozess kann sein Soft-Limit jederzeit bis maximal zum Hard-Limit anheben, aber niemals darüber hinaus.
2Warum bekomme ich too many open files, obwohl mein Server wenig Last hat?
Das Soft-Limit für offene Dateien liegt oft bei einer veralteten Distributions-Voreinstellung von 1024. Viele gleichzeitige Verbindungen, auch bei geringer Last insgesamt, können dieses Limit schnell erreichen.
3Wie erhöhe ich das Limit für offene Dateien nur für mein Skript?
Mit ulimit -n vor dem Start des Dienstes im selben Skript, vorausgesetzt der neue Wert liegt unter oder gleich dem Hard-Limit. Andernfalls muss das Hard-Limit zuerst systemweit angehoben werden.
4Begrenzt ulimit -v den physischen Speicherverbrauch eines Prozesses?
Nein, ulimit -v begrenzt den virtuellen Adressraum, nicht den tatsächlich genutzten physischen Speicher. Für physische Speichergrenzen sind cgroup-basierte Limits das passendere Werkzeug.
5Wie setze ich ein Limit nur für einen Teilschritt meines Skripts?
Indem der Teilschritt in eine Subshell mit runden Klammern gekapselt wird. Ein dort gesetztes ulimit gilt nur innerhalb dieser Subshell und wirkt sich nicht auf den Rest des Skripts aus.
6Warum wirkt mein per ulimit gesetztes Limit nicht auf den systemd-Dienst?
Weil systemd-Dienste PAM-Login-Sessions und damit limits.conf umgehen. Für systemd-Dienste müssen Limits direkt als LimitNOFILE oder LimitNPROC in der Unit-Datei gesetzt werden.
7Wo prüfe ich die Limits eines bereits laufenden fremden Prozesses?
In /proc/[pid]/limits. Diese Datei zeigt Soft- und Hard-Limits für jede Ressource unabhängig davon, in welcher Shell der Prozess ursprünglich gestartet wurde.
8Kann ich das Hard-Limit ohne Root-Rechte erhöhen?
Nein, ein unprivilegierter Prozess kann das Hard-Limit nur senken, niemals erhöhen. Eine Erhöhung des Hard-Limits erfordert Root-Rechte oder eine systemweite Konfiguration.
9Warum scheitern nur einzelne Worker-Prozesse statt das ganze Deployment?
Weil ulimit -u die Gesamtzahl aller Threads und Prozesse eines Benutzers begrenzt. Erst wenn diese Gesamtzahl unter Produktionslast das Limit erreicht, scheitern weitere fork()-Aufrufe scheinbar zufällig.
10Sollte ich Limits im Deployment-Skript oder in der systemd-Unit-Datei setzen?
Für langlebige Dienste gehören Limits in die systemd-Unit-Datei, weil sie bei jedem Neustart zuverlässig neu durchgesetzt werden. ulimit im Skript eignet sich vor allem für kurzlebige Build- und Batch-Schritte.