Install Python and a virtual environment

2 min read07 November 2026

Debian 13 ships Python 3.13, but its system installation is protected: global pip install is blocked to avoid breaking system tools. The right approach is one virtual environment per application, with pinned dependencies, then a systemd service that starts the application with that exact Python.

Install Python and venv

Bash
sudo apt update
sudo apt install -y python3 python3-venv python3-pip
python3 --version

python3-venv is a separate package: without it, python3 -m venv fails with ensurepip is not available. Debian enforces PEP 668 and rejects global pip installs, hence the externally-managed-environment error if you try. Do not bypass this protection with --break-system-packages on a production server.

Create the environment

Switch to the deployment user before creating the venv:

Bash
sudo mkdir -p /srv/app
sudo chown deploy:www-data /srv/app
sudo -iu deploy
cd /srv/app
python3 -m venv venv
source venv/bin/activate
python -m pip install --upgrade pip
pip install -r requirements.txt
deactivate

After activation, which python must point to /srv/app/venv/bin/python. Pin versions in requirements.txt (flask==3.1.0) to make deployment reproducible. The venv directory does not go into Git: it is recreated from the requirements file.

systemd service

Create /etc/systemd/system/app.service:

INI
[Unit]
Description=Python application
After=network.target

[Service]
User=deploy
Group=www-data
WorkingDirectory=/srv/app
EnvironmentFile=/etc/app.env
ExecStart=/srv/app/venv/bin/python /srv/app/main.py
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target
Bash
sudo systemctl daemon-reload
sudo systemctl enable --now app
sudo systemctl status app

ExecStart uses the venv Python, not /usr/bin/python3, otherwise installed packages will not be found. EnvironmentFile loads PORT, DATABASE_URL and other variables; the file must be readable by deploy only (chmod 600).

Listening and reverse proxy

The application reads its port from the environment and listens on 0.0.0.0. For Flask or FastAPI, do not run the development server in production: use Gunicorn or Uvicorn behind Nginx. The application port stays local and closed in the firewall; only 80 and 443 are public.

Verify and troubleshoot

journalctl -u app -n 100 --no-pager shows startup errors. Common causes: a wrong path in ExecStart, a module missing from the venv, an unloaded variable, a port already in use (ss -ltnp). Test with curl http://127.0.0.1:PORT/ before concluding on the Nginx side. After pip install -r requirements.txt, restart the service: a reload does not pick up new packages.

Advertisement

Was this article helpful?

Read next