Cloud Explainers

How to Fix Python pip error: externally-managed-environment

Python11 min videoPublished Nov 5, 2026

pip's error: externally-managed-environment, for beginners, fixed on a real Ubuntu 24.04 laptop: Ana's tea shop script needs the requests library and pip refuses to install it. We find the EXTERNALLY-MANAGED file apt put there, then use the three safe fixes the error suggests (the apt package, a virtual environment, pipx for apps) and show what --break-system-packages really did: it deleted apt's copy of requests without telling apt.

How to Fix Python pip error: externally-managed-environment, the full picture: diagram of prices.py, pip, apt, System Python 3.12, Ubuntu's tools, pipx, .venv, Ubuntu's system Python, Ana's own environments
The whole picture, from the video's recap.

externally-managed-environment

You type pip install, and pip refuses: error, externally-managed-environment. Ana just set up a new Ubuntu laptop. She wants to run a small script that prints her tea shop's prices, and it needs one library, requests. pip won't install it, and without it, the script fails. By the end, you'll know why Ubuntu blocks pip, and three safe ways to get your package.

laptop: Ana's terminal
pip install requestserror: externally-managed-environment

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

    If you wish to install a non-Debian-packaged Python package,
    create a virtual environment using python3 -m venv path/to/venv.
    Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
    sure you have python3-full installed.

    If you wish to install a non-Debian packaged Python application,
    it may be easiest to use pipx install xyz, which will manage a
    virtual environment for you. Make sure you have pipx installed.

    See /usr/share/doc/python3.12/README.venv for more information.

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.
laptop: Ana's terminal
python3 prices.pyTraceback (most recent call last):
  File "/home/ana/prices/prices.py", line 1, in <module>
    import requests
ModuleNotFoundError: No module named 'requests'

Here's the picture. The script needs requests. pip, the tool that installs Python packages, wants to put it into the Python that came with Ubuntu, Python 3.12. But that Python already has an owner: apt, Ubuntu's package manager.

Why pip says no

So what happened? Before pip installs anything, it checks the Python it belongs to for one file, called EXTERNALLY-MANAGED. On Ubuntu, that file is there. It means: someone else manages this Python's packages, so pip must not. So pip stops before it changes a thing, and prints the error. This rule is a Python standard, PEP 668, and Ubuntu has followed it since version 23.04.

What pip checks before it installs: diagram of Ana, pip, Python 3.12, pip install requests, EXTERNALLY-MANAGED?, yes, it's here, error: externally-managed-environment, PEP 668 · Ubuntu 23.04 and later, Debian 12 and later

Here's the file. dpkg, the tool under apt that records every file it installs, says an Ubuntu package put it there. Ask dpkg about the folder where Python packages live, and it names 23 Ubuntu packages that put files in it, even the Python library apt itself uses. And Ubuntu commands like add-apt-repository are Python programs that run on this same Python. You don't need to run these yourself: they only show who owns that folder.

laptop: Ana's terminal
ls /usr/lib/python3.12/EXTERNALLY-MANAGED/usr/lib/python3.12/EXTERNALLY-MANAGED
laptop: Ana's terminal
dpkg -S /usr/lib/python3.12/EXTERNALLY-MANAGEDlibpython3.12-stdlib:amd64: /usr/lib/python3.12/EXTERNALLY-MANAGED
laptop: Ana's terminal
dpkg -S /usr/lib/python3/dist-packagessoftware-properties-common, python3.12, python3-wheel, python3-wadllib, python3-software-properties, python3-six, python3-setuptools, python3-pyparsing, python3-pkg-resources, python3-pip, python3-oauthlib, python3-lazr.uri, python3-lazr.restfulclient, python3-launchpadlib, python3-jwt, python3-httplib2, python3-gi, python3-distro, python3-dbus, python3-cryptography, python3-cffi-backend:amd64, python3-blinker, python3-apt: /usr/lib/python3/dist-packages
laptop: Ana's terminal
head -1 /usr/bin/add-apt-repository#!/usr/bin/python3

That's the real problem: two installers, one folder. apt puts its Python packages there, and Ubuntu's tools are built and tested against exactly those versions. If pip swapped one for a newer version, apt would have no idea, and a tool you rely on could break. Even dash dash user is blocked, because those packages would be loaded by every Python program you run, Ubuntu's included.

