Fish gives you autosuggestions, syntax highlighting and man-page completions on the first prompt, with no plugin to install and no rc file to write. Fedora carries it in the main repository, so the install is one command. The work is in the details around it: making it the login shell, knowing which config file wins, picking a plugin manager that is still maintained, and removing it later without locking a user out. The steps below were run on Fedora 45 and Fedora 44 in October 2026 with fish 4.6.0, Fisher 4.4.8 and Oh My Fish version 8. Fisher was exercised on Fedora 45 and Oh My Fish on Fedora 44.
Install fish from the Fedora repository
No extra repository is needed. Install the package:
sudo dnf install fish
On Fedora 45 the transaction pulls three small dependencies. Fedora 44 resolves the same four packages:
Updating and loading repositories:
Repositories loaded.
Package Arch Version Repository Size
Installing:
fish x86_64 0:4.6.0-2.fc45 fedora 21.2 MiB
Installing dependencies:
ncurses-term noarch 0:6.6-3.fc45 fedora 1.8 MiB
pcre2-utf32 x86_64 0:10.47-1.fc45.2 fedora 619.1 KiB
Installing weak dependencies:
man-pages noarch 0:6.13-6.fc45 fedora 2.9 MiB
Transaction Summary:
Installing: 4 packages
Total size of inbound packages is 9 MiB. Need to download 9 MiB.
After this operation, 27 MiB extra will be used (install 27 MiB, remove 0 B).
[1/4] pcre2-utf32-0:10.47-1.fc45.2.x86_ 100% | 721.1 KiB/s | 240.9 KiB | 00m00s
[2/4] ncurses-term-0:6.6-3.fc45.noarch 100% | 1.9 MiB/s | 994.3 KiB | 00m01s
[3/4] fish-0:4.6.0-2.fc45.x86_64 100% | 4.4 MiB/s | 4.4 MiB | 00m01s
[4/4] man-pages-0:6.13-6.fc45.noarch 100% | 3.9 MiB/s | 3.9 MiB | 00m01s
--------------------------------------------------------------------------------
[4/4] Total 100% | 3.4 MiB/s | 9.4 MiB | 00m03s
Running transaction
[1/6] Verify package files 100% | 117.0 B/s | 4.0 B | 00m00s
[2/6] Prepare transaction 100% | 50.0 B/s | 4.0 B | 00m00s
[3/6] Installing pcre2-utf32-0:10.47-1. 100% | 28.8 MiB/s | 620.0 KiB | 00m00s
[4/6] Installing ncurses-term-0:6.6-3.f 100% | 13.2 MiB/s | 2.2 MiB | 00m00s
[5/6] Installing fish-0:4.6.0-2.fc45.x8 100% | 106.7 MiB/s | 21.5 MiB | 00m00s
[6/6] Installing man-pages-0:6.13-6.fc4 100% | 11.0 MiB/s | 3.3 MiB | 00m00s
Complete!
Confirm the version:
fish --version
Both releases report the same build:
fish, version 4.6.0
The package registers itself as a valid login shell. Older guides tell you to append it to /etc/shells by hand. On Fedora that step is already done:
cat /etc/shells
The last two lines were added by the RPM:
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash
/usr/bin/tmux
/bin/tmux
/usr/bin/fish
/bin/fish
Newer fish from the upstream build service
Fedora’s package trails upstream. The fish project publishes its own RPMs on the openSUSE Build Service, and the fish releases page lists what is current. When we checked, the build service had a Fedora 44 repository but no Fedora 45 one yet:
| Source | Fedora 45 | Fedora 44 |
|---|---|---|
| Fedora repository | fish 4.6.0 | fish 4.6.0 |
| shells:fish:release:4 on OBS | No repository (404) | fish 4.9.3 |
On Fedora 44, add the repository with the release number filled in automatically:
sudo dnf config-manager addrepo --from-repofile=https://download.opensuse.org/repositories/shells:fish:release:4/Fedora_$(rpm -E %fedora)/shells:fish:release:4.repo
Fish from the Fedora repository is already installed at this point, so dnf install fish would report “Nothing to do”. Upgrade it instead:
sudo dnf upgrade fish
The build service package replaces the Fedora one. On the first transaction from this repository, dnf also imports the project signing key, as in the fresh-install output further down. Our test machine had already trusted it:
Updating and loading repositories:
Repositories loaded.
Package Arch Version Repository Size
Upgrading:
fish x86_64 0:4.9.3-2.2 shells_fish_release_4 16.6 MiB
replacing fish x86_64 0:4.6.0-1.fc44 fedora 21.2 MiB
Transaction Summary:
Upgrading: 1 package
Replacing: 1 package
Total size of inbound packages is 4 MiB. Need to download 4 MiB.
After this operation, 5 MiB will be freed (install 17 MiB, remove 21 MiB).
[1/1] fish-0:4.9.3-2.2.x86_64 100% | 1.4 MiB/s | 3.9 MiB | 00m03s
--------------------------------------------------------------------------------
[1/1] Total 100% | 1.4 MiB/s | 3.9 MiB | 00m03s
Running transaction
[1/4] Verify package files 100% | 66.0 B/s | 1.0 B | 00m00s
[2/4] Prepare transaction 100% | 21.0 B/s | 2.0 B | 00m00s
[3/4] Upgrading fish-0:4.9.3-2.2.x86_64 100% | 124.9 MiB/s | 16.6 MiB | 00m00s
[4/4] Removing fish-0:4.6.0-1.fc44.x86_ 100% | 8.8 KiB/s | 1.7 KiB | 00m00s
Complete!
On a machine without fish yet, sudo dnf install fish after adding the repository installs the newer build directly. dnf imports the project signing key on that first install:
Updating and loading repositories:
Repositories loaded.
Package Arch Version Repository Size
Installing:
fish x86_64 0:4.9.3-2.2 shells_fish_release_4 16.6 MiB
Installing dependencies:
pcre2-utf32 x86_64 0:10.47-1.fc44.1 fedora 623.1 KiB
Transaction Summary:
Installing: 2 packages
Total size of inbound packages is 4 MiB. Need to download 4 MiB.
After this operation, 17 MiB extra will be used (install 17 MiB, remove 0 B).
[1/2] pcre2-utf32-0:10.47-1.fc44.1.x86_ 100% | 596.5 KiB/s | 242.2 KiB | 00m00s
[2/2] fish-0:4.9.3-2.2.x86_64 100% | 1.9 MiB/s | 3.9 MiB | 00m02s
--------------------------------------------------------------------------------
[2/2] Total 100% | 711.8 KiB/s | 4.1 MiB | 00m06s
Running transaction
[3/3] https://download.opensuse.org/rep 100% | 2.5 KiB/s | 1.1 KiB | 00m00s
--------------------------------------------------------------------------------
[3/3] Total 100% | 711.8 KiB/s | 4.1 MiB | 00m06s
Importing OpenPGP key 0xD880C8E4:
UserID : "shells:fish OBS Project <shells:[email protected]>"
Fingerprint: 24A63B31CAB41B33EC48801E2CE2AC08D880C8E4
From : https://download.opensuse.org/repositories/shells:/fish:/release:/4/Fedora_44/repodata/repomd.xml.key
The key was successfully imported.
[1/4] Verify package files 100% | 117.0 B/s | 2.0 B | 00m00s
[2/4] Prepare transaction 100% | 24.0 B/s | 2.0 B | 00m00s
[3/4] Installing pcre2-utf32-0:10.47-1. 100% | 76.2 MiB/s | 623.9 KiB | 00m00s
[4/4] Installing fish-0:4.9.3-2.2.x86_6 100% | 77.3 MiB/s | 16.6 MiB | 00m00s
Complete!
The same command on Fedora 45 fails until the build service adds that release. Because the URL uses $(rpm -E %fedora), it starts working without edits once the directory exists:
https://download.opensuse.org/reposito 100% | 181.0 B/s | 399.0 B | 00m02s
>>> Status code: 404 for https://download.opensuse.org/repositories/shells:/fish:/release:/4/Fedora_45/shells:fish:release:4.repo (IP: 195.135.223.226) - https://download.opensuse.org/repositories/shells:/fish:/release:/4/Fedora_45/shells:fish:release:4.repo
>>> Status code: 404 for https://download.opensuse.org/repositories/shells:/fish:/release:/4/Fedora_45/shells:fish:release:4.repo (IP: 195.135.223.226) - https://download.opensuse.org/repositories/shells:/fish:/release:/4/Fedora_45/shells:fish:release:4.repo
>>> Status code: 404 for https://download.opensuse.org/repositories/shells:/fish:/release:/4/Fedora_45/shells:fish:release:4.repo (IP: 195.135.223.226) - https://download.opensuse.org/repositories/shells:/fish:/release:/4/Fedora_45/shells:fish:release:4.repo
>>> Status code: 404 for https://download.opensuse.org/repositories/shells:/fish:/release:/4/Fedora_45/shells:fish:release:4.repo (IP: 195.135.223.226) - https://download.opensuse.org/repositories/shells:/fish:/release:/4/Fedora_45/shells:fish:release:4.repo
>>> Status code: 404 for https://download.opensuse.org/repositories/shells:/fish:/release:/4/Fedora_45/shells:fish:release:4.repo (IP: 195.135.223.226)
Failed to download repository configuration file "https://download.opensuse.org/repositories/shells:fish:release:4/Fedora_45/shells:fish:release:4.repo": Failed to download files
Everything else in this guide behaves the same with either build.
Make fish your login shell
On current Fedora, chsh ships in the base util-linux package, so there is nothing extra to install. Run it as your own user and type your password when asked:
chsh -s /usr/bin/fish
The change is written to /etc/passwd straight away:
Changing shell for josphat.
Password:
Shell changed.
Check the account entry:
getent passwd $USER
The last field is now fish:
josphat:x:1001:1001:Josphat Mutai:/home/josphat:/usr/bin/fish
Log out and back in for the desktop session to pick it up. A new terminal then greets you with the fish banner, and the shell suggests completions from history as you type. The grey text in the next screenshot is an autosuggestion. Press the right arrow key to accept it.

