von /etc/profile bis zur eigenen .bashrc
Wer schon einmal eine Umgebungsvariable in einer SSH-Sitzung gesetzt hat, die im Cron-Job plötzlich fehlte, kennt das Problem: Bash lädt je nach Kontext völlig unterschiedliche Startdateien. Die Ladereihenfolge von profile, bash_profile und bashrc entscheidet, welche Konfiguration überhaupt wirksam wird, und genau dieses Verständnis fehlt vielen Administratoren.
Inhaltsverzeichnis
- 1. Warum die Ladereihenfolge so oft verwirrt
- 2. Login-Shell und Non-Login-Shell: der zentrale Unterschied
- 3. /etc/profile und /etc/profile.d: die systemweite Ebene
- 4. Die Login-Kette: bash_profile, bash_login und profile
- 5. .bashrc: die interaktive Non-Login-Shell
- 6. Non-interaktive Shells und BASH_ENV
- 7. SSH, Cron und systemd in der Praxis
- 8. Typische Fehler: verlorene PATH-Einträge und doppelte Ausgaben
- 9. Ladereihenfolge debuggen und Vergleichstabelle
- 10. Zusammenfassung
- 11. FAQ
1. Warum die Ladereihenfolge so oft verwirrt
Kaum ein Thema unter Linux sorgt so zuverlässig für Verwirrung wie die Ladereihenfolge der Bash-Startdateien. Ein Administrator setzt eine Umgebungsvariable in der .bashrc, öffnet ein neues Terminal, und die Variable ist da. Derselbe Cron-Job, der dieselbe Variable braucht, scheitert am nächsten Morgen mit einer kryptischen Fehlermeldung. Der Grund ist simpel und gleichzeitig selten dokumentiert: Bash entscheidet anhand des Aufrufkontexts, welche Datei sie überhaupt liest, und Cron ruft die Shell auf eine Art auf, die .bashrc komplett ignoriert.
Die Ladereihenfolge ist keine willkürliche Designentscheidung, sondern folgt einer klaren Logik, die sich an zwei Fragen orientiert: Ist die Shell eine Login-Shell, und ist sie interaktiv. Aus der Kombination dieser beiden Eigenschaften ergibt sich exakt, welche Dateien in welcher Reihenfolge eingelesen werden. Wer diese Logik einmal verinnerlicht hat, findet Konfigurationsfehler in Sekunden, statt stundenlang zu raten, warum eine Variable "manchmal" da ist und manchmal nicht.
2. Login-Shell und Non-Login-Shell: der zentrale Unterschied
Eine Login-Shell entsteht, wenn man sich frisch am System anmeldet: über eine physische Konsole, per ssh benutzer@server ohne Kommando, oder mit bash --login. Eine Non-Login-Shell entsteht dagegen, wenn man aus einer bereits laufenden Sitzung heraus ein neues Terminal öffnet, etwa in einem grafischen Desktop, oder wenn man bash ohne weitere Optionen aufruft. Diese Unterscheidung ist der wichtigste Hebel für die gesamte Ladereihenfolge, denn Login-Shells und Non-Login-Shells lesen komplett unterschiedliche Dateisätze.
Eine zweite, unabhängige Achse ist die Frage, ob eine Shell interaktiv ist, also mit einem Prompt auf Eingaben wartet, oder nicht interaktiv läuft, wie es bei Skripten, Cron-Jobs und systemd-Diensten der Fall ist. Zusammen ergeben Login-Status und Interaktivität vier mögliche Kombinationen, und für jede dieser vier Kombinationen liest Bash einen anderen Satz an Startdateien. Genau hier liegt die Ursache, warum ein Skript, das per Cron läuft, andere Umgebungsvariablen sieht als dasselbe Skript, wenn man es manuell im Terminal startet.
# Determine whether the current shell is a login shell
shopt -q login_shell && echo "login shell" || echo "not a login shell"
# Determine whether the current shell is interactive
case $- in
*i*) echo "interactive" ;;
*) echo "not interactive" ;;
esac
# Combine both checks in a small diagnostic script
cat <<'EOF' > /tmp/shell-context.sh
#!/usr/bin/env bash
if shopt -q login_shell; then
login="login"
else
login="non-login"
fi
case $- in
*i*) interactive="interactive" ;;
*) interactive="non-interactive" ;;
esac
echo "Context: $login, $interactive shell (PID $$)"
EOF
chmod +x /tmp/shell-context.sh
3. /etc/profile und /etc/profile.d: die systemweite Ebene
Bei jeder Login-Shell liest Bash zuerst die systemweite Datei /etc/profile, bevor überhaupt eine benutzerspezifische Datei zum Zug kommt. Diese Datei ist der richtige Ort für Einstellungen, die für alle Benutzer eines Servers gelten sollen, etwa einen systemweiten PATH-Zusatz oder eine globale Umask. Auf den meisten modernen Distributionen bindet /etc/profile zusätzlich alle Dateien aus dem Verzeichnis /etc/profile.d/*.sh ein, was die Wartung erheblich vereinfacht, weil jedes Paket seine eigene Konfigurationsdatei ablegen kann, ohne die zentrale Datei zu verändern.
Diese modulare Struktur erklärt, warum etwa nach der Installation von rvm oder einer PHP-Version plötzlich neue Umgebungsvariablen in jeder Login-Shell verfügbar sind: Das Installationsskript legt einfach eine neue Datei in /etc/profile.d/ ab. Wichtig ist, dass /etc/profile nur bei Login-Shells gelesen wird. Öffnet man in einem laufenden Desktop nur ein neues Terminalfenster, wird diese Datei in aller Regel nicht erneut ausgeführt, denn dieses neue Fenster ist typischerweise keine Login-Shell.
4. Die Login-Kette: bash_profile, bash_login und profile
Nach /etc/profile sucht Bash in einer Login-Shell nach genau einer der folgenden drei Dateien im Home-Verzeichnis des Benutzers, und zwar in dieser festen Reihenfolge: zuerst ~/.bash_profile, dann ~/.bash_login, und erst wenn keine der beiden existiert, ~/.profile. Sobald eine dieser Dateien gefunden wird, liest Bash nur diese eine und ignoriert die restlichen. Das führt in der Praxis oft zu einem Fallstrick: Legt man eine ~/.bash_profile an, wird eine bereits vorhandene ~/.profile von Bash komplett übergangen, was Einstellungen aus dieser Datei unwirksam macht, ohne dass eine Fehlermeldung erscheint.
Die gängige Praxis auf den meisten Systemen ist deshalb, gar keine eigene ~/.bash_profile anzulegen, sondern in ~/.profile zu arbeiten, weil diese Datei zusätzlich von POSIX-kompatiblen Shells wie dash gelesen wird. Wer Bash-spezifische Konfiguration in der Login-Kette benötigt, legt stattdessen eine minimale ~/.bash_profile an, die lediglich ~/.bashrc nachlädt. Damit greifen Login-Shell und Non-Login-Shell auf dieselbe zentrale Konfigurationsdatei zu, und die Wartung bleibt an einer einzigen Stelle gebündelt.
# ~/.bash_profile — minimal pattern: delegate to .bashrc
# Ensures a login shell picks up the same interactive settings
if [ -f "$HOME/.bashrc" ]; then
source "$HOME/.bashrc"
fi
# Login-only additions still belong here, not in .bashrc
export PATH="$HOME/.local/bin:$PATH"
umask 022
5. .bashrc: die interaktive Non-Login-Shell
Die Datei ~/.bashrc ist ausschließlich für interaktive Non-Login-Shells zuständig, also für neue Terminalfenster, Tabs in einem Terminal-Multiplexer oder Sitzungen, die aus einer bereits laufenden Shell heraus mit bash gestartet werden. Hier gehören Aliase, die Prompt-Konfiguration (PS1), die History-Einstellungen und Shell-Optionen wie shopt hin, weil diese Dinge nur in interaktiven Sitzungen überhaupt einen Effekt haben. Umgebungsvariablen, die auch in nicht interaktiven Kontexten verfügbar sein müssen, sollten hingegen nicht ausschließlich in .bashrc gesetzt werden.
Ein verbreitetes Missverständnis ist die Annahme, .bashrc würde bei jeder Bash-Sitzung ausnahmslos gelesen. Tatsächlich wird sie bei einer reinen Login-Shell ohne den oben gezeigten Nachlade-Trick gar nicht automatisch geladen, was erklärt, warum Aliase, die man in .bashrc definiert hat, nach einem frischen SSH-Login manchmal fehlen, aber nach dem Öffnen eines zusätzlichen Terminals plötzlich vorhanden sind.
6. Non-interaktive Shells und BASH_ENV
Für nicht interaktive Shells, also solche, die ein Skript ausführen, ohne auf einen Prompt zu warten, liest Bash standardmäßig gar keine der bisher genannten Dateien. Stattdessen prüft Bash die Umgebungsvariable BASH_ENV, und falls diese gesetzt ist, wird die dort referenzierte Datei geladen, bevor das eigentliche Skript ausgeführt wird. Diese Variable ist der einzige offizielle Hebel, um nicht interaktiven Shells eine gemeinsame Konfiguration mitzugeben, wird in der Praxis aber selten genutzt, weil viele Administratoren ihre Variablen stattdessen explizit im Skript selbst setzen.
Wichtig für die Fehlersuche: BASH_ENV wird nur von Bash selbst ausgewertet, nicht von sh oder anderen POSIX-Shells, und sie greift nur bei nicht interaktiven Shells, niemals bei interaktiven Login- oder Non-Login-Shells. Wer also erwartet, dass eine über Cron gestartete Variable auch in der interaktiven Shell des Benutzers auftaucht, verwechselt zwei völlig getrennte Ladepfade.
# Set BASH_ENV so non-interactive scripts pick up shared config
export BASH_ENV="/etc/bash_env_common.sh"
# /etc/bash_env_common.sh — loaded before every non-interactive script
export PHP_INI_SCAN_DIR="/etc/php/8.4/custom.d"
export MAGENTO_MODE="production"
# Verify the effect from a non-interactive invocation
bash -c 'echo "MAGENTO_MODE inside non-interactive shell: $MAGENTO_MODE"'
7. SSH, Cron und systemd in der Praxis
Eine SSH-Sitzung mit ssh benutzer@server ohne angehängtes Kommando erzeugt eine interaktive Login-Shell, die also /etc/profile, danach die Login-Kette und je nach Nachlade-Trick auch .bashrc durchläuft. Führt man dagegen ssh benutzer@server 'befehl' mit einem direkt angehängten Kommando aus, entsteht eine nicht interaktive Non-Login-Shell, die standardmäßig gar keine der genannten Dateien liest, sondern nur BASH_ENV auswertet. Das erklärt, warum ferngesteuerte Deployment-Skripte oft mit einem leeren PATH zu kämpfen haben, obwohl im interaktiven SSH alles funktioniert.
Cron-Jobs laufen grundsätzlich als nicht interaktive, nicht anmeldende Shells mit einer sehr minimalen Umgebung, meist nur SHELL, PATH und HOME. systemd-Dienste sind noch strikter: Sie erben überhaupt keine Shell-Startdateien, sondern beziehen ihre Umgebung ausschließlich aus Environment= und EnvironmentFile= in der Unit-Datei. Wer also PHP-Umgebungsvariablen für einen Magento-Cron-Job oder einen systemd-Timer benötigt, muss diese explizit im Job selbst oder in der Unit-Datei setzen, statt sich auf .bashrc zu verlassen.
8. Typische Fehler: verlorene PATH-Einträge und doppelte Ausgaben
Der häufigste Fehler in der Praxis ist, eine benötigte Umgebungsvariable ausschließlich in .bashrc zu definieren und dann davon auszugehen, dass sie auch in einem Cron-Job oder einem SSH-Einzeilenbefehl verfügbar ist. Die zweithäufigste Fehlerquelle sind Ausgaben mit echo in .bashrc oder .bash_profile, etwa für ein Willkommensbanner: In nicht interaktiven Kontexten führt so etwas dazu, dass ein Skript, das die Ausgabe eines SSH-Kommandos parst, plötzlich unerwartete Textzeilen vor dem eigentlichen Ergebnis erhält, weil die Startdatei mitgelesen wurde.
Ein dritter klassischer Fehler betrifft doppeltes Anhängen an PATH. Lädt ~/.bash_profile zusätzlich ~/.bashrc nach, und beide Dateien hängen denselben Pfad an PATH an, wächst die Variable bei jedem neuen Terminal um einen weiteren doppelten Eintrag. Die robuste Lösung ist eine Prüfung, ob der Pfad bereits enthalten ist, bevor er erneut angehängt wird, statt blind zu konkatenieren.
# Idempotent PATH extension — avoids duplicate entries on repeated sourcing
add_to_path() {
local new_path="$1"
case ":$PATH:" in
*":$new_path:"*) ;; # already present, do nothing
*) PATH="$new_path:$PATH" ;; # not present, prepend once
esac
}
add_to_path "$HOME/.local/bin"
add_to_path "/usr/local/php8.4/bin"
export PATH
9. Ladereihenfolge debuggen und Vergleichstabelle
Um die tatsächlich geladene Reihenfolge nachzuvollziehen, hilft der Aufruf bash -x -l -c 'exit', der jede eingelesene Datei mit dem Trace-Modus sichtbar macht. Alternativ setzt man in jeder verdächtigen Startdatei eine eigene echo-Zeile mit dem Dateinamen an den Anfang, startet eine neue Sitzung im jeweiligen Kontext und liest die Reihenfolge der Ausgaben ab. Für SSH-Kommandos eignet sich ssh benutzer@server 'echo $0; echo $-', um zu prüfen, ob die entstandene Shell interaktiv war oder nicht.
| Kontext | Login-Shell | Interaktiv | Gelesene Dateien |
|---|---|---|---|
| SSH ohne Kommando | Ja | Ja | /etc/profile, bash_profile/bash_login/profile |
| SSH mit Kommando | Nein | Nein | nur BASH_ENV, falls gesetzt |
| Neues Terminalfenster | Nein | Ja | ~/.bashrc |
| Cron-Job | Nein | Nein | nur BASH_ENV, minimale Umgebung |
| systemd-Service | Nein | Nein | keine, nur Unit-Datei-Environment |
Diese Tabelle macht deutlich, warum eine zentrale, von .bash_profile nachgeladene .bashrc zusammen mit einer sauber gepflegten BASH_ENV-Datei die robusteste Kombination ist. Statt Variablen an mehreren Stellen redundant zu pflegen, definiert man sie einmal in einer gemeinsamen Datei und bindet diese über die jeweils passenden Mechanismen ein.
Mironsoft
Linux-Serveradministration und Shell-Umgebungen für PHP-Hosting
Umgebungsvariablen, die dort ankommen, wo sie gebraucht werden?
Wir prüfen eure Shell-Konfiguration über SSH, Cron und systemd hinweg, beheben inkonsistente Ladereihenfolgen und sorgen dafür, dass Deployment-Skripte und Cron-Jobs zuverlässig dieselbe Umgebung sehen.
Konfigurations-Audit
Analyse aller Startdateien und ihrer tatsächlichen Ladereihenfolge im Betrieb
Bereinigung
Konsolidierung redundanter PATH-Einträge und widersprüchlicher Variablen
Deployment-Absicherung
Konsistente Umgebung für Cron, systemd-Timer und CI/CD-Pipelines
10. Zusammenfassung
Die Ladereihenfolge der Bash-Startdateien folgt zwei Achsen: Login-Status und Interaktivität. Login-Shells lesen zuerst /etc/profile, danach genau eine der Dateien ~/.bash_profile, ~/.bash_login oder ~/.profile. Interaktive Non-Login-Shells lesen ausschließlich ~/.bashrc. Nicht interaktive Shells lesen standardmäßig gar nichts außer der über BASH_ENV referenzierten Datei. Cron-Jobs und systemd-Dienste folgen eigenen, noch strikteren Regeln und erhalten ihre Umgebung nicht aus den klassischen Startdateien.
Wer eine robuste, wartbare Konfiguration will, lässt ~/.bash_profile die ~/.bashrc nachladen, pflegt gemeinsame Umgebungsvariablen zentral über BASH_ENV oder /etc/profile.d/, und verzichtet auf Ausgaben in Startdateien, die auch von nicht interaktiven Kontexten erreicht werden könnten. Diese wenigen Regeln beseitigen den Großteil der Verwirrung rund um verschwundene Umgebungsvariablen im Serveralltag.
Bash-Ladereihenfolge: Das Wichtigste auf einen Blick
Login-Shell
/etc/profile, dann genau eine von bash_profile, bash_login oder profile. Nur bei frischem Login oder bash --login.
Interaktive Non-Login-Shell
Ausschließlich ~/.bashrc. Betrifft neue Terminalfenster und verschachtelte interaktive Shells.
Nicht interaktive Shell
Standardmäßig keine Datei, nur BASH_ENV falls gesetzt. Betrifft Skript-Ausführungen und einzeilige SSH-Kommandos.
Cron & systemd
Eigene, minimale Umgebung. Cron nutzt keine Startdateien, systemd-Units nur Environment= und EnvironmentFile=.