The good news: the error lists the ways out. Install the package from apt. Or make a virtual environment for your project. Or, for a program you run as a command, use pipx. The last line offers an override, and later we'll see what it really does.

What the error message suggests: cards of An apt package, A virtual environment, pipx, for apps, The override

The apt package

The first way is the simplest. Ana installs python3-requests with sudo apt. sudo runs a command as the administrator, so it asks for her password. Then she runs her script again. It prints the prices. apt installed requests version 2.31.0.

laptop: Ana's terminal
sudo apt install -y python3-requests[sudo] password for ana:
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  python3-certifi python3-chardet python3-idna python3-urllib3
Suggested packages:
  python3-openssl python3-socks python-requests-doc python3-brotli
The following NEW packages will be installed:
  python3-certifi python3-chardet python3-idna python3-requests python3-urllib3
0 upgraded, 5 newly installed, 0 to remove and 5 not upgraded.
Need to get 478 kB of archives.
After this operation, 2556 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu noble/main amd64 python3-certifi all 2023.11.17-1 [165 kB]
Get:2 http://archive.ubuntu.com/ubuntu noble/main amd64 python3-chardet all 5.2.0+dfsg-1 [117 kB]
Get:3 http://archive.ubuntu.com/ubuntu noble-updates/main amd64 python3-idna all 3.6-2ubuntu0.2 [49.7 kB]
Get:4 http://archive.ubuntu.com/ubuntu noble-updates/main amd64 python3-urllib3 all 2.0.7-1ubuntu0.8 [95.9 kB]
Get:5 http://archive.ubuntu.com/ubuntu noble-updates/main amd64 python3-requests all 2.31.0+dfsg-1ubuntu1.2 [50.8 kB]
Fetched 478 kB in 0s (5237 kB/s)
debconf: delaying package configuration, since apt-utils is not installed

Selecting previously unselected package python3-certifi.
(Reading database ... 10903 files and directories currently installed.)
Preparing to unpack .../python3-certifi_2023.11.17-1_all.deb ...
Unpacking python3-certifi (2023.11.17-1) ...
Selecting previously unselected package python3-chardet.
Preparing to unpack .../python3-chardet_5.2.0+dfsg-1_all.deb ...
Unpacking python3-chardet (5.2.0+dfsg-1) ...
Selecting previously unselected package python3-idna.
Preparing to unpack .../python3-idna_3.6-2ubuntu0.2_all.deb ...
Unpacking python3-idna (3.6-2ubuntu0.2) ...
Selecting previously unselected package python3-urllib3.
Preparing to unpack .../python3-urllib3_2.0.7-1ubuntu0.8_all.deb ...
Unpacking python3-urllib3 (2.0.7-1ubuntu0.8) ...
Selecting previously unselected package python3-requests.
Preparing to unpack .../python3-requests_2.31.0+dfsg-1ubuntu1.2_all.deb ...
Unpacking python3-requests (2.31.0+dfsg-1ubuntu1.2) ...
Setting up python3-chardet (5.2.0+dfsg-1) ...
Setting up python3-certifi (2023.11.17-1) ...
Setting up python3-idna (3.6-2ubuntu0.2) ...
Setting up python3-urllib3 (2.0.7-1ubuntu0.8) ...
Setting up python3-requests (2.31.0+dfsg-1ubuntu1.2) ...
laptop: Ana's terminal
python3 prices.pyEarl Grey    $6.50
Sencha       $8.00
Chai         $5.75
laptop: Ana's terminal
apt list --installed python3-requestsListing... Done
python3-requests/noble-updates,noble-security,now 2.31.0+dfsg-1ubuntu1.2 all [installed]
N: There is 1 additional version. Please use the '-a' switch to see it

Ubuntu packages many libraries this way, named python3 dash and the library's name. On the map, apt did the installing, so the new library lands in the system Python's folder with apt's records up to date. sudo was needed because that folder belongs to the whole system, not to Ana. Her script imports it from there, and Ubuntu's tools keep working.

That's the right choice for a script that only needs common libraries. Ubuntu tested this version with everything else it ships, and apt upgrade keeps it patched. The catch: it's often older. PyPI, where pip downloads from, had 2.34.2 that day. And many libraries aren't packaged at all.