Leave root on bash. Fish is not a POSIX shell, so scripts and recovery work stay simpler when the system accounts keep the default. Scripts with a #!/bin/bash line run under bash whatever your login shell is.
Know where fish reads its configuration
A fresh account has an empty ~/.config/fish. Fish creates files there only when you save something. These are the locations that matter:
| Path | What it holds |
|---|---|
~/.config/fish/config.fish | Your startup commands, read by every fish instance |
~/.config/fish/functions/ | One file per function, loaded on first use |
~/.config/fish/conf.d/ | Snippets read before config.fish; plugin managers write here |
~/.config/fish/fish_variables | Universal variables, managed by fish itself |
/etc/fish/config.fish | System-wide startup file, comments only on Fedora |
/usr/share/fish/vendor_conf.d/ | Snippets shipped by other Fedora packages |
The vendor directory is where Fedora surprises people. List the system locations:
ls /etc/fish /etc/fish/conf.d /usr/share/fish/vendor_conf.d
Two snippets from other packages run in every fish session:
/etc/fish:
completions
conf.d
config.fish
functions
/etc/fish/conf.d:
/usr/share/fish/vendor_conf.d:
debuginfod.fish
nano-default-editor.fish
Read it before you set EDITOR yourself:
cat /usr/share/fish/vendor_conf.d/nano-default-editor.fish
It only applies nano when EDITOR is unset:
# Ensure GNU nano is set as EDITOR if it isn't already set
# This is set as a universal variable so that any other definition
# by the user would win
# Cf. https://fishshell.com/docs/current/index.html#variables-scope
if ! set -q EDITOR;
set -x EDITOR /usr/bin/nano
end
Set variables, PATH, functions and abbreviations
Fedora’s default editor is nano, and Workstation ships only vim-minimal, which provides vi but no vim command. Install the full editor if you want vim as EDITOR:
sudo dnf install vim-enhanced
Fish has three variable scopes you will use: local, global and universal. A universal variable is saved to fish_variables and shared by every running fish. Set the editor that way and inspect it:
set -U EDITOR vim
set -S EDITOR
In the current shell, the global nano from the vendor snippet still wins, because a global beats a universal:
$EDITOR: set in global scope, exported, with 1 elements
$EDITOR[1]: |/usr/bin/nano|
$EDITOR: set in universal scope, unexported, with 1 elements
$EDITOR[1]: |vim|
Open a new terminal and echo $EDITOR prints vim. The vendor snippet sees EDITOR already set and does nothing. Note the word “unexported” in the output, though: a plain set -U is visible to fish but not to programs it starts, so git and sudoedit never see it. The set -gx line in config.fish below exports it properly.
Functions are saved one per file. Define one, then write it to disk with funcsave:
function ll; ls -lh $argv; end
funcsave ll
cat ~/.config/fish/functions/ll.fish
Fish reformats the body when it saves it:
funcsave: wrote /home/josphat/.config/fish/functions/ll.fish
function ll
ls -lh $argv;
end
Abbreviations are the catch. abbr --add typed at the prompt lasts only for that shell. In our test, a new shell listed no abbreviations at all. Put them in config.fish instead. Create the directory that the PATH line below expects, because fish_add_path skips paths that do not exist:
mkdir -p ~/.local/bin
nano ~/.config/fish/config.fish
This is the file we ran on both releases:
if status is-interactive
abbr --add gs git status
abbr --add dnfu sudo dnf upgrade --refresh
end
set -gx EDITOR vim
fish_add_path ~/.local/bin
fish_config theme choose nord
The is-interactive guard keeps abbreviations out of scripts. set -gx sets the editor explicitly, so it no longer depends on the vendor snippet. fish_add_path stores the directory in the universal fish_user_paths list. The last line loads a colour theme, covered in the next section. Open a new shell and check the abbreviations:
abbr --list
Both survive now:
gs
dnfu
Type gs and press Space, and fish expands it to git status in place. The history then records the full command, not the shortcut.
Pick a prompt and a colour theme
Fish ships sample prompts and themes. List the prompts:
fish_config prompt list
Thirteen come with the package:
acidhub
arrow
astronaut
default
disco
informative
informative_vcs
minimalist
nim
pythonista
scales
simple
terlar
fish_config prompt show renders all of them in the terminal. To keep one, save it. The command asks before it overwrites the current prompt, so we piped the answer in:
echo y | fish_config prompt save informative_vcs
The prompt lands in your functions directory:
Overwriting
funcsave: wrote /home/josphat/.config/fish/functions/fish_prompt.fish
Themes work differently in current fish. fish_config theme save still works, but the man page marks it “not recommended” because it freezes colours into universal variables. A theme with light and dark variants then stops following the terminal background. Load the theme from config.fish with choose instead, as in the file above. A fresh account has no universal colour variables. If you ran theme save on an older setup, the leftover universal values are shadowed by the theme and only come back if you remove the choose line. Clearing them is optional tidying:
for v in (set -U --names | string match "fish*color*"); set -e -U $v; end
A new interactive shell then confirms where the colours come from:
fish -i -c "set -S fish_color_command"
The theme name is recorded next to the value:
$fish_color_command: set in global scope, unexported, with 2 elements
$fish_color_command[1]: |88c0d0|
$fish_color_command[2]: |--theme=nord|
fish_config theme list prints the bundled themes, including catppuccin, dracula, nord and solarized.
Use the web configuration tool
Run fish_config with no arguments on a desktop session. It starts a small Python web server, on port 8000 when that is free, and opens your browser on a URL with a random token in the path. Close the tab and press Enter in the terminal to stop it. The colors tab previews every theme against sample commands:

