Skip to main content
DevOpsbeginner

Fix "error: externally-managed-environment" in pip

Fix `error: externally-managed-environment` from pip. Use a venv or pipx instead, and why --break-system-packages is a genuine last resort.

9 min readUpdated August 2026

Running pip install on a modern Linux distribution or a Homebrew Python now stops with:

error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try brew install
    xyz, where xyz is the package you are trying to
    install.

    If you wish to install a Python library that isn't in Homebrew,
    use a virtual environment:

    python3 -m venv path/to/venv
    source path/to/venv/bin/activate
    python3 -m pip install xyz
...
note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.

This is not a bug and not a broken pip. It is PEP 668 working as designed. Your Python belongs to something else — Debian's apt, Fedora's dnf, or Homebrew — and that packager has marked the installation off-limits to pip, because pip and the system package manager both write to the same site-packages and neither knows about the other's files.

The fix, in three lines:

python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install <package>

Inside a virtual environment the error is gone, permanently, because the venv is yours.

Why This Happens

Before PEP 668, sudo pip install would happily overwrite a library that apt had installed and still considered its own. The next system upgrade would then either revert your change or break, and system tools written in Python — including, on some distributions, the package manager — could stop working. The failure came much later than the cause, which made it miserable to debug.

PEP 668 moves that failure to the moment it can still be avoided. A packaged Python ships a marker file, and pip refuses to write into the environment when it finds one:

python3 -c "import sysconfig, os; d = sysconfig.get_path('stdlib'); print(os.path.join(d, 'EXTERNALLY-MANAGED'), os.path.exists(os.path.join(d, 'EXTERNALLY-MANAGED')))"
/opt/homebrew/opt/python@3.13/Frameworks/Python.framework/Versions/3.13/lib/python3.13/EXTERNALLY-MANAGED True

That file also contains the message body you saw, which is why the suggested commands differ between machines — Homebrew tells you to brew install, Debian tells you to apt install python3-xyz. The error itself is identical; the advice is supplied by the packager.

Fix 1: A Virtual Environment (Use This)

For anything your own code imports, this is the correct answer:

cd /path/to/project
python3 -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
python3 -m pip install requests

Your prompt gains a (.venv) prefix while it is active. Confirm packages land in the right place:

python3 -c "import requests; print(requests.__file__)"
# /path/to/project/.venv/lib/python3.13/site-packages/requests/__init__.py

Add .venv/ to .gitignore and record dependencies instead of committing the directory:

python3 -m pip freeze > requirements.txt

You do not have to activate it to use it — calling the interpreter by path works and is more reliable in scripts, cron jobs and CI:

/path/to/project/.venv/bin/python -m pip install -r requirements.txt
/path/to/project/.venv/bin/python main.py

Fix 2: pipx, for Command-Line Tools

If the thing you are installing is an application rather than a library — black, ruff, httpie, awscli, poetry — a project venv is the wrong shape. pipx gives each application its own hidden venv and links just the executable onto your PATH:

brew install pipx          # macOS
sudo apt install pipx      # Debian / Ubuntu
pipx ensurepath

pipx install black
pipx install ruff

The tools then never conflict with each other or with your projects, and upgrading one cannot break another:

pipx list
pipx upgrade-all
Advertisement

Fix 3: Install the Distribution's Package

If your distribution already packages what you need, letting it manage the package is the most robust option — it will be upgraded with the rest of the system:

sudo apt install python3-requests      # Debian / Ubuntu
sudo dnf install python3-requests      # Fedora / RHEL
brew install python-requests           # Homebrew, where available

The trade-off is version lag: distribution packages are often older than PyPI, and you cannot pin a specific version per project. For application dependencies, prefer a venv.

Fix 4: Containers

In a Dockerfile, build a venv and put it first on PATH. Everything afterwards — including plain pip install — then targets the venv automatically:

FROM python:3.13-slim

RUN python3 -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