apt install python3-…: cards of Tested by Ubuntu, Kept up to date, Often older, Not everything

A virtual environment

For newer versions, or any library from PyPI, use a virtual environment: a venv. It's just a folder inside your project. It has its own pip, and its own place for packages. It borrows the system's Python program to run, but never touches the packages apt installed. So pip is allowed to install anything there.

A virtual environment, inside Ana's project: diagram of prices.py, .venv, System Python 3.12

One command makes it. python3 is the system Python. Dash m venv runs its built-in venv module. And dot venv is the folder to create, the name most projects use.

Make a virtual environment: diagram of python3 -m venv.venv, python3 = The system Python, -m venv = Run the venv module, .venv = The folder to create, Run it inside your project's folder

On a fresh Ubuntu, the first try fails too. Ubuntu ships part of the venv module separately, in one more apt package, python3-venv, and the message names it. Ana installs it, with sudo and apt.

laptop: Ana's terminal
python3 -m venv .venvThe virtual environment was not created successfully because ensurepip is not
available.  On Debian/Ubuntu systems, you need to install the python3-venv
package using the following command.

    apt install python3.12-venv

You may need to use sudo with that command.  After installing the python3-venv
package, recreate your virtual environment.

Failing command: /home/ana/prices/.venv/bin/python3
laptop: Ana's terminal
sudo apt install -y python3-venvReading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  python3-pip-whl python3-setuptools-whl python3.12-venv
The following NEW packages will be installed:
  python3-pip-whl python3-setuptools-whl python3-venv python3.12-venv
0 upgraded, 4 newly installed, 0 to remove and 5 not upgraded.
Need to get 2430 kB of archives.
After this operation, 2783 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu noble-updates/universe amd64 python3-pip-whl all 24.0+dfsg-1ubuntu1.3 [1707 kB]
Get:2 http://archive.ubuntu.com/ubuntu noble-updates/universe amd64 python3-setuptools-whl all 68.1.2-2ubuntu1.2 [716 kB]
Get:3 http://archive.ubuntu.com/ubuntu noble-updates/universe amd64 python3.12-venv amd64 3.12.3-1ubuntu0.17 [5684 B]
Get:4 http://archive.ubuntu.com/ubuntu noble-updates/universe amd64 python3-venv amd64 3.12.3-0ubuntu2.1 [1032 B]
Fetched 2430 kB in 1s (2127 kB/s)
debconf: delaying package configuration, since apt-utils is not installed

Selecting previously unselected package python3-pip-whl.
(Reading database ... 11075 files and directories currently installed.)
Preparing to unpack .../python3-pip-whl_24.0+dfsg-1ubuntu1.3_all.deb ...
Unpacking python3-pip-whl (24.0+dfsg-1ubuntu1.3) ...
Selecting previously unselected package python3-setuptools-whl.
Preparing to unpack .../python3-setuptools-whl_68.1.2-2ubuntu1.2_all.deb ...
Unpacking python3-setuptools-whl (68.1.2-2ubuntu1.2) ...
Selecting previously unselected package python3.12-venv.
Preparing to unpack .../python3.12-venv_3.12.3-1ubuntu0.17_amd64.deb ...
Unpacking python3.12-venv (3.12.3-1ubuntu0.17) ...
Selecting previously unselected package python3-venv.
Preparing to unpack .../python3-venv_3.12.3-0ubuntu2.1_amd64.deb ...
Unpacking python3-venv (3.12.3-0ubuntu2.1) ...
Setting up python3-setuptools-whl (68.1.2-2ubuntu1.2) ...
Setting up python3-pip-whl (24.0+dfsg-1ubuntu1.3) ...
Setting up python3.12-venv (3.12.3-1ubuntu0.17) ...
Setting up python3-venv (3.12.3-0ubuntu2.1) ...

Now the venv command works, and makes the folder. source runs the activate script inside this terminal, switching it to the venv, and the prompt now starts with dot venv. which shows that python and pip now mean the copies inside the folder.

laptop: Ana's terminal
python3 -m venv .venv
laptop: Ana's terminal
source .venv/bin/activate
laptop: Ana's terminal
(.venv) ana@laptop:~/prices$ which python pip/home/ana/prices/.venv/bin/python
/home/ana/prices/.venv/bin/pip