The prompt tab renders each sample prompt with your real user and host name, so you see the result before you commit to it:

The server binds to all interfaces, but it drops any client that is not on localhost before the token is even checked, so other machines on the network cannot use it. If port 8000 is taken, it uses the next free port up to 9000.
Manage plugins with Fisher
Oh My Fish was the default answer for years. Its own README now opens with a warning that it “has been unmaintained for years” and that some themes and packages are broken. For new setups, use Fisher. It is a single fish function, it records your plugins in a plain text file, and it can also install Oh My Fish packages.
The fzf.fish plugin used below needs fzf and fd, and its git searches need git, which Oh My Fish also requires. All three are in the Fedora repository:
sudo dnf install fzf fd-find git
Bootstrap Fisher from fish, not bash, since the installer is fish code piped to source:
curl -sL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source && fisher install jorgebucaran/fisher
Fisher installs itself as a regular plugin:
fisher install version 4.4.8
Fetching https://api.github.com/repos/jorgebucaran/fisher/tarball/HEAD
Installing jorgebucaran/fisher
/home/josphat/.config/fish/functions/fisher.fish
/home/josphat/.config/fish/completions/fisher.fish
Installed 1 plugin/s
Add two plugins in one call. fzf.fish binds fuzzy search for files, history, git log and processes. autopair.fish closes brackets and quotes:
fisher install PatrickF1/fzf.fish jorgebucaran/autopair.fish
fisher list
The list includes Fisher itself:
jorgebucaran/fisher
patrickf1/fzf.fish
jorgebucaran/autopair.fish
The same list is written to ~/.config/fish/fish_plugins. Commit that file with your dotfiles, and fisher update on a new machine installs everything in it. The same command updates all plugins in place. Remove one by name:
fisher remove jorgebucaran/autopair.fish
Fisher deletes exactly the files it installed:
fisher remove version 4.4.8
Removing jorgebucaran/autopair.fish
/home/josphat/.config/fish/functions/_autopair_backspace.fish
/home/josphat/.config/fish/functions/_autopair_insert_left.fish
/home/josphat/.config/fish/functions/_autopair_insert_right.fish
/home/josphat/.config/fish/functions/_autopair_insert_same.fish
/home/josphat/.config/fish/functions/_autopair_tab.fish
/home/josphat/.config/fish/conf.d/autopair.fish
Removed 1 plugin/s
Install Oh My Fish for its themes
The main reason to still install Oh My Fish is its theme catalogue. It installed and ran cleanly on the Fedora fish package in our test. Pick one manager per account. Do not run Oh My Fish and Fisher side by side. Download the installer and check it against the published checksum before you run it:
curl -sL https://raw.githubusercontent.com/oh-my-fish/oh-my-fish/master/bin/install > install
curl -sL https://raw.githubusercontent.com/oh-my-fish/oh-my-fish/master/bin/install.sha256 | sha256sum --check
Only continue on OK:
install: OK
Run the installer with fish. Leave out the two flags to answer its questions yourself:
fish install --noninteractive --yes
It clones into ~/.local/share/omf and hooks itself in through conf.d:
Checking for a sane environment...
Installing Oh My Fish to /home/josphat/.local/share/omf...
Using release channel "stable".
Cloning master from https://github.com/oh-my-fish/oh-my-fish.git...
Writing bootstrap to /home/josphat/.config/fish/conf.d/omf.fish...
Setting up Oh My Fish configuration...
Updating https://github.com/oh-my-fish/packages-main master... Done!
Installing package default
✔ default successfully installed.
Installation successful!
List the themes it can fetch:
omf theme | head -30
The first rows of the catalogue:
Installed:
default
Available:
agnoster gentoo plain
aight gianu pure
anchor gitstatus puritan
ays gnuykeaj pygmalion
batman godfather random
beloglazov graystatus randomrussel
bira harleen red-snapper
bobthefish heartsteal rider
boxfish idan robbyrussell
budspencer integral sashimi
cbjohnson jacaetevha scorphish
chain johanson separation
clearance kawasaki simple
cmorrell kovial simple-ass-prompt
coffeeandcode krisleech simplevi
cor l slacker
cyan lambda slavic-cat
damin lambda-zero solarfish
dangerous lavender sushi
default lolfish syl20bnr
dmorrell mars taktoa
doughsay mint technopagan
easy-eel mish toaster
eclm mokou tomita
edan mtahmed trout
eden nai tweetjay
Install bobthefish, a powerline-style theme, and switch to it:
omf install bobthefish
omf theme bobthefish
The prompt redraws immediately. bobthefish draws arrow separators and a right-hand timestamp, so use a terminal font that carries powerline glyphs:

