Published on

Fixing 'dotnet not found' on Linux (Bash, Zsh, Fish & CI)

Authors

The problem

Have you ever experienced installing .NET on Linux just to find out that the following day executing dotnet in the terminal returns

dotnet: command not found

Or worse — it works in one terminal but not another. Or Rider works, VS Code works, but your shell doesn't. Or CI builds fail with:

The command "dotnet" could not be found

This post explains why that happens and how to fix it properly — across Bash, Zsh, Fish, and CI environments. Think of it as a debugging guide to help you understand why things stopped working and how to get them back on track.


Step 1: Confirm the actual problem

Before changing anything, verify that the system actually sees .NET.

Is dotnet on PATH?

which dotnet

If nothing prints → it's not on your PATH.

If you see something like:

/home/youruser/.dotnet/dotnet

Then it exists on the system, but your shell config might not be loading the .NET path correctly.


Verify installed SDKs

dotnet --info

This shows:

  • SDK location
  • Installed runtimes
  • Base path

Typical install paths:

Install methodLocation
Microsoft apt repo/usr/share/dotnet
dotnet-install.sh$HOME/.dotnet
Snap/snap/bin/

Note: If you installed via snap install dotnet-sdk, the binary lands in /snap/bin/, which is sometimes not on PATH in all shell contexts. The fixes below apply the same way — just use the correct path.

If dotnet --info works in one shell but not another → this is almost always a shell startup issue.


Understanding Linux shell startup files

Different files are loaded depending on:

  • Login shell vs non-login shell
  • Interactive vs non-interactive shell
  • Which shell you use

Bash

Bash uses different startup files depending on how it's launched.

Login shell loads (in order, stops at the first one found)

  1. ~/.bash_profile
  2. ~/.bash_login
  3. ~/.profile

Bash checks these in order and only reads the first one it finds. This means if you have a ~/.bash_profile that doesn't source ~/.bashrc, your .bashrc exports won't load in login shells either.

Non-login interactive shell loads

  • ~/.bashrc

Most terminals open non-login shells, so .bashrc is usually what matters.


Zsh

Zsh loads:

  • ~/.zprofile (login shells)
  • ~/.zshrc (interactive shells)

If you added PATH changes to .profile, Zsh will not read them.


Fish

Fish ignores .bashrc, .profile, .zshrc entirely.

Fish uses:

~/.config/fish/config.fish

The real cause

If you installed .NET using the script:

./dotnet-install.sh

It installs to:

$HOME/.dotnet

But it does not permanently modify your PATH.

You must add these lines to the correct startup file:

export DOTNET_ROOT=$HOME/.dotnet
export PATH=$PATH:$HOME/.dotnet

Why both? PATH is what lets your shell find the dotnet command. DOTNET_ROOT is used separately by tools like Rider, VS Code, and OmniSharp to locate the SDK — they don't always rely on PATH alone.


The correct fix (by shell)

Bash

Edit:

nano ~/.bashrc

Add:

export DOTNET_ROOT=$HOME/.dotnet
export PATH=$PATH:$HOME/.dotnet

Reload:

source ~/.bashrc

Zsh

Edit:

nano ~/.zshrc

Add:

export DOTNET_ROOT=$HOME/.dotnet
export PATH=$PATH:$HOME/.dotnet

Reload:

source ~/.zshrc

Fish

Edit:

nano ~/.config/fish/config.fish

Add:

set -x DOTNET_ROOT $HOME/.dotnet
fish_add_path $HOME/.dotnet

Note: fish_add_path (Fish 3.2+) is the idiomatic way to add to PATH in Fish — it handles deduplication and persistence automatically. If you're on an older version of Fish, use set -x PATH $PATH $HOME/.dotnet instead.

Restart the terminal.


Login vs non-login shell gotcha

Run:

echo $0

If you see:

-bash

That's a login shell.

If you see:

bash

That's non-login.

If your PATH config is in .profile but your terminal runs non-login shells, it won't apply.


CI environments (GitHub Actions, Azure DevOps, etc.)

In CI, shell startup files usually do not run.

Install .NET explicitly in the pipeline.

GitHub Actions example

- name: Setup .NET
  uses: actions/setup-dotnet@v4
  with:
    dotnet-version: "8.0.x"

Tip: Avoid relying on .bashrc / .profile in CI. Treat pipelines as fresh machines.


Multiple SDKs + wrong version used

Sometimes dotnet exists, but the wrong SDK is selected.

Check installed SDKs:

dotnet --list-sdks

Pin the version with a global.json:

{
  "sdk": {
    "version": "8.0.401"
  }
}

Quick diagnostic checklist

  1. which dotnet
  2. dotnet --info
  3. echo $PATH
  4. echo $0
  5. Confirm correct shell config file
  6. Reload shell
  7. Open a new terminal

Linux is predictable once you understand shell startup order and how shells differ. Most "dotnet not found" errors are just misplaced environment variables.