Panellicense

Set up CloudLinux Python Selector on a cPanel server

Let shared-hosting customers pick their own Python runtime per app — Django, Flask, or FastAPI on Python 3.9 through 3.12, isolated under CageFS, managed from cPanel. End-to-end in about 30 minutes.

10 min readUpdated 2026-05-16cloudlinux · python · selector · cpanel
schema: HowToschema: FAQPageschema: BreadcrumbList

Python is the awkward middle child of shared cPanel hosting. Most sysadmins either ban it outright ("just use a VPS"), or expose a single system-wide interpreter that breaks the next time CloudLinux pushes a kernel update. CloudLinux's Python Selector solves this the same way PHP Selector and Node.js Selector solve their respective version problems: each cPanel user picks their own runtime, the WSGI app runs isolated inside CageFS via Phusion Passenger, and Apache or LiteSpeed proxies the public URL to the right user-space process.

This guide walks the full install — alt-python packages, Passenger wiring, the cPanel Setup Python App UI, and the per-user limits you need before customers start pushing Django projects. Budget about 30 minutes on a 4-vCPU VPS, most of it the EasyApache 4 rebuild.

Prerequisites

You need:

  • CloudLinux installed with an active license. If you're starting from a stock AlmaLinux or CentOS 7 box, follow installing CloudLinux on cPanel first.
  • CageFS enabled and initialised. Python Selector mounts alt-python paths into each user's cage — without CageFS, cloudlinux-selector enable --interpreter=python returns an error and the cPanel icon never appears.
  • Apache with mod_lsapi and mod_alt_passenger, or LiteSpeed Web Server 5.4+. The default mod_ruid2 handler does not work for Python — Passenger needs to spawn per-user WSGI processes that ruid2 can't manage.
  • At least 800 MB free in /opt/alt/ per Python minor version installed.
  • LVE memory limits bumped to at least 1.5 GB PMEM. Django's collectstatic and FastAPI's reload-on-change watcher both routinely peak above 1 GB during startup, and a tight limit kills them with no useful error in the user's terminal.

Step 1 — Install the alt-python package set

CloudLinux ships every supported Python as a separate alt-python<MAJOR><MINOR> package, independent of the system Python that cPanel itself depends on:

yum install alt-python39 alt-python310 alt-python311 alt-python312

The full LTS-and-current set takes about 1.6 GB on disk. If you want every version including older ones for customers migrating in legacy Django 3.x apps, use the group:

yum groupinstall alt-python

Confirm what landed and which version is the default:

cloudlinux-selector list --interpreter=python --json | jq '.data.versions'

You should see one entry per installed minor version with installed: true and exactly one with default: true. That default is what new apps get when the user clicks Create Application without changing the dropdown — pick a version your customers will actually want before going live.

Step 2 — Install and enable Passenger

Python Selector uses the same patched Phusion Passenger as Node.js Selector, so if you've already set up Node.js apps this step is already done — skip to step 3. Otherwise:

yum install ea-apache24-mod_alt_passenger

Then in WHM under EasyApache 4 → Customize → Apache Modules, tick mod_alt_passenger and rebuild. Or from the CLI:

ea_install_profile --install /etc/cpanel/ea4/profiles/cpanel/default.json

Verify Passenger is loaded:

httpd -M 2>/dev/null | grep passenger

Expected output: alt_passenger_module (shared). If empty, the rebuild didn't pick up the module — check /etc/cpanel/ea4/ea4.conf for the mod_alt_passenger entry and rebuild again.

Step 3 — Enable Python Selector

One command:

cloudlinux-selector enable --interpreter=python

This:

  • Registers the Setup Python App icon in cPanel under the Software section.
  • Mounts /opt/alt/alt-python* paths read-only into every CageFS user.
  • Writes per-user shell wrappers so python, python3, pip, and virtualenv resolve to the user-selected version inside SSH.

Restart cpsrvd so the new icon appears immediately:

service cpsrvd restart

Step 4 — Set sensible defaults

The defaults file at /etc/cloudlinux-selector/python.json controls what new apps get when the user doesn't override:

nano /etc/cloudlinux-selector/python.json

A working 2026 default:

{
  "default_version": "3.11",
  "allowed_versions": ["3.9", "3.10", "3.11", "3.12"],
  "default_mode": "production",
  "default_passenger": {
    "max_pool_size": 4,
    "min_instances": 0,
    "pool_idle_time": 300
  }
}

min_instances: 0 matters on a shared box — it lets idle Django and Flask apps spin down after five minutes of no traffic rather than holding 80-120 MB of RAM each forever. The cost is a 1-2 second cold start on the next request, which is fine for a hobby site and unacceptable for a high-traffic API. Customers running production workloads can bump min_instances in their own app config.

Apply the defaults:

cloudlinux-selector apply-defaults --interpreter=python

Step 5 — Confirm the cPanel UI works

Log in as a non-root cPanel user, scroll to Software, and click Setup Python App. You should see an empty app list and a Create Application button. Click through with:

  • Python version: 3.11
  • Application mode: Production
  • Application root: pytest
  • Application URL: /pytest
  • Application startup file: passenger_wsgi.py
  • Application entry point: application

Click Create. The selector creates /home/<user>/pytest/, a virtualenv under /home/<user>/virtualenv/pytest/3.11/, and a Passenger config block in the user's vhost.

Drop a minimal passenger_wsgi.py into the application root:

import sys

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return [f'python {sys.version}'.encode()]

Restart the app from the UI (or run cloudlinux-selector restart --interpreter=python --app-root=/home/<user>/pytest --user=<user> as root) and hit https://<user-domain>/pytest — you should see python 3.11.x.

Step 6 — Set per-user limits before customers deploy

Three limit groups matter for Python workloads on shared hosting:

LVE memory — Django's manage.py collectstatic peaks around 1 GB on a medium app; a Pandas-heavy data tool can easily hit 2 GB. The default 1 GB PMEM will OOM-kill these at startup with Killed and nothing in the Passenger log. Bump per-user for known Python customers, or cluster-wide:

lvectl set <username> --pmem=2G --vmem=0

vmem=0 (unlimited) is correct — CPython's allocator reserves large virtual ranges it never commits, and a non-zero vmem causes apparent random MemoryError exceptions during ML library imports.

EP (entry processes) — each running Python app counts as 1 EP, plus one per Passenger worker spawned during a request burst. Default 20 is usually fine for a user running 1-2 apps. Customers running 5+ apps or any FastAPI with high concurrency should be at 40.

I/Opip install of a heavy dependency tree (NumPy, SciPy, Pandas all together) can read 200-300 MB of wheels from the cache and write the same volume into the virtualenv. The default 1 MB/s IO limit turns a 90-second install into 15 minutes. Bump to at least 10 MB/s for users you know will manage real dependencies:

lvectl set <username> --io=10240 --iops=1024

See CloudLinux LVE tuning without angry customers for the full resource model.

Three configuration mistakes that flood support

  1. Forgetting to rebuild Apache after installing mod_alt_passenger. The yum install completes silently, but until EasyApache 4 rebuilds the httpd config no apps will start. The cPanel UI shows the app as "Started" and the public URL returns 503. Always finish with /scripts/restartsrv_httpd after the rebuild.

  2. Setting Application URL to /. Mounting a Python app at the document root makes Passenger handle every request to the domain, including static files the user expects Apache to serve from public_html. This silently breaks any WordPress install or media gallery on the same vhost. Use a subdirectory like /api or /app, or use a dedicated subdomain with the document root pointing at the app's public folder.

  3. Letting users run pip install --user or sudo pip install. Both land outside the virtualenv — --user writes to ~/.local/, which the selector ignores on app spawn, and sudo is blocked by CageFS anyway. Packages installed this way appear to work in the user's SSH session and then ModuleNotFoundError at request time. The activation script the cPanel UI runs sets PIP_USER=0 and PIP_REQUIRE_VIRTUALENV=1, so SSH users hitting this should be told to activate first.