omf install adds plugins the same way, omf update refreshes everything, and omf remove takes a package out. When something looks wrong, run the built-in check:
omf doctor
A healthy install reports the versions it found:
Oh My Fish version: 8
OS type: Linux
Fish version: fish, version 4.6.0
Git version: git version 2.56.0
Git core.autocrlf: no
Checking for a sane environment...
Your shell is ready to swim.
Neither manager slowed shell startup in a way you would notice. Across 20 runs, an interactive fish -i -c exit took 14 to 19 ms per start, including the sudo -u overhead of the test loop, for a bare account (a throwaway user created for the timing), the Fisher account with fzf.fish, and the Oh My Fish account with bobthefish. That figure covers startup only, not drawing the prompt.
Remove fish without locking anyone out
Order matters here. The RPM removes fish from /etc/shells, but it does not touch accounts that still use it. We removed the package while a test account still had fish as its shell, then tried to log in as that user:
su: failed to execute /usr/bin/fish: No such file or directory
The account cannot start a login shell until root changes it. Find every account that still points at fish before you remove anything:
getent passwd | grep fish
On our Fedora 45 machine it found two, our own account and the timing test account:
josphat:x:1001:1001:Josphat Mutai:/home/josphat:/usr/bin/fish
baretest:x:1002:1002::/home/baretest:/usr/bin/fish
Then take out the plugin manager. With Fisher, pipe the list into remove:
fisher list | fisher remove
With Oh My Fish, run omf destroy in a terminal. It refuses to run without a TTY, and it asks for confirmation:

