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.

by ·

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 (opens in a new tab). 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. The most important commands and options for daily work are in the zsh cheat sheet; 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 (opens in a new tab), 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 (opens in a new tab) 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 (opens in a new tab) as the default for new user accounts, and Kali Linux (opens in a new tab) 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 (opens in a new tab) 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:

Terminal
# 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:

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:

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:

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 (opens in a new tab).

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:

~/.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 (opens in a new tab), 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 (opens in a new tab), 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 (opens in a new tab), 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.

Step 5: Install Oh My Zsh

Oh My Zsh (opens in a new tab) 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:

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:

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. 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:

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:

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 (opens in a new tab). 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:

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 (opens in a new tab) 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 (opens in a new tab) 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:

Terminal
# 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.

Terminal
# 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:

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; for comparison there are cheat sheets for bash and fish. How I secure the supply chain of my build tools is covered in Working securely with Node.js and npm, and how to install and update Node.js itself in Node2Shell and the question that follows.

Sources: zsh manual (opens in a new tab) · zsh FAQ (opens in a new tab) · zsh news (opens in a new tab) · Oh My Zsh (opens in a new tab) · Powerlevel10k (opens in a new tab) · zsh-syntax-highlighting (opens in a new tab) · Z shell on Wikipedia (opens in a new tab)