A common gotcha — Django and ALLOWED_HOSTS

A Django app deployed through Python Selector receives requests from Passenger with the upstream Host header preserved, but the WSGIPath Apache sees is the proxy URL, not the public domain. Django's default ALLOWED_HOSTS = [] rejects every request with DisallowedHost and the user sees a generic 500. The fix is to populate ALLOWED_HOSTS with the actual public domain plus localhost:

ALLOWED_HOSTS = [
    'example.com',
    'www.example.com',
    'localhost',
    '127.0.0.1',
]

If you're moving customers in from cPanel's old EasyApache 3 mod_wsgi setup, note that Passenger does not pass mod_wsgi's wsgi.input semantics — file uploads larger than 100 KB need DATA_UPLOAD_MAX_MEMORY_SIZE raised in Django settings, not just at the web-server level.

Logs to keep an eye on

tail -F /var/log/cloudlinux-selector.log
tail -F /usr/local/apache/logs/error_log | grep -i passenger
passenger-status                              # per-app process count and memory
cloudlinux-selector get-status --interpreter=python --user=<user>

A healthy server shows Passenger spawning Python processes on request and reaping them after pool_idle_time. If passenger-status shows dozens of processes per user during quiet hours, min_instances was bumped — check the user's app config in /home/<user>/<app>/passenger_wsgi.py and the global defaults file.

Does Python Selector require CageFS?+
Yes. The selector mounts alt-python paths into the user's cage and spawns WSGI processes via CageFS-aware Passenger. Without CageFS, `cloudlinux-selector enable --interpreter=python` errors out and the Setup Python App icon never appears in cPanel.
Can I run Python Selector without CloudLinux?+
No. The selector is CloudLinux-only — it depends on CageFS, LVE, and the patched alt_passenger module. On stock AlmaLinux or CentOS you have to either run system Python (and accept the version cPanel pins), use pyenv per-user (which breaks Passenger), or move to a Python-native panel like aaPanel.
What Python versions does CloudLinux support in 2026?+
Currently supported: 3.9 (EOL October 2025, kept for legacy migrations), 3.10 (security-fix only through October 2026), 3.11 (active until October 2027), and 3.12 (active until October 2028). End-of-life versions stay in alt-python repos for migration but stop receiving CloudLinux security backports — check `yum list available 'alt-python*'`.
Why does `pip install` fail or hang on my CloudLinux server?+
Two common causes. First, LVE IO limit too low — default 1 MB/s makes heavy dependency trees crawl. Bump to 10 MB/s with `lvectl set <user> --io=10240`. Second, PMEM too low — modern wheels are large and pip's resolver can hit 800 MB+. Set PMEM to at least 2 GB and vmem to 0.
Can users SSH in and run `python` directly?+
Yes, once they source the app's virtualenv: `source /home/<user>/virtualenv/<app>/<ver>/bin/activate`. After that, `python`, `pip`, and any installed CLI tools resolve to the selected version. Without activation, the system PATH gives them a stub that prints help text.
How do I migrate an existing mod_wsgi or Gunicorn app to Python Selector?+
Create a new app in the cPanel UI with the same startup file (rename to `passenger_wsgi.py` if needed), copy the source over, install dependencies from inside the activated virtualenv with `pip install -r requirements.txt`, then switch the public domain to the new app URL. There's no in-place migration — Passenger uses a different process model than Gunicorn.

Next steps

Switch in an afternoon

Switch from your current reseller — free.

We migrate active cPanel, Plesk, LiteSpeed and CloudLinux licenses from any reseller. We prorate the first month so you never pay twice, and your customers see zero downtime during the swap.