It leaves ~/.config/omf behind, so delete that by hand. For each other user that getent listed, switch the shell as root, replacing the name (we deleted our throwaway test user instead):
sudo usermod --shell /bin/bash otheruser
Switch your own account back to bash, then remove the package:
chsh -s /bin/bash
sudo dnf remove fish
dnf also drops the dependencies that came with it:
Package Arch Version Repository Size
Removing:
fish x86_64 0:4.6.0-2.fc45 fedora 21.2 MiB
Removing unused dependencies:
man-pages noarch 0:6.13-6.fc45 fedora 2.9 MiB
ncurses-term noarch 0:6.6-3.fc45 fedora 1.8 MiB
pcre2-utf32 x86_64 0:10.47-1.fc45.2 fedora 619.1 KiB
Transaction Summary:
Removing: 4 packages
After this operation, 27 MiB will be freed (install 0 B, remove 27 MiB).
Running transaction
[1/5] Prepare transaction 100% | 40.0 B/s | 4.0 B | 00m00s
[2/5] Removing fish-0:4.6.0-2.fc45.x86_ 100% | 17.5 KiB/s | 1.7 KiB | 00m00s
[3/5] Removing ncurses-term-0:6.6-3.fc4 100% | 47.5 KiB/s | 2.7 KiB | 00m00s
[4/5] Removing man-pages-0:6.13-6.fc45. 100% | 45.6 KiB/s | 2.9 KiB | 00m00s
[5/5] Removing pcre2-utf32-0:10.47-1.fc 100% | 29.0 B/s | 5.0 B | 00m00s
Complete!
Your history and settings stay in ~/.local/share/fish and ~/.config/fish until you delete them, which makes a later reinstall pick up where you left off. If you are deciding between shells, our comparison of Linux and macOS shells sets fish against zsh and others, and the zsh with Oh My Zsh guide covers the closest alternative. The Starship prompt works in fish too if you want one prompt across every shell. For the rest of a fresh Fedora setup, see things to do after installing Fedora 45 and the dnf5 cheat sheet.