What did activate change? When you type a command, the shell searches a list of folders, called the path, in order. Activate puts the venv's bin folder at the front. So python now finds the venv's copy first, and the system's only after it. Deactivate takes it off again. Nothing was installed or moved.

Where the shell looks for python, after activate: diagram of .venv/bin, /usr/bin

So the same pip install that failed now just works. pip downloads requests 2.34.2 from PyPI, with 4 libraries it needs. And the script prints the prices.

laptop: Ana's terminal
(.venv) ana@laptop:~/prices$ pip install requestsCollecting requests
  Downloading requests-2.34.2-py3-none-any.whl.metadata (4.8 kB)
Collecting charset_normalizer<4,>=2 (from requests)
  Downloading charset_normalizer-3.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (46 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 46.4/46.4 kB 1.7 MB/s eta 0:00:00

Collecting idna<4,>=2.5 (from requests)
  Downloading idna-3.20-py3-none-any.whl.metadata (7.2 kB)
Collecting urllib3<3,>=1.26 (from requests)
  Downloading urllib3-2.8.0-py3-none-any.whl.metadata (7.4 kB)
Collecting certifi>=2023.5.7 (from requests)
  Downloading certifi-2026.10.23-py3-none-any.whl.metadata (2.5 kB)
Downloading requests-2.34.2-py3-none-any.whl (73 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 73.1/73.1 kB 3.3 MB/s eta 0:00:00

Downloading certifi-2026.10.23-py3-none-any.whl (135 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 135.1/135.1 kB 6.0 MB/s eta 0:00:00

Downloading charset_normalizer-3.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (257 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 257.6/257.6 kB 10.4 MB/s eta 0:00:00

Downloading idna-3.20-py3-none-any.whl (69 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 69.6/69.6 kB 3.3 MB/s eta 0:00:00

Downloading urllib3-2.8.0-py3-none-any.whl (135 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 135.7/135.7 kB 5.6 MB/s eta 0:00:00

Installing collected packages: urllib3, idna, charset_normalizer, certifi, requests
Successfully installed certifi-2026.10.23 charset_normalizer-3.5.2 idna-3.20 requests-2.34.2 urllib3-2.8.0
laptop: Ana's terminal
(.venv) ana@laptop:~/prices$ python prices.pyEarl Grey    $6.50
Sencha       $8.00
Chai         $5.75

On the map, pip now writes only into Ana's own folder. Her script runs from the venv, and everything apt installed is untouched. Each project gets its own venv, so two projects can even need different versions of the same library. And if a venv ever gets into a mess, delete the folder and make it again.

Ana saves the exact versions with pip freeze, into a file called requirements.txt. Then she types deactivate, to leave the environment. Outside it, python3 dash c runs one line of Python, and the system Python still has the requests from apt, 2.31.0. Two versions, no conflict.

laptop: Ana's terminal
(.venv) ana@laptop:~/prices$ pip freeze > requirements.txt
laptop: Ana's terminal
(.venv) ana@laptop:~/prices$ cat requirements.txtcertifi==2026.10.23
charset-normalizer==3.5.2
idna==3.20
requests==2.34.2
urllib3==2.8.0
laptop: Ana's terminal
(.venv) ana@laptop:~/prices$ deactivate
laptop: Ana's terminal
python3 -c 'import requests; print(requests.__version__)'2.31.0

Each line of requirements.txt pins one package to one version. The name, two equals signs, which mean exactly this version, and the version itself. Keep this file with your code, and anyone can rebuild the same environment, including you, on your next laptop.

One line of requirements.txt: diagram of requests==2.34.2, requests = The package, == = Exactly, 2.34.2 = The version, pip install -r requirements.txt rebuilds it

pipx for apps

The third way, pipx, is for programs you run as commands, not libraries you import. Ana wants httpie, a friendly tool for testing web addresses. pipx gives every app its own hidden venv, then puts the app's command in a bin folder in her home, so she can run it from any terminal.

pipx: one hidden venv per app: diagram of pipx install httpie, httpie's own venv, the http command, Ana's home folder

pipx comes from apt. Then she types pipx install httpie. It installs version 3.2.4, and warns that the bin folder isn't on her path yet. pipx ensurepath adds it, and says to open a new terminal.

laptop: Ana's terminal
sudo apt install -y pipxReading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  python3-argcomplete python3-click python3-colorama python3-packaging python3-platformdirs
  python3-psutil python3-userpath
The following NEW packages will be installed:
  pipx python3-argcomplete python3-click python3-colorama python3-packaging
  python3-platformdirs python3-psutil python3-userpath
0 upgraded, 8 newly installed, 0 to remove and 5 not upgraded.
Need to get 1194 kB of archives.
After this operation, 5564 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu noble-updates/universe amd64 python3-argcomplete all 3.1.4-1ubuntu0.1 [33.8 kB]
Get:2 http://archive.ubuntu.com/ubuntu noble/main amd64 python3-packaging all 24.0-1 [41.1 kB]
Get:3 http://archive.ubuntu.com/ubuntu noble/main amd64 python3-platformdirs all 4.2.0-1 [16.1 kB]
Get:4 http://archive.ubuntu.com/ubuntu noble/main amd64 python3-colorama all 0.4.6-4 [32.1 kB]
Get:5 http://archive.ubuntu.com/ubuntu noble/main amd64 python3-click all 8.1.6-2 [79.0 kB]
Get:6 http://archive.ubuntu.com/ubuntu noble/universe amd64 python3-userpath all 1.9.1-1 [9416 B]
Get:7 http://archive.ubuntu.com/ubuntu noble/universe amd64 pipx all 1.4.3-1 [787 kB]
Get:8 http://archive.ubuntu.com/ubuntu noble/main amd64 python3-psutil amd64 5.9.8-2build2 [195 kB]
Fetched 1194 kB in 0s (11.9 MB/s)
debconf: delaying package configuration, since apt-utils is not installed

Selecting previously unselected package python3-argcomplete.
(Reading database ... 11092 files and directories currently installed.)
Preparing to unpack .../0-python3-argcomplete_3.1.4-1ubuntu0.1_all.deb ...
Unpacking python3-argcomplete (3.1.4-1ubuntu0.1) ...
Selecting previously unselected package python3-packaging.
Preparing to unpack .../1-python3-packaging_24.0-1_all.deb ...
Unpacking python3-packaging (24.0-1) ...
Selecting previously unselected package python3-platformdirs.
Preparing to unpack .../2-python3-platformdirs_4.2.0-1_all.deb ...
Unpacking python3-platformdirs (4.2.0-1) ...
Selecting previously unselected package python3-colorama.
Preparing to unpack .../3-python3-colorama_0.4.6-4_all.deb ...
Unpacking python3-colorama (0.4.6-4) ...
Selecting previously unselected package python3-click.
Preparing to unpack .../4-python3-click_8.1.6-2_all.deb ...
Unpacking python3-click (8.1.6-2) ...
Selecting previously unselected package python3-userpath.
Preparing to unpack .../5-python3-userpath_1.9.1-1_all.deb ...
Unpacking python3-userpath (1.9.1-1) ...
Selecting previously unselected package pipx.
Preparing to unpack .../6-pipx_1.4.3-1_all.deb ...
Unpacking pipx (1.4.3-1) ...
Selecting previously unselected package python3-psutil.
Preparing to unpack .../7-python3-psutil_5.9.8-2build2_amd64.deb ...
Unpacking python3-psutil (5.9.8-2build2) ...
Setting up python3-colorama (0.4.6-4) ...
Setting up python3-click (8.1.6-2) ...
Setting up python3-platformdirs (4.2.0-1) ...
Setting up python3-psutil (5.9.8-2build2) ...
Setting up python3-packaging (24.0-1) ...
Setting up python3-argcomplete (3.1.4-1ubuntu0.1) ...
Setting up python3-userpath (1.9.1-1) ...
Setting up pipx (1.4.3-1) ...
laptop: Ana's terminal
pipx install httpie  installed package httpie 3.2.4, installed using Python 3.12.3
  These apps are now globally available
    - http
    - httpie
    - https
  These manual pages are now globally available
    - man1/http.1
    - man1/httpie.1
    - man1/https.1
⚠️  Note: '/home/ana/.local/bin' is not on your PATH environment variable. These apps will not
    be globally accessible until your PATH is updated. Run `pipx ensurepath` to automatically
    add it, or manually modify your PATH in your shell's config file (i.e. ~/.bashrc).
done! ✨ 🌟 ✨
laptop: Ana's terminal
pipx ensurepathSuccess! Added /home/ana/.local/bin to the PATH environment variable.

Consider adding shell completions for pipx. Run 'pipx completions' for instructions.

You will need to open a new terminal or re-login for the PATH changes to take effect.

Otherwise pipx is ready to go! ✨ 🌟 ✨

In a new terminal, the http command just works. Ana asks her shop for the price list, and httpie prints the answer: status 200, and the prices.

laptop: a new terminal
http localhost:8000/prices.jsonHTTP/1.0 200 OK
Content-Length: 48
Content-type: application/json
Date: Thu, 05 Nov 2026 01:54:32 GMT
Last-Modified: Thu, 05 Nov 2026 01:40:39 GMT
Server: SimpleHTTP/0.6 Python/3.12.3

{
    "Chai": 5.75,
    "Earl Grey": 6.5,
    "Sencha": 8.0
}

On the map, pipx is a separate space of its own. Each app's environment sits in Ana's home folder, away from her project, and away from the system Python. apt only installed pipx itself.

So which one when? A venv is for libraries your own code imports, and it lives in your project. pipx is for programs you run, and it lives in your home. With a venv, you activate it; pipx does that part for you.

A venv or pipx?: comparison table of A venv vs pipx, For, Lives in, Activate it?, Example

Forcing it

Now the override. Many answers online say: add dash dash break system packages. Ana tries it with sudo, to upgrade requests. Read what pip says: it uninstalled requests 2.31.0, the copy from apt, and installed 2.34.2 into user local.

laptop: a new terminal
sudo pip install --break-system-packages --upgrade requests[sudo] password for ana:
Requirement already satisfied: requests in /usr/lib/python3/dist-packages (2.31.0)
Collecting requests
  Downloading requests-2.34.2-py3-none-any.whl.metadata (4.8 kB)
Collecting charset_normalizer<4,>=2 (from requests)
  Downloading charset_normalizer-3.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (46 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 46.4/46.4 kB 853.9 kB/s eta 0:00:00

Requirement already satisfied: idna<4,>=2.5 in /usr/lib/python3/dist-packages (from requests) (3.6)
Requirement already satisfied: urllib3<3,>=1.26 in /usr/lib/python3/dist-packages (from requests) (2.0.7)
Requirement already satisfied: certifi>=2023.5.7 in /usr/lib/python3/dist-packages (from requests) (2023.11.17)
Downloading requests-2.34.2-py3-none-any.whl (73 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 73.1/73.1 kB 1.9 MB/s eta 0:00:00

Downloading charset_normalizer-3.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (257 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 257.6/257.6 kB 4.4 MB/s eta 0:00:00

Installing collected packages: charset_normalizer, requests
  Attempting uninstall: requests
    Found existing installation: requests 2.31.0
    Uninstalling requests-2.31.0:
      Successfully uninstalled requests-2.31.0
Successfully installed charset_normalizer-3.5.2 requests-2.34.2
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
laptop: a new terminal
python3 -c 'import requests; print(requests.__version__, requests.__file__)'2.34.2 /usr/local/lib/python3.12/dist-packages/requests/__init__.py

Why did Python pick pip's copy? Python searches its folders in order. On Ubuntu, the folder sudo pip writes to, user local, comes first. the folder apt uses comes after it. So whatever pip puts there quietly wins over what apt installed. Ubuntu's own tools would load it too, a version they were never tested with.

Where Python looks for a package, in order: diagram of /usr/local/…, /usr/lib/python3/…

Here's what that did. pip deleted the files apt had installed, and put its own copy somewhere else. apt was never told. Its records still say version 2.31.0 is installed. From now on, the two disagree, and that's how systems quietly break.

What --break-system-packages did: diagram of pip, Python's folders, apt's records, delete requests 2.31.0, add requests 2.34.2, (nothing), requests 2.31.0: installed?, apt still lists 2.31.0 as installed

Watch it break. Ana changes her mind and uninstalls pip's copy. Now Python can't find requests at all. Yet apt still lists it as installed. dpkg dash dash verify checks the files apt installed against its records, and they're missing.

laptop: a new terminal
sudo pip uninstall -y --break-system-packages requestsFound existing installation: requests 2.34.2
Uninstalling requests-2.34.2:
  Successfully uninstalled requests-2.34.2
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
laptop: a new terminal
python3 -c 'import requests; print(requests.__version__, requests.__file__)'Traceback (most recent call last):
  File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'requests'
laptop: a new terminal
apt list --installed python3-requestsListing... Done
python3-requests/noble-updates,noble-security,now 2.31.0+dfsg-1ubuntu1.2 all [installed]
N: There is 1 additional version. Please use the '-a' switch to see it
laptop: a new terminal
dpkg --verify python3-requests | head -4missing     /usr/lib/python3/dist-packages/requests
missing     /usr/lib/python3/dist-packages/requests/__init__.py
missing     /usr/lib/python3/dist-packages/requests/__version__.py
missing     /usr/lib/python3/dist-packages/requests/_internal_utils.py

To repair it, reinstall the apt package. Python finds 2.31.0 again, in the folder apt uses.

laptop: a new terminal
sudo apt install --reinstall -y python3-requestsReading package lists... Done
Building dependency tree... Done
Reading state information... Done
0 upgraded, 0 newly installed, 1 reinstalled, 0 to remove and 5 not upgraded.
Need to get 50.8 kB of archives.
After this operation, 0 B of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu noble-updates/main amd64 python3-requests all 2.31.0+dfsg-1ubuntu1.2 [50.8 kB]
Fetched 50.8 kB in 0s (143 kB/s)
debconf: delaying package configuration, since apt-utils is not installed

(Reading database ... 11454 files and directories currently installed.)
Preparing to unpack .../python3-requests_2.31.0+dfsg-1ubuntu1.2_all.deb ...
Unpacking python3-requests (2.31.0+dfsg-1ubuntu1.2) over (2.31.0+dfsg-1ubuntu1.2) ...
Setting up python3-requests (2.31.0+dfsg-1ubuntu1.2) ...
laptop: a new terminal
python3 -c 'import requests; print(requests.__version__, requests.__file__)'2.31.0 /usr/lib/python3/dist-packages/requests/__init__.py

So compare the two ways to get a newer requests. The override changes the Python Ubuntu itself runs on; a venv changes only your folder. The override hides changes from apt; a venv leaves apt in charge. And undoing a mistake in a venv means deleting one folder.

A newer requests, two ways: comparison table of --break-system-packages vs A venv, Changes, apt knows?, Ubuntu's tools, Undo

Keep it from coming back

To keep this from coming back, treat the environment as disposable. Ana deletes it, then rebuilds it from requirements.txt in one line. The same versions come back, and the script runs, with the environment's Python. Calling its own pip and python by their path works without activating it.

laptop: a new terminal
rm -rf .venv
laptop: a new terminal
python3 -m venv .venv && .venv/bin/pip install -r requirements.txtCollecting certifi==2026.10.23 (from -r requirements.txt (line 1))
  Using cached certifi-2026.10.23-py3-none-any.whl.metadata (2.5 kB)
Collecting charset-normalizer==3.5.2 (from -r requirements.txt (line 2))
  Using cached charset_normalizer-3.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (46 kB)
Collecting idna==3.20 (from -r requirements.txt (line 3))
  Using cached idna-3.20-py3-none-any.whl.metadata (7.2 kB)
Collecting requests==2.34.2 (from -r requirements.txt (line 4))
  Using cached requests-2.34.2-py3-none-any.whl.metadata (4.8 kB)
Collecting urllib3==2.8.0 (from -r requirements.txt (line 5))
  Using cached urllib3-2.8.0-py3-none-any.whl.metadata (7.4 kB)
Using cached certifi-2026.10.23-py3-none-any.whl (135 kB)
Using cached charset_normalizer-3.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (257 kB)
Using cached idna-3.20-py3-none-any.whl (69 kB)
Using cached requests-2.34.2-py3-none-any.whl (73 kB)
Using cached urllib3-2.8.0-py3-none-any.whl (135 kB)
Installing collected packages: urllib3, idna, charset-normalizer, certifi, requests
Successfully installed certifi-2026.10.23 charset-normalizer-3.5.2 idna-3.20 requests-2.34.2 urllib3-2.8.0
laptop: a new terminal
.venv/bin/python prices.pyEarl Grey    $6.50
Sencha       $8.00
Chai         $5.75

Four habits stop this error for good. Give every project its own virtual environment. Keep requirements.txt with your code, and leave the environment's folder out of Git. Install command-line apps with pipx. And never run sudo pip.

Habits that prevent it: cards of A venv per project, Save the versions, Apps with pipx, Never sudo pip

Recap

So: externally managed means apt owns the system's Python, and pip won't fight it. For a common library, install the apt package. For your own projects, use a venv. For command-line apps, use pipx. And leave break system packages alone.

If this got your first pip install working, a subscribe helps us make more real Python demos.

Every command and output in this video came from a real run, on Ubuntu 24.04.5 LTS, with Python 3.12.3. Thanks for watching Cloud Explainers.

Sources

Demos: a stock Ubuntu 24.04 container on our own lab machine, standing in for a new laptop; the tea shop and its prices are made up. Watch the full video on YouTube.

More videos

Databases & SQL11 min

How a NoSQL Database Like MongoDB Works

Databases & SQL9 min

How to Fix MySQL Too Many Connections Database Errors

Go (Golang)9 min

Golang Concurrency Will Finally Make Sense After This

AWS10 min

How Amazon S3 Works

Kubernetes10 min

How Kubernetes Works

Docker10 min

How to Fix Docker Port Already Allocated Errors

PostgreSQL10 min

How PostgreSQL Works

Git8 min

How to Fix Git Merge Conflicts

Linux9 min

How to Fix No Space Left on Device in Linux

Networking10 min

How the Internet Works

Databases & SQL9 min

SQL vs NoSQL: When Each One Wins

Auth (OAuth, JWT)8 min

How to Fix JWT Token Expired Errors (401 Unauthorized)

Claude & the Claude Developer Platform9 min

Claude API vs Claude Pro: Why Your Subscription Doesn't Include It

AI agent skills9 min

Why AI Agent Skills Beat Giant Prompts

Linux10 min

Linux Cron Jobs Will Finally Make Sense After This

Cybersecurity10 min

Security Logs Will Finally Make Sense After This

Cybersecurity8 min

How to Fix a Security Leak: An API Key Pushed to GitHub

DNS9 min

DNS Will Finally Make Sense After This

Cybersecurity9 min

Never Ignore a Security Update

Auth (OAuth, JWT)9 min

Two-Factor Authentication Will Finally Make Sense

Networking8 min

Never Expose a Database to the Internet

Cybersecurity10 min

SSH Keys vs Passwords: Which Is Safer for Security?

Linux10 min

The 5 Levels of Linux Boot: From Power On to Login

Networking12 min

The 5 Levels of Network Firewalls

Cybersecurity10 min

Password Security: Why Hackers Crack Weak Passwords

Linux9 min

Why Your Linux Server Runs Out of Memory

Networking10 min

HTTP vs HTTPS: What Actually Changes on Your Network

Linux12 min

Every Linux File Permission You Need to Know

Linux10 min

Linux Processes vs Threads: When Each One Wins

Networking11 min

The OSI Model Will Finally Make Sense After This

Git11 min

The 7 Levels of Git

Networking10 min

Every Network Troubleshooting Command You Need

Cybersecurity10 min

Hashing vs Encryption in Cybersecurity: When Each One Wins

Networking11 min

Why the Internet Ran Out of IP Addresses

Linux11 min

Linux Package Managers: apt vs Snap vs Flatpak

Python9 min

Why Python Is Slow (and When It Doesn't Matter)

Networking11 min

TCP vs UDP: When Each One Wins

Linux8 min

Stop Running Linux as Root (Do This Instead)

Linux11 min

Linux Users and Groups Will Finally Make Sense

Cybersecurity9 min

Why Phishing Beats Good Security (and How to Spot It)

Docker12 min

The 7 Levels of Docker

AI agents & MCP12 min

AI Agents Will Finally Make Sense After This

Git8 min

Stop Using git push --force (Use This Instead)

AI agent skills10 min

Agent Skills vs MCP: When Each One Wins

Networking12 min

Networking Will Finally Make Sense After This

Linux12 min

Every Linux Command You Actually Need

Cybersecurity12 min

The 7 Levels of Cybersecurity

System design12 min

Every System Design Concept You Need, in One Diagram

PostgreSQL10 min

Why PostgreSQL Ignores Your Index