Why are there so many of them?
zsh reads up to four config files at startup. Each has a different purpose. Putting the right line in the wrong file is the most common "my env doesn't load" bug.
The four zsh files
~/.zshenv— runs for every zsh invocation, including non-interactive scripts. Use sparingly.~/.zprofile— login shells only (the first shell of a session). Set$PATHhere. Homebrew shellenv lives here.~/.zshrc— interactive shells. Aliases, prompts, plugins, completion. Most of your config goes here.~/.zlogin— login shells, after .zshrc. Rarely used.
The bash equivalents
~/.bash_profile— login shells.~/.bashrc— interactive non-login shells.- Old hack: source one from the other so you only edit
.bashrc.
Login vs interactive (preview)
Login shell = the first shell of a session; interactive means you can type at a prompt. A macOS Terminal window is commonly both, while an SSH command may start a different shell shape. Scheduled jobs are a separate boundary: cron and launchd do not read zsh startup files for you. Next lesson covers the table.
Where to put what
- PATH and locale for a login zsh → .zprofile. Scheduled jobs do not inherit it automatically; declare their environment in the job or a controlled wrapper.
- Aliases, functions, prompt → .zshrc.
- Variables you want every script to see → .zshenv (rare).
Reload after editing
source ~/.zshrc or exec zsh reloads. The second is cleaner — it replaces the current shell with a fresh one and preserves your terminal window.
Startup files have different frequency and side effects
.zshenv can run for non-interactive zsh, so output, slow commands, or prompt setup there can break scripts. Put login and interactive work in narrower files and keep universal startup fast and silent.
source and a new shell are different reloads
source ~/.zshrc reapplies configuration on top of current state and can duplicate PATH entries or hooks. Even exec zsh may not reproduce the original login context. Validate with the kind of fresh session the changed file actually owns.