# Installing and setting up zsh: from apt to a lean .zshrc

> Install zsh, make it your default shell and set it up: lean without a framework or with Oh My Zsh, with plugins, prompt, startup times and uninstall.

Source: https://www.jpkc.com/db/en/blog/zsh-installieren/

I work with zsh in my own environment, and every time I set up a new machine or server I face the same questions: install the package, switch the default shell — and then Oh My Zsh, or rather a lean `.zshrc` of my own? The nudge for this article came from a compact [guide on Linux TLDR](https://linuxtldr.com/installing-zsh/). I follow the same path, but differ in a few places: with a variant that needs no framework at all, with measured startup times, with the current state of Powerlevel10k and with an uninstall that doesn't delete your history along the way.

If you only need the commands, jump straight to [step 1](#step-1-install-zsh). The most important commands and options for daily work are in the [zsh cheat sheet](https://www.jpkc.com/db/en/cheatsheets/shell-system/zsh/); this article is about installation, setup and upkeep.

## The short version

| What | How |
| --- | --- |
| **Install** | `sudo apt install zsh` · `sudo dnf install zsh` · `sudo pacman -S zsh` · already there on macOS |
| **Default shell** | `chsh -s "$(command -v zsh)"`, then log out and back in |
| **First start** | `zsh-newuser-install` menu: `0` creates a `.zshrc` that holds only a comment |
| **Without a framework** | your own `.zshrc` with history, completion and two plugins from the package manager |
| **With a framework** | Oh My Zsh: read the installer first, then run it; update with `omz update` |
| **Startup time** (measured) | empty 9 ms · own `.zshrc` ~45 ms, with two plugins ~65–70 ms · Oh My Zsh 97 ms, with two plugins 139 ms |
| **Prompt** | Powerlevel10k works but is barely maintained; alternatives: Starship, Pure, the built-in prompt |
| **Uninstall** | switch the shell back first, then remove the package |

## What zsh is — and which version you get

The Z shell was written by Paul Falstad as a student at Princeton; according to [Wikipedia](https://en.wikipedia.org/wiki/Z_shell), the first version appeared in 1990. He took the name from the login of a teaching assistant, Zhong Shao, whose user name was "zsh". Contrary to what you often read, zsh is not an extended Bash: the [zsh FAQ](https://zsh.sourceforge.io/FAQ/zshfaq01.html) describes it as the standard shell that most resembles the Korn shell — with borrowings from csh and tcsh and many extensions of its own on top.

It became widely known mainly through two decisions: Apple has used zsh [since macOS 10.15 Catalina](https://support.apple.com/en-us/102360) as the default for new user accounts, and [Kali Linux](https://www.kali.org/blog/kali-linux-2020-4-release/) switched in 2020. On most Linux distributions bash remains the default, but zsh is part of every official package selection.

The version is worth a closer look. After 5.9 in May 2022 there was no release for four years; then [versions 5.9.1 and 5.9.2](https://zsh.sourceforge.io/News/) arrived in May and July 2026, mostly bug fixes; among other things, 5.9.1 moved PCRE support to PCRE2. The stable distributions haven't caught up yet:

| System | zsh version in the default repository |
| --- | --- |
| Debian 12 / 13 | 5.9 |
| Ubuntu 24.04 / 26.04 LTS | 5.9 |
| Fedora 43 / 44 | 5.9 |
| Arch Linux, Homebrew | 5.9.2 |

For everyday use this doesn't matter — 5.9 is mature, and everything in this article works with it. That's why I always install zsh from the distribution and not from a third-party source.

## Step 1: Install zsh

The package is called `zsh` everywhere:

```bash
# Debian, Ubuntu, Linux Mint
sudo apt install zsh

# Fedora, RHEL, AlmaLinux, Rocky Linux
sudo dnf install zsh

# Arch, Manjaro, EndeavourOS
sudo pacman -S zsh

# macOS: zsh is preinstalled; Homebrew has the newer 5.9.2
brew install zsh
```

Then check that everything is there and that the package manager has registered zsh as an allowed login shell:

```bash
zsh --version
grep zsh /etc/shells
```

Debian, Ubuntu and Fedora add `/bin/zsh` and `/usr/bin/zsh` to `/etc/shells` themselves during installation. Before you change anything, you can simply start zsh with `zsh` and look around. `exit` takes you back to your old shell.

## Step 2: Make zsh your default shell

On Linux the login shell is stored in `/etc/passwd` (on macOS in Directory Services), and `chsh` changes it:

```bash
chsh -s "$(command -v zsh)"
```

`chsh` asks for your password and only accepts paths listed in `/etc/shells`. The change takes effect at your next login — so log out and back in. This shows whether it worked:

```bash
getent passwd "$USER" | cut -d: -f7   # should print /usr/bin/zsh or /bin/zsh
```

macOS has no `getent`; there you check with `dscl . -read ~/ UserShell`.

Three pitfalls:

- **Homebrew zsh on macOS.** The newer zsh at `$(brew --prefix)/bin/zsh` (Apple Silicon: `/opt/homebrew/bin/zsh`, Intel: `/usr/local/bin/zsh`) is not in `/etc/shells`. Add it there (`echo "$(brew --prefix)/bin/zsh" | sudo tee -a /etc/shells`) before you run `chsh`. For everyday use, the zsh that ships with macOS is perfectly fine, though.
- **Minimal Fedora installs.** Slim images lack `chsh`. It is part of `util-linux`, so `sudo dnf install util-linux`.
- **Servers.** Don't switch root's shell on a remote server, and keep a second SSH session open during the first test. A broken `.zshrc` won't lock you out, but it can make logging in unpleasant.

## Step 3: The first start

When zsh starts for the first time and finds none of the files `.zshenv`, `.zprofile`, `.zshrc` or `.zlogin`, the setup assistant `zsh-newuser-install` appears. The options:

- **`q`** quits without changes. The assistant returns at the next start.
- **`0`** creates a `.zshrc` containing only a comment. That's the right choice if you're about to install Oh My Zsh or write your own file.
- **`1`** guides you through menus for history, completion, key bindings and common shell options.
- **`2`** applies the configuration recommended by the system administrator. This option only exists if `/etc/zsh/newuser.zshrc.recommended` is present — Debian and Ubuntu ship that file. For a quick, usable start it's a good choice.

Good to know: zsh reads several files at startup in a fixed order. As a user, two of them matter most. `~/.zshenv` should only contain what every zsh really needs, including non-interactive scripts — such as a `PATH` addition. Everything for interactive work, i.e. prompt, history, completion, aliases and plugins, belongs in `~/.zshrc`. The full order is in the [manual](https://zsh.sourceforge.io/Doc/Release/Files.html).

## Step 4: The lean variant without a framework

Before you reach for Oh My Zsh: zsh brings most things itself. Completion, menu selection, a good history and even Git information in the prompt need no framework. This is my basic setup as a starting point:

```zsh title="~/.zshrc"
# History: keep a lot, share it between terminals, drop duplicates
HISTFILE=~/.zsh_history
HISTSIZE=10000
SAVEHIST=10000
setopt SHARE_HISTORY HIST_IGNORE_ALL_DUPS HIST_IGNORE_SPACE

# Convenience
setopt AUTO_CD            # type a directory name to cd into it
bindkey -e                # emacs-style line editing (Ctrl-A, Ctrl-E, ...)

# Completion with an interactive menu
zmodload zsh/complist
autoload -Uz compinit && compinit
zstyle ':completion:*' menu select

# Prompt: user@host, directory, git branch on the right
autoload -Uz vcs_info
precmd() { vcs_info }
zstyle ':vcs_info:git:*' formats '%b'
setopt PROMPT_SUBST
PROMPT='%n@%m %~ %# '
RPROMPT='${vcs_info_msg_0_}'

# Plugins from the package manager (Debian/Ubuntu paths) — highlighting last
source /usr/share/zsh-autosuggestions/zsh-autosuggestions.zsh
source /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
```

On Debian and Ubuntu you get the two plugins with `sudo apt install zsh-autosuggestions zsh-syntax-highlighting`; Fedora and Arch have packages of the same name, though the paths may differ. A few details about this file:

- **You need to set `HISTFILE` and `SAVEHIST`.** Without `HISTFILE`, according to the [manual](https://zsh.sourceforge.io/Doc/Release/Parameters.html), zsh doesn't save the history at all, and `SAVEHIST` sets how many entries end up in the file. macOS already sets both in `/etc/zshrc`.
- **`SHARE_HISTORY` alone is enough.** Many guides also set `INC_APPEND_HISTORY`. The manual's section on [options](https://zsh.sourceforge.io/Doc/Release/Options.html), however, says that `SHARE_HISTORY` already includes this behaviour and that the other option should then be turned off.
- **`HIST_IGNORE_SPACE`** keeps commands that start with a space out of the history. Handy if you ever have to type a token on the command line.
- **`zsh-syntax-highlighting` must be loaded last.** The authors say so explicitly in their [installation guide](https://github.com/zsh-users/zsh-syntax-highlighting/blob/master/INSTALL.md), and they even recommend sourcing it manually rather than through a framework.
- **On Ubuntu, `compinit` would otherwise run twice.** There, `/etc/zsh/zshrc` already calls `compinit`. Put `skip_global_compinit=1` in your `~/.zshenv`, and your `.zshrc` alone takes care of it.

With `source ~/.zshrc` or a new terminal, everything is active. In my measurement this file starts in 65 to 70 milliseconds, and in just over 40 without the two plugins — more on that [further down](#how-fast-does-your-zsh-start).

## Step 5: Install Oh My Zsh

[Oh My Zsh](https://github.com/ohmyzsh/ohmyzsh) is the best-known framework for zsh: several hundred plugins, well over a hundred themes, an update function and a large community. If you want to try out a lot, it's a convenient way in. You need `git` and `curl` or `wget`.

The official installation is a one-liner that runs a script straight from the internet. The project itself advises looking at unfamiliar install scripts first — and that's exactly what I do:

```bash
curl -fsSL -o install-omz.sh https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh
less install-omz.sh        # read it first
sh install-omz.sh
```

What happens: the script clones the repository to `~/.oh-my-zsh`, after asking renames an existing `.zshrc` to `.zshrc.pre-oh-my-zsh`, creates a new one and, if you agree, switches your default shell. There are switches for automated setups: `--unattended` neither changes the shell nor starts zsh afterwards, and `--keep-zshrc` leaves your existing `.zshrc` in place.

The new `.zshrc` is well commented. The two most important lines are `ZSH_THEME="robbyrussell"` for the look and `plugins=(git)` for the plugins that get loaded.

**Updates.** By default, Oh My Zsh asks every two weeks whether it should update itself. You can update manually with `omz update`. You control the behaviour in `.zshrc`, **before** the line `source $ZSH/oh-my-zsh.sh`:

```zsh
zstyle ':omz:update' mode reminder   # prompt (default) | auto | reminder | disabled
zstyle ':omz:update' frequency 14    # days between checks
zstyle ':omz:update' cooldown 10     # only apply changes that are at least 10 days old
```

The `cooldown` option is new, and I particularly like it: Oh My Zsh then only applies changes that are a few days old. It's the same idea as the quarantine for fresh packages that pnpm 11 and Bun offer with `minimumReleaseAge`; more on that in [Anthropic and Bun](https://www.jpkc.com/db/en/blog/bun-anthropic-claude-code/#security-the-part-that-occupies-me-most). A framework that updates itself from a Git repository is a supply chain, too.

## Step 6: Plugins with Oh My Zsh

The bundled plugins live in `~/.oh-my-zsh/plugins`; your own or third-party ones belong in `~/.oh-my-zsh/custom/plugins` (variable `$ZSH_CUSTOM`). This is how you install the two plugins from step 4 there:

```bash
git clone --depth=1 https://github.com/zsh-users/zsh-autosuggestions \
  "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autosuggestions"
git clone --depth=1 https://github.com/zsh-users/zsh-syntax-highlighting \
  "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting"
```

The second part is the important one, and guides often only show it in a screenshot: cloning alone loads nothing. You have to add the plugins in `.zshrc`, with `zsh-syntax-highlighting` last again:

```zsh
plugins=(git zsh-autosuggestions zsh-syntax-highlighting)
```

Then `omz reload` reloads the configuration. Type a few letters of an earlier command, and autosuggestions proposes the rest in grey; the right arrow key accepts it.

## Step 7: A prompt that suits you

At this point, many guides recommend [Powerlevel10k](https://github.com/romkatv/powerlevel10k). The theme is fast and pretty, but you should know what you're getting into: since May 2024, the top of the README has said that the project has very limited support, gets no new features, that most bugs will go unfixed and that help requests will be ignored. The last release dates from January 2024. It still works — but I wouldn't build anything new on it today.

If you use it anyway, do it the way the README describes for Oh My Zsh:

```bash
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git \
  "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k"
# then in ~/.zshrc:  ZSH_THEME="powerlevel10k/powerlevel10k"
```

The wizard starts by itself the next time you open zsh, and later at any time with `p10k configure`. For the symbols, the README recommends the Nerd Font MesloLGS NF, which you set in your terminal.

The alternatives:

- **[Starship](https://starship.rs/)** is a prompt written in Rust that looks the same in zsh, bash, fish and PowerShell and is actively maintained. You enable it with one line at the end of `.zshrc`: `eval "$(starship init zsh)"`. Here, too: look at the install script first, or install Starship from your package manager where it's offered.
- **[Pure](https://github.com/sindresorhus/pure)** is minimalist and fast, good for anyone who wants a calm prompt with Git status.
- **The built-in prompt** from step 4 with `vcs_info` needs nothing extra. With `autoload -Uz promptinit && promptinit` and `prompt -l` you can also see the themes zsh ships itself.

## How fast does your zsh start?

A framework costs startup time, and you notice it with every new terminal tab. I measured it in a fresh Debian 13 container, each value the mean of 20 starts of `zsh -i -c exit` after a first warm-up run:

| Configuration | Startup time |
| --- | --- |
| empty `.zshrc` | 9 ms |
| the `.zshrc` from step 4 without the two plugins | 42–46 ms |
| the full `.zshrc` from step 4, with two plugins | 64–70 ms |
| Oh My Zsh with the default configuration (`git` plugin) | 97 ms |
| Oh My Zsh with autosuggestions and syntax-highlighting | 139 ms |

The absolute numbers depend heavily on the machine and its load, while the ratios barely change. In absolute terms, all of this is fast. On older hardware, over a network drive or with many plugins, however, 100 milliseconds can quickly become a noticeable half second. This is how you measure your own configuration:

```zsh
# Quick check: total for 10 starts — divide by 10 for the average
time (for i in {1..10}; do zsh -i -c exit; done)

# Which part of your .zshrc costs the time?
# Add at the very top of ~/.zshrc:   zmodload zsh/zprof
# Add at the very bottom:            zprof
```

After startup, `zprof` lists the functions with their share of the time. The usual suspects are version managers such as nvm, completion scripts of large tools and too many plugins.

## What is different in zsh compared to bash

For daily work, zsh feels almost like bash. A few differences surprise everyone once, though — I reproduced all of them in the same test environment:

- **Arrays start at 1.** `a=(eins zwei drei); echo $a[1]` prints `eins` in zsh, whereas `${a[1]}` in bash prints `zwei`.
- **No automatic word splitting.** `v="a b"; for x in $v; do …` iterates over one value in zsh and two in bash. That's safer, but it breaks some bash snippets.
- **Recursive globbing is built in.** `**/*.md` finds all Markdown files in subdirectories in zsh; bash first needs `shopt -s globstar`.
- **A leading `=` is special.** `echo ===test` fails in zsh with `==test not found`, because zsh replaces `=command` with the command's path. If you print separator lines like that, put them in quotes.

This leads to a simple rule: **your scripts stay bash or sh scripts.** The shebang line `#!/bin/bash` or `#!/bin/sh` decides which shell runs a script, not your login shell. zsh is for interactive work. If you want to try a shell snippet in zsh that was written for sh, run it with `emulate sh -c '…'`.

## Uninstalling cleanly

If zsh isn't for you, the order matters. If you remove the package while zsh is still your login shell, your next login ends up nowhere.

```bash
# 1. Remove Oh My Zsh first, while you are still in zsh
uninstall_oh_my_zsh

# 2. Switch your login shell back, log out and in again, then check
chsh -s "$(command -v bash)"
getent passwd "$USER" | cut -d: -f7

# 3. Only now remove the package
sudo apt remove zsh          # or: sudo dnf remove zsh / sudo pacman -R zsh
```

`uninstall_oh_my_zsh` deletes `~/.oh-my-zsh`, renames the current `.zshrc` with a date stamp and restores your old `.zshrc.pre-oh-my-zsh`. If the installer switched your shell, it switches it back as well. If you're already in bash, the function no longer exists; then run `sh ~/.oh-my-zsh/tools/uninstall.sh`.

Afterwards, clean up selectively instead of deleting everything at once with `rm -rf .zsh*`. That would also hit `~/.zsh_history` and the backup copies you may still need:

```bash
ls -a ~ | grep -E 'zsh|p10k|oh-my'   # see what is there first
rm -f ~/.zcompdump* ~/.p10k.zsh      # caches and the Powerlevel10k config
```

## FAQ

### Do I need Oh My Zsh?

No. Everything most people value about Oh My Zsh — completion with a menu, a good history, Git in the prompt, suggestions and highlighting while you type — you get with the `.zshrc` from step 4 and two packages. Oh My Zsh pays off if you want to try lots of plugins and have the upkeep taken off your hands.

### Is `curl … | sh` dangerous?

Running a script straight from the internet means trusting the server and the connection completely at that moment. For well-known projects over HTTPS the risk is manageable, but looking costs a minute. Download the script, read it, then run it.

### Do my bash aliases work in zsh?

Mostly, yes. You can copy simple `alias` lines from `.bashrc` to `.zshrc`. Functions that work with arrays or word splitting should be tested, because zsh behaves differently there.

### Where do I put my PATH?

In `~/.zshenv` if non-interactive zsh calls need it too, otherwise in `~/.zshrc`. On macOS, the system files set the PATH at login, so your own additions usually belong in `~/.zprofile` there.

### Is Powerlevel10k still safe to use?

It works, and there's no reason to remove it right away. Since hardly any bugs get fixed any more, though, I'd go for Starship, Pure or the built-in prompt on a new setup.

## Conclusion

zsh is installed and set as your default shell in two minutes. The real decision comes afterwards: framework or not. In my measurement, Oh My Zsh is convenient and costs you around 100 milliseconds per start, almost 140 with two plugins. A `.zshrc` of your own with zsh's built-in tools and the same two plugins from the package manager gets by with half of that and depends on no update chain other than your distribution's. For me, that's the better starting point, because I know every line that runs when I open a terminal. If you like to experiment, take Oh My Zsh, but with `cooldown` and a prompt that is still maintained.

## Further reading

The most important commands, glob qualifiers and options for everyday use are in the [zsh cheat sheet](https://www.jpkc.com/db/en/cheatsheets/shell-system/zsh/); for comparison there are cheat sheets for [bash](https://www.jpkc.com/db/en/cheatsheets/shell-system/bash/) and [fish](https://www.jpkc.com/db/en/cheatsheets/shell-system/fish/). How I secure the supply chain of my build tools is covered in [Working securely with Node.js and npm](https://www.jpkc.com/db/en/blog/nodejs-npm-absichern/), and how to install and update Node.js itself in [Node2Shell and the question that follows](https://www.jpkc.com/db/en/blog/node2shell-nodejs-installieren/).

**Sources:** [zsh manual](https://zsh.sourceforge.io/Doc/Release/) · [zsh FAQ](https://zsh.sourceforge.io/FAQ/) · [zsh news](https://zsh.sourceforge.io/News/) · [Oh My Zsh](https://github.com/ohmyzsh/ohmyzsh) · [Powerlevel10k](https://github.com/romkatv/powerlevel10k) · [zsh-syntax-highlighting](https://github.com/zsh-users/zsh-syntax-highlighting) · [Z shell on Wikipedia](https://en.wikipedia.org/wiki/Z_shell)

