cheat sheet

Python Installation

Install Python 3 inside WSL2/Ubuntu via apt, the deadsnakes PPA, or pyenv. Covers the python3-venv quirk and version aliasing.

Python Installation — WSL Ubuntu

Prerequisites

Ensure WSL2 is installed and you have an Ubuntu distro running. Update packages first:

bash
sudo apt update && sudo apt upgrade -y

Method 1 — apt (quickest for system Python)

Ubuntu ships Python 3, but often an older minor version. This is fine for scripts; for projects pin the version explicitly.

bash
sudo apt install python3 python3-pip python3-venv -y

Output:

text
Reading package lists... Done
Building dependency tree... Done
The following NEW packages will be installed:
  python3 python3-pip python3-venv
0 upgraded, 3 newly installed, 0 to remove and 0 not upgraded.

The python3-venv gappython3 -m venv fails with ensurepip is not available if python3-venv is not installed separately. Always run sudo apt install python3-venv even if python3 is already present.

Method 2 — deadsnakes PPA (specific newer version)

The deadsnakes PPA provides newer Python versions not yet in the Ubuntu main repos:

bash
sudo add-apt-repository ppa:deadsnakes/ppa -y
sudo apt update
sudo apt install python3.12 python3.12-venv python3.12-dev -y

Output:

text
Get:1 http://ppa.launchpad.net/deadsnakes/ppa/ubuntu jammy/main amd64 python3.12 amd64 3.12.3-1+jammy1 [621 kB]
...
Setting up python3.12 (3.12.3-1+jammy1) ...

Method 3 — pyenv (manage multiple versions)

pyenv lets you install and switch between any Python version without affecting system Python:

bash
# Install pyenv dependencies
sudo apt install -y make build-essential libssl-dev zlib1g-dev \
  libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \
  libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev \
  libffi-dev liblzma-dev

# Install pyenv
curl https://pyenv.run | bash

# Add to ~/.bashrc or ~/.zshrc
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(pyenv init -)"' >> ~/.bashrc
source ~/.bashrc

# Install Python
pyenv install 3.12.3
pyenv global 3.12.3

Output:

text
Downloading Python-3.12.3.tar.xz...
Installing Python-3.12.3...
Installed Python-3.12.3 to /home/alice/.pyenv/versions/3.12.3

Set python3.12 as the default python3

If you installed via deadsnakes and want python3 to point to 3.12:

bash
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1
sudo update-alternatives --config python3

Output:

text
There are 2 choices for the alternative python3:

  Selection    Path                      Priority   Status
------------------------------------------------------------
* 0            /usr/bin/python3.10        2         auto mode
  1            /usr/bin/python3.10        2         manual mode
  2            /usr/bin/python3.12        1         manual mode

Press <enter> to keep the current choice, or type selection number: 2
update-alternatives: using /usr/bin/python3.12 to provide /usr/bin/python3

Do not point the system python3 away from the distro default on Ubuntu — some system tools (e.g. apt, do-release-upgrade) depend on the original interpreter. Use update-alternatives carefully, or prefer pyenv/venv instead.

Verify

bash
python3 --version
python3 -m pip --version
python3 -c "import venv; print('venv OK')"

Output:

text
Python 3.12.3
pip 24.0 from /usr/lib/python3/dist-packages/pip (python 3.12)
venv OK

Next steps

bash
python3 -m venv .venv
source .venv/bin/activate

See Virtual Environments for the full guide.

WSL setup essentials

WSL2 is the supported WSL flavor for Python development — it is a real Linux kernel running on a lightweight Hyper-V VM, with file-system performance much closer to native Linux than WSL1. Confirm you are on WSL2, that virtualisation is enabled in BIOS, and that the kernel is current before installing Python.

powershell
# In an elevated Windows PowerShell
wsl --install                          # first-time install
wsl --set-default-version 2            # ensure WSL2 is the default
wsl --update                           # bring the kernel up to date
wsl --list --verbose                   # show distros + their version
wsl --install Ubuntu-24.04             # install a specific distro

Output:

text
  NAME            STATE           VERSION
* Ubuntu-24.04    Running         2
  Ubuntu-22.04    Stopped         2

If wsl --list --verbose shows VERSION 1 next to your distro, convert it: wsl --set-version Ubuntu-24.04 2. WSL1 lacks the proper kernel features and Python performance suffers.

Method — uv python install

uv is the fastest path on WSL because it bypasses the Linux build dependencies (build-essential, libssl-dev, libsqlite3-dev, ...) that pyenv needs. uv downloads a precompiled, statically-linked CPython tarball under ~/.local/share/uv/python/ and is ready in 1–2 seconds.

bash
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
exec $SHELL    # reload PATH

# Install Pythons
uv python install 3.12
uv python install 3.11 3.13

# Pin the current project
uv python pin 3.12
cat .python-version

# Use without touching system PATH
uv venv --python 3.12
uv run python --version

Output:

text
Installed Python 3.12.3 in 1.34s
 + cpython-3.12.3-linux-x86_64-gnu (python3.12)

uv's Pythons live entirely under ~/.local/share/uv/python/. They never touch /usr/bin, never trigger PEP 668 errors, and never break apt — making uv the safest choice for WSL development.

Pairing with the Windows side

WSL Python and Windows Python are completely separate installations. They have different filesystems, different PATH, different interpreter binaries, and different package caches. For most workflows, pick one side and do all your Python development there.

Recommendations:

WorkflowPick
Web backend, CLI tools, data science, ML, scriptingWSL — Linux is what production runs on
Windows-native GUI apps (PyQt with native theming, py2exe)Windows
Desktop apps that need Win32 APIsWindows
Office automation (pywin32, COM)Windows
Everything elseWSL
bash
# From WSL — call Windows Python (note the .exe suffix)
python.exe --version
python.exe -c "import sys; print(sys.executable)"

# From WSL — open a Windows-side script
python.exe '/mnt/c/Users/Alice/code/winapp/main.py'

# From Windows PowerShell — call WSL Python
wsl -- python3 --version
wsl -- python3 ~/code/linapp/main.py

Output:

text
Python 3.12.3
C:\Users\Alice\AppData\Local\Programs\Python\Python312\python.exe
Python 3.12.3

Never share a virtual environment across the WSL/Windows boundary. The .venv directory contains interpreter binaries (.exe on Windows, ELF on Linux) and activation scripts (.ps1 vs .sh). They are mutually incompatible — pick one side per project.

Performance — work in ~, not /mnt/c

WSL2's biggest performance gotcha is file IO across the WSL/Windows boundary. The Windows C:\ drive is mounted at /mnt/c inside WSL via the 9P protocol, which is dramatically slower than the WSL native filesystem at ~.

Operation~ (ext4)/mnt/c (9P)Slowdown
git clone of a small repo1.2 s9.8 s~8×
pip install of pandas + numpy12 s95 s~8×
find . -name '*.py' over 10k files0.3 s4.1 s~14×
python -m pytest with file watch0.6 s12 s~20×
bash
# Right — work entirely inside WSL's native filesystem
cd ~/code/my-project
git clone https://github.com/alicedev/my-project.git
python3 -m venv .venv

# Wrong — operating on Windows-side files
cd /mnt/c/Users/Alice/code/my-project          # slow
python3 -m venv .venv                          # slower still

If you must keep code on the Windows side (because someone else's tooling expects it there), edit it via VS Code's WSL remote extension instead of running Python over /mnt/c. VS Code opens the workspace as \\wsl$\Ubuntu\home\alice\... which uses the same fast filesystem.

VS Code integration

The Remote — WSL extension makes VS Code act like it is running inside the WSL Linux distro. The Python extension picks up WSL-side interpreters, lints, debuggers, and venvs natively.

bash
# From inside WSL, open the current directory in VS Code Windows
code .

# This opens VS Code on Windows but with a WSL backend.
# The terminal, debugger, and Python interpreter all run inside WSL.
jsonc
// .vscode/settings.json — relative paths work because the venv is in the workspace
{
  "python.defaultInterpreterPath": ".venv/bin/python",
  "python.terminal.activateEnvironment": true,
  "python.analysis.typeCheckingMode": "basic"
}

Press Ctrl+Shift+P → "Python: Select Interpreter" to pick which Python VS Code uses. You will see your WSL pyenv versions, your uv-managed Pythons, and the system /usr/bin/python3 — pick the one matching your .python-version or .venv.

Shared venvs and code organisation

Two patterns work well in WSL:

Pattern A — code in WSL, venv in WSL (recommended):

bash
~/code/my-project/
├── .venv/                  # WSL-side Linux venv
├── pyproject.toml
└── src/

Everything runs at native ext4 speed. VS Code via the WSL extension is happy.

Pattern B — code on Windows (/mnt/c), separate venvs per side:

text
C:\Users\Alice\code\my-project\
├── .venv-win\              # Windows-side venv (activated from PowerShell)
├── .venv-wsl\              # WSL-side venv (activated from bash)
└── pyproject.toml

Add both .venv-win/ and .venv-wsl/ to .gitignore. Each side has its own activation:

powershell
# Windows
.venv-win\Scripts\Activate.ps1
bash
# WSL
source .venv-wsl/bin/activate

Pattern B exists to support legacy projects only. Pattern A (everything in WSL) is faster, simpler, and what most modern Python tooling expects.

Method — pipx for global CLI tools

pipx installs each Python CLI tool (httpie, black, ruff, mypy, etc.) into its own isolated venv under ~/.local/pipx/venvs/ and shims the entry point into ~/.local/bin/. This sidesteps PEP 668 entirely.

bash
sudo apt install pipx
pipx ensurepath
exec $SHELL

# Install some common Python CLIs
pipx install httpie
pipx install ruff
pipx install black

# List what's installed
pipx list

# Upgrade everything
pipx upgrade-all

Output:

text
installed package httpie 3.2.2, installed using Python 3.12.3
  These apps are now globally installed
    - http
    - https
    - httpie
done! ✨ 🌟 ✨
venvs are in /home/alice/.local/pipx/venvs
   package httpie 3.2.2, installed using Python 3.12.3
   package ruff 0.4.4, installed using Python 3.12.3

pipx is the modern answer to "I just want mypy on PATH" — without breaking the system Python and without spamming ~/.local/lib/python3.12/site-packages with a tangle of dependencies.

Networking and proxies

WSL2's networking is virtualised — localhost works in both directions, but the WSL IP changes on each reboot. Behind a corporate proxy, you may need to configure both apt and pip separately.

bash
# Apt proxy
echo 'Acquire::http::Proxy "http://proxy.example.com:8080";' | \
  sudo tee /etc/apt/apt.conf.d/proxy.conf

# Pip proxy (per user)
mkdir -p ~/.pip
cat > ~/.pip/pip.conf <<EOF
[global]
index-url = https://pypi.internal.example.com/simple/
trusted-host = pypi.internal.example.com
proxy = http://proxy.example.com:8080
EOF

# Env-var fallback for one-off commands
export HTTPS_PROXY=http://proxy.example.com:8080
export HTTP_PROXY=http://proxy.example.com:8080

The Windows side and the WSL side have separate proxy configurations. Setting the proxy in Windows Internet Options does not propagate to WSL automatically. Tools like wsl-vpnkit help when your corporate VPN intercepts WSL traffic.

SSH agent forwarding

A practical detail: if you git clone git@github.com:... from WSL, you want your Windows-side SSH agent (or 1Password / Pageant) to handle the keys — not duplicate them inside WSL.

bash
# Install npiperelay on Windows, then in ~/.bashrc:
export SSH_AUTH_SOCK=$HOME/.ssh/agent.sock
ss -a | grep -q $SSH_AUTH_SOCK || \
  (rm -f $SSH_AUTH_SOCK; setsid socat UNIX-LISTEN:$SSH_AUTH_SOCK,fork \
    EXEC:"npiperelay.exe -ei -s //./pipe/openssh-ssh-agent",nofork &)
bash
ssh-add -l    # should list keys from your Windows agent
git clone git@github.com:alicedev/private-repo.git

systemd inside WSL

Modern WSL2 (Windows 11 22H2+, recent kernel) supports systemd, which matters because some Python tooling (gunicorn-as-a-service, redis-server, postgresql) expects systemd. Enable it once:

bash
# /etc/wsl.conf inside WSL
sudo tee /etc/wsl.conf <<EOF
[boot]
systemd=true

[network]
generateResolvConf=true

[interop]
appendWindowsPath=false
EOF
powershell
# From Windows
wsl --shutdown
# Re-launch your WSL distro
wsl
bash
# Confirm systemd is PID 1
ps -p 1 -o comm=
systemctl is-system-running

Output:

text
systemd
running

appendWindowsPath=false in /etc/wsl.conf removes Windows directories from your WSL $PATH. This is strongly recommended — it stops python.exe from accidentally winning over python3, and it dramatically speeds up tab completion in bash.

Verification recipes

After any install, run these to confirm the result.

bash
# What is python3 resolving to?
which -a python3
type python3

# Version
python3 --version

# Is pip wired to the same interpreter?
python3 -m pip --version

# Does the venv module work?
python3 -m venv /tmp/_probe && rm -rf /tmp/_probe && echo 'venv OK'

# Does SSL work?
python3 -c "import ssl; print(ssl.OPENSSL_VERSION)"

# Is the build complete?
python3 -c "import bz2, lzma, ctypes, hashlib, ssl, sqlite3, zlib, readline; print('stdlib OK')"

# Can pip reach PyPI?
python3 -m pip install --dry-run --user requests

# Is the WSL ↔ Windows bridge working?
python.exe --version

Output:

text
/home/alice/.local/share/uv/python/cpython-3.12.3-linux-x86_64-gnu/bin/python3
Python 3.12.3
pip 24.0 from /home/alice/.local/share/uv/python/cpython-3.12.3-linux-x86_64-gnu/lib/python3.12/site-packages/pip (python 3.12)
venv OK
OpenSSL 3.0.13 30 Jan 2024
stdlib OK
Would install requests-2.32.3 ...
Python 3.12.3

Troubleshooting

SymptomCauseFix
python3 -m venv .venv says ensurepip is not availableMissing python3-venvsudo apt install python3-venv (or python3.12-venv for the deadsnakes version)
pip install says error: externally-managed-environmentPEP 668 enforced on Ubuntu 23.04+Use a venv, or install pipx, or use uv
pip install is extremely slowProject is on /mnt/c not ~Move the project to ~/code/...
import ssl fails after pyenv installBuild deps missingsudo apt install libssl-dev, pyenv uninstall 3.12.3 && pyenv install 3.12.3
Tools like code, git, python.exe not found from WSLappendWindowsPath=false removed them — was that intended?Either re-enable it in /etc/wsl.conf or call them with full paths
localhost:8000 from Windows doesn't reach Flask running in WSLWindows host networking not configuredNewer WSL versions auto-forward; otherwise add networkingMode=mirrored to .wslconfig
pip install for a C-extension package fails with gcc: command not foundNo build toolchainsudo apt install build-essential python3-dev
Git complains about CRLF on files edited from WindowsLine-ending normalisationgit config --global core.autocrlf input on the WSL side
WSL distro won't start after a Windows updateKernel mismatchwsl --update from elevated PowerShell

Common pitfalls (extended)

Mixing Windows Python and WSL Python on PATH — If appendWindowsPath=true (the default) and you have Windows Python installed, python.exe may shadow Linux python3. Set appendWindowsPath=false and call Windows tools with their full names (python.exe, code.exe) when needed.

PEP 668 and apt install python3-<pkg> — Some Python packages have apt equivalents (python3-numpy, python3-pandas). These bypass PEP 668 but install system-wide and may be older than PyPI. Inside a venv, use pip install; outside, decide consciously between the two.

WSL clock drift — On suspend/resume, WSL's clock can drift seconds-to-minutes behind real time, breaking TLS handshakes with cryptic errors. sudo hwclock -s or sudo ntpdate -u time.cloudflare.com resyncs.

Back up your WSL distro before major changes: wsl --export Ubuntu-24.04 D:\backups\ubuntu.tar and restore with wsl --import Ubuntu-24.04 D:\WSL\Ubuntu D:\backups\ubuntu.tar.

Real-world recipes

Fresh WSL Ubuntu 24.04 in five commands

bash
sudo apt update && sudo apt upgrade -y
sudo apt install -y python3 python3-venv python3-pip pipx git
pipx ensurepath
pipx install uv
uv python install 3.12 3.13

Newest Python via deadsnakes on Ubuntu 22.04

bash
sudo add-apt-repository ppa:deadsnakes/ppa -y
sudo apt update
sudo apt install -y python3.13 python3.13-venv python3.13-dev
python3.13 -m venv .venv
source .venv/bin/activate

Reproducible project bootstrap

bash
mkdir ~/code/my-project && cd ~/code/my-project
uv init --python 3.12
uv add fastapi 'uvicorn[standard]'
uv add --dev pytest ruff mypy
uv run pytest

Connecting a Windows browser to a Flask app running in WSL

bash
# WSL side
source .venv/bin/activate
python -m flask --app app run --host 0.0.0.0 --port 8000
powershell
# Windows side — modern WSL with mirrored networking just works
curl http://localhost:8000/
# Otherwise grab the WSL IP
wsl hostname -I

CI parity — run tests under multiple Pythons locally

bash
uv python install 3.11 3.12 3.13
nox -s tests           # or tox -e py311,py312,py313

Cleaning up

bash
# uv-managed Pythons
uv python uninstall 3.13

# apt-installed
sudo apt remove --purge python3.12 python3.12-* && sudo apt autoremove

# pyenv-installed
pyenv uninstall 3.12.3

# Nuke the entire distro and start over (irreversible)
wsl --unregister Ubuntu-24.04
wsl --install Ubuntu-24.04

Next steps

bash
python3 -m venv .venv
source .venv/bin/activate

See Virtual Environments for the full guide, WSL interop for shell bridging tips, and pip vs uv for the package-manager comparison.