This is worth doing even though a container is disposable. It keeps your dependencies separate from the base image's system Python, so an image rebuild on a newer base cannot silently change a library your app depends on.

The Last Resort: --break-system-packages

The flag exists, and the error message names it. It does exactly what it says:

python3 -m pip install --break-system-packages <package>     # not recommended

This disables a safety check that exists for a real reason. With it, pip will overwrite files owned by apt, dnf or brew. The failure does not appear at install time — it appears later, when a system upgrade collides with your version, or when an OS utility written in Python imports the library you replaced and gets an incompatible API. On Debian and Ubuntu, system tooling depends on specific versions of packages like requests and urllib3, so this can break apt itself and leave you repairing the system by hand.

Making it permanent is worse, because it applies to every future install and to anyone who uses that account:

# ~/.config/pip/pip.conf — don't
[global]
break-system-packages = true

There is one defensible use: a throwaway container or VM that you rebuild from a Dockerfile and never upgrade in place. Even there a venv costs two extra lines and removes the risk entirely.

Verify the Fix

Confirm you are in a virtual environment and that the install landed there:

python3 -c "import sys; print(sys.prefix != sys.base_prefix)"   # True inside a venv
python3 -m pip install requests
python3 -c "import requests; print(requests.__version__)"

For a pipx-installed tool, check what pipx is managing and that the executable resolves to its shim:

pipx list
which black
# ~/.local/bin/black

Prevention

  • Create a venv as the first step of every new project, before the first pip install. It costs one command and prevents this error entirely.
  • Use pipx for anything you run as a command, and venvs for anything you import.
  • Never sudo pip install. It was already the wrong thing before PEP 668; the error is now simply telling you so up front.
  • Put the venv creation in your project README and Makefile, so a new contributor's first pip install is already inside one.
  • In CI and Docker, reference the venv interpreter by absolute path rather than relying on activation, which does not persist across shell invocations or Docker layers.

Frequently Asked Questions

Find answers to common questions

Your Python installation is managed by something else — a Linux distribution's package manager, or Homebrew — and it has marked itself off-limits to pip. This is PEP 668. It is a guardrail, not a bug: pip is refusing to install into a directory that apt, dnf or brew also writes to.

Create a virtual environment and install there: 'python3 -m venv .venv', 'source .venv/bin/activate', then 'pip install '. Inside a venv the error disappears entirely, because the venv is yours rather than the OS's.

Only as a genuine last resort, and never on a machine you care about. It disables the safety check and lets pip overwrite files your system package manager owns, which can break OS tooling that depends on specific library versions. On some distributions that includes the package manager itself.

Use a venv for libraries your own project imports. Use pipx for command-line applications you want on your PATH, such as black, ruff or awscli — pipx creates a hidden venv per application and links only the executable, so the tools stay isolated from each other.

PEP 668 enforcement rolled out gradually. Debian 12, Ubuntu 23.04, Fedora 38 and recent Homebrew all ship the marker file that triggers it, so an upgrade of the OS or of Homebrew turns a previously working 'pip install' into this error with no change on your side.

Usually not — most externally managed installations block the --user path too, since it still shadows system packages on sys.path. A virtual environment is the supported answer. Where --user does work, it is still riskier than a venv because everything shares one directory.

Create a virtual environment in the image and put it on PATH: 'RUN python3 -m venv /opt/venv' then 'ENV PATH="/opt/venv/bin:$PATH"'. This is cleaner than --break-system-packages even in a container, because it keeps your dependencies separate from the base image's system Python.

From an EXTERNALLY-MANAGED file in your Python's stdlib directory, written by whoever packaged that Python. The wording differs between Homebrew, Debian and Fedora because each supplies its own text, which is why the suggested commands you see may not match another machine's.

Run: python3 -c "import sysconfig, os; d = sysconfig.get_path('stdlib'); print(os.path.exists(os.path.join(d, 'EXTERNALLY-MANAGED')))". True means the marker is present. You can also look for the file directly in the printed stdlib path.