Migrate to RunxBuild and earn up to $50 in hosting credit on your first deposit.

Calculate your savings
unxBuild
Back to Blog Troubleshooting

ModuleNotFoundError: No Module Named 'requests' — The Three Real Causes and the One That Catches Even Senior Engineers

Sean

Platform Writer

Jun 17, 2026
8 min read

ModuleNotFoundError: No module named 'requests' almost always means one of three things: the package is not installed for the Python the script is using, the script is running in a virtual environment that does not have requests, or the system Python is in PEP 668 “externally managed” mode and pip install quietly refused to install into it. The reason the question still gets asked in 2026 is that the third one is new. Pre-2023, the canonical Stack Overflow answer of pip install requests worked in every case. Post-PEP 668, the same command fails on a fresh Debian, Ubuntu, or Fedora install with a error: externally-managed-environment message that looks like a permissions error and is actually a policy one.

This post is the three-cause framework. Each cause has a different fix, and the wrong fix is the one that wastes an afternoon. Read the symptoms, jump to the matching cause, run the matching fix.

The interesting thing about this error is that it surfaces something most Python tutorials skip: Python is not one program. It is a binary, a set of standard libraries, a set of site-packages directories, and a sys.path that ties them together. “No module named X” means Python looked in all of those places and did not find X. The reason it did not find X is the interesting question, and it has only three answers.

ModuleNotFoundError: No Module Named 'requests' — The Three Real Causes and the One That Catches Even Senior Engineers

Table of contents

The direct answer

If you want the answer without the framework:

# Are we in a virtual environment?
which python
which pip
# Are they the same?
python -m pip install requests
# Not in a venv? Either create one or break the system-managed lock:
python -m venv .venv
source .venv/bin/activate
python -m pip install requests

The python -m pip form is the version that almost always works. Calling pip directly calls whatever pip is on the PATH, which may not be the pip for the python the script is using. Calling python -m pip uses the Python the script is about to run, so the install lands in the right place.

The rest of the post is what to do when that does not work.

The three real causes

ModuleNotFoundError: No module named 'requests' has exactly three root causes:

  1. The package is genuinely not installed. requests is not in any site-packages directory the running Python looks at.
  2. The package is installed, but for a different Python. The dev has two Pythons (3.11 and 3.12, or a system Python and a Homebrew Python), and requests is installed for the one the script is not using.
  3. The package would be installed, but the system Python is externally managed and refused. This is the new one. PEP 668 made pip install on a system Python a no-op-with-error-message instead of a silent corruption of the system packages.

Most Stack Overflow answers cover only cause 1. The result is that engineers with causes 2 or 3 run pip install requests over and over, get a “successfully installed” message, and then watch the same error come back. The package is installed. It is just installed somewhere the script cannot see it.

Cause 1: the package is not installed

The diagnostic:

python -c "import requests; print(requests.__file__)"

If the answer is ModuleNotFoundError, the package is not installed for this Python. The fix:

python -m pip install requests

This is the textbook answer. It works when the python on the path is the Python the script is using, and when that Python’s site-packages is writable. Both of those are less true than they used to be, but in a plain virtual environment, this is the entire fix.

Verify with the same import requests check. If it works, the error is gone. Move on.

Cause 2: the wrong Python is running

The diagnostic:

which python
which pip
python -c "import sys; print(sys.executable)"

If which python and python -c "import sys; print(sys.executable)" point to different binaries, the system has more than one Python. This is normal on macOS (system Python 2.7 from Apple, Homebrew Python 3.12, a pyenv-managed Python), normal on Linux (system Python 3.11 from apt, a manually compiled Python 3.12), and normal on Windows (the Microsoft Store Python, the python.org Python, a conda Python).

The fix is to install requests for the Python the script is actually using, not for whatever pip happens to be on the path:

# Make sure we're using the right pip
python -m pip install requests

# Or, with the full path
/usr/local/bin/python3.12 -m pip install requests

A useful pattern: alias pip to python -m pip in the shell, so the wrong-pip problem stops being a problem:

# in ~/.zshrc or ~/.bashrc
alias pip='python -m pip'

That is a small change that prevents an entire class of “it works on my machine” bugs.

Cause 3: the system Python is externally managed (PEP 668)

This is the new failure mode, and it is the one that catches people who have been using the same command for a decade.

The error:

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

note: If you believe this is a mistake, please contact your
    operating system distributor with a hint that the package
    manager and pip should support a more permissive coexistence.

What happened: starting with Debian 12, Ubuntu 23.04+, and Fedora 38, the system Python ships with a marker file (EXTERNALLY-MANAGED) that tells pip to refuse to install into the system site-packages. The reasoning is sound — apt install python3-requests is the right way to install requests on a Debian system, and pip install would shadow it. The execution is awkward because the error message reads like a permissions failure.

The fix is one of three, in order of preference:

# Option 1: use a virtual environment (the right answer)
python -m venv .venv
source .venv/bin/activate
python -m pip install requests

# Option 2: install the system package (on Debian/Ubuntu)
sudo apt install python3-requests

# Option 3: override the marker (only if you know what you are doing)
pip install requests --break-system-packages

The right answer is option 1, every time. The reason is that virtual environments make the wrong-Python problem go away entirely. Every Python project should be in a virtual environment, and any project that is not will eventually hit this error, or a worse one.

A useful one-liner for new projects:

python -m venv .venv && source .venv/bin/activate && python -m pip install requests

That creates the venv, activates it, and installs requests in one go. The venv is then part of the project’s .gitignore. The next developer clones, runs source .venv/bin/activate, and gets the same requests install.

The IDE/editor trap that looks like cause 2

A surprisingly common version of this error is the one where the shell command works and the editor does not. The shell sees requests. The script in PyCharm, VS Code, or Cursor does not. The cause is that the editor is configured to use a different Python interpreter than the one in the active shell.

The fix is to point the editor at the same Python the shell is using. In VS Code, the Python: Select Interpreter command lets the developer pick the interpreter from a list. In PyCharm, the project settings have a Python interpreter dropdown. In Cursor, the extension config exposes the same.

Once the editor’s interpreter matches the shell’s Python, the import requests line stops being red, the linter stops being angry, and the run button works.

The test: open a Python file in the editor, run the same import requests line, and see if the editor’s Python resolves it. If it does not, the editor is on the wrong interpreter, and no amount of pip install will fix the issue from the shell.

A one-page diagnostic that fixes 95% of cases

Run these four lines, in order, and the error will resolve to one of the four:

# 1. Which Python is the shell using?
which python
# 2. Which Python is the script actually using?
head -1 script.py   # check the shebang
python -c "import sys; print(sys.executable)"
# 3. Is requests installed for that Python?
python -c "import requests"
# 4. Is pip pointing at the same Python?
python -m pip --version

The output of those four lines tells the whole story:

  • which python and sys.executable agree, and import requests works: the error is not what the traceback says. Check the path the script is being run from and the virtual environment activation.
  • which python and sys.executable agree, and import requests fails: cause 1 or 3. python -m pip install requests in a venv fixes it.
  • which python and sys.executable disagree: cause 2. The script is using one Python; the install is going to another. Use the full path or python -m pip.
  • python -m pip install fails with externally-managed-environment: cause 3. Create a venv.

That four-line check is faster than reading any blog post, including this one. Run it, read the output, fix the matching cause.

The deploy-side version of this error

The same error shows up in production, and the cause is usually different. On a PaaS deploy, the runtime image has a specific Python, a specific list of system packages, and a specific site-packages directory. The build installs requests into the build environment. The runtime is a different container. The runtime’s site-packages does not have requests. The error fires on the first request.

The fix is a requirements.txt (or pyproject.toml) that the deploy installs at build time, and a runtime that uses the same image. The Dockerfile that gets this right:

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]

The requirements.txt contains requests==2.32.3 (or whatever the project uses). The build installs it into the image. The runtime uses the image. The Python is the same. The site-packages is the same. The error cannot happen.

A platform that lets the developer set the Python version, manages a requirements file, and rebuilds the image on dependency changes is the one that turns this class of error from a deploy-day outage into a non-event. The RunxBuild platform is built around that pattern.

For a sanity check on whether the deploy cost matches the deploy capability, the hosting cost calculator is the right tool.

The opinion this post is built on

The reason ModuleNotFoundError: No module named 'requests' is still a top-of-Stack-Overflow question is that Python’s import system is genuinely confusing. The same import line can fail or succeed depending on which Python the shell is using, which Python the script is using, which Python the editor is using, and whether the system Python is externally managed. Four variables, one error message, infinite ways to be confused.

The fix that survives the most cases is the boring one: every Python project lives in a virtual environment, every install goes through python -m pip, and the editor and the shell point at the same Python. None of that is glamorous. All of it is the price of admission for using a language where “the Python” is a moving target.

PEP 668 made the boring fix mandatory instead of optional, which is the right call. The platform layer is where the rest of the discipline lives. A platform that bakes the venv into the build, pins the Python version, installs from a requirements file, and runs the runtime in the same image as the build is a platform where this error class stops being a deploy-day event. Until every team has that platform, the four-line diagnostic is the next best thing.

Run the four lines. Read the output. Fix the matching cause. Move on. The error is a one-cause-per-incident failure, and the cause is almost always one of the three above.

FAQ

Why does pip install requests say “successfully installed” but the import still fails?

Because the pip on the path is not the pip for the Python the script is using. Use python -m pip install requests instead, which uses the pip that matches the python the script will run. Or, check that the python and pip binaries point to the same environment with python -m pip --version and which pip.

What is PEP 668 and why is it breaking my install?

PEP 668 is the Python Enhancement Proposal that made system Python installations refuse pip install by default. The reasoning is that apt install python3-requests is the right way to install requests on a system-managed Python, and pip install would conflict with the package manager. The fix is to use a virtual environment (python -m venv .venv && source .venv/bin/activate) or to install via the system package manager (apt install python3-requests).

Should I use --break-system-packages?

Only if you understand the trade-off and you are sure you are not on a system you share with other users or other tools. The flag overrides the PEP 668 safety net, and the next apt upgrade may overwrite or conflict with the pip-installed package. For a project, use a virtual environment. For a one-shot script on a disposable VM, --break-system-packages is fine.

Why does my code work in the shell but not in my editor?

The editor is using a different Python interpreter than the shell. In VS Code, run Python: Select Interpreter and pick the one that matches the shell’s python. In PyCharm, change the project’s Python interpreter in Settings → Project → Python Interpreter. The shell’s which python output is the answer the editor should match.

How do I install requests in a virtual environment?

python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
python -m pip install requests

The venv is a self-contained Python with its own site-packages. The activation puts the venv’s python and pip on the path. The install lands in the venv, not the system Python. When the project is shipped or shared, the requirements.txt carries the same packages to the next machine.

Can I install requests without a virtual environment?

Yes, on a system without PEP 668 enforcement (older macOS, older Linux, or with --break-system-packages), pip install requests installs into the system Python. This is fine for a personal machine, not fine for a shared system or a project that needs to ship. Every project should be in a venv; the discipline pays for itself the first time the project needs to ship to a different machine.

#modulenotfounderror no module named requests#modulenotfounderror#python requests module#pip install requests#pep 668 externally-managed#python venv