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:
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.
sudo apt install python3 python3-pip python3-venv -y
Output:
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-venvgap —python3 -m venvfails withensurepip is not availableifpython3-venvis not installed separately. Always runsudo apt install python3-venveven ifpython3is already present.
Method 2 — deadsnakes PPA (specific newer version)
The deadsnakes PPA provides newer Python versions not yet in the Ubuntu main repos:
sudo add-apt-repository ppa:deadsnakes/ppa -y
sudo apt update
sudo apt install python3.12 python3.12-venv python3.12-dev -y
Output:
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:
# 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:
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:
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1
sudo update-alternatives --config python3
Output:
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
python3away from the distro default on Ubuntu — some system tools (e.g.apt,do-release-upgrade) depend on the original interpreter. Useupdate-alternativescarefully, or prefer pyenv/venv instead.
Verify
python3 --version
python3 -m pip --version
python3 -c "import venv; print('venv OK')"
Output:
Python 3.12.3
pip 24.0 from /usr/lib/python3/dist-packages/pip (python 3.12)
venv OK
Next steps
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.
# 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:
NAME STATE VERSION
* Ubuntu-24.04 Running 2
Ubuntu-22.04 Stopped 2
If
wsl --list --verboseshows 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.
# 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:
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 breakapt— 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:
| Workflow | Pick |
|---|---|
| Web backend, CLI tools, data science, ML, scripting | WSL — Linux is what production runs on |
| Windows-native GUI apps (PyQt with native theming, py2exe) | Windows |
| Desktop apps that need Win32 APIs | Windows |
Office automation (pywin32, COM) | Windows |
| Everything else | WSL |
# 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:
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
.venvdirectory contains interpreter binaries (.exeon Windows, ELF on Linux) and activation scripts (.ps1vs.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 repo | 1.2 s | 9.8 s | ~8× |
pip install of pandas + numpy | 12 s | 95 s | ~8× |
find . -name '*.py' over 10k files | 0.3 s | 4.1 s | ~14× |
python -m pytest with file watch | 0.6 s | 12 s | ~20× |
# 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.
# 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.
// .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-versionor.venv.
Shared venvs and code organisation
Two patterns work well in WSL:
Pattern A — code in WSL, venv in WSL (recommended):
~/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:
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:
# Windows
.venv-win\Scripts\Activate.ps1
# 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.
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:
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
pipxis the modern answer to "I just wantmypyon PATH" — without breaking the system Python and without spamming~/.local/lib/python3.12/site-packageswith 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.
# 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-vpnkithelp 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.
# 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 &)
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:
# /etc/wsl.conf inside WSL
sudo tee /etc/wsl.conf <<EOF
[boot]
systemd=true
[network]
generateResolvConf=true
[interop]
appendWindowsPath=false
EOF
# From Windows
wsl --shutdown
# Re-launch your WSL distro
wsl
# Confirm systemd is PID 1
ps -p 1 -o comm=
systemctl is-system-running
Output:
systemd
running
appendWindowsPath=falsein/etc/wsl.confremoves Windows directories from your WSL$PATH. This is strongly recommended — it stopspython.exefrom accidentally winning overpython3, and it dramatically speeds up tab completion in bash.
Verification recipes
After any install, run these to confirm the result.
# 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:
/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
| Symptom | Cause | Fix |
|---|---|---|
python3 -m venv .venv says ensurepip is not available | Missing python3-venv | sudo apt install python3-venv (or python3.12-venv for the deadsnakes version) |
pip install says error: externally-managed-environment | PEP 668 enforced on Ubuntu 23.04+ | Use a venv, or install pipx, or use uv |
pip install is extremely slow | Project is on /mnt/c not ~ | Move the project to ~/code/... |
import ssl fails after pyenv install | Build deps missing | sudo apt install libssl-dev, pyenv uninstall 3.12.3 && pyenv install 3.12.3 |
Tools like code, git, python.exe not found from WSL | appendWindowsPath=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 WSL | Windows host networking not configured | Newer WSL versions auto-forward; otherwise add networkingMode=mirrored to .wslconfig |
pip install for a C-extension package fails with gcc: command not found | No build toolchain | sudo apt install build-essential python3-dev |
| Git complains about CRLF on files edited from Windows | Line-ending normalisation | git config --global core.autocrlf input on the WSL side |
| WSL distro won't start after a Windows update | Kernel mismatch | wsl --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.exemay shadow Linuxpython3. SetappendWindowsPath=falseand 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, usepip 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 -sorsudo ntpdate -u time.cloudflare.comresyncs.
Back up your WSL distro before major changes:
wsl --export Ubuntu-24.04 D:\backups\ubuntu.tarand restore withwsl --import Ubuntu-24.04 D:\WSL\Ubuntu D:\backups\ubuntu.tar.
Real-world recipes
Fresh WSL Ubuntu 24.04 in five commands
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
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
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
# WSL side
source .venv/bin/activate
python -m flask --app app run --host 0.0.0.0 --port 8000
# 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
uv python install 3.11 3.12 3.13
nox -s tests # or tox -e py311,py312,py313
Cleaning up
# 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
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.