If you've built a Django or Flask app and your only hosting experience so far is shared cPanel plans, the jump can feel confusing. There's no gunicorn command to run, no systemd service to write — cPanel expects you to use its Setup Python App tool, which wires your code into Apache through Passenger. Get the file layout wrong and you'll stare at a 500 error with no obvious clue why.

This walkthrough covers the actual deployment steps, the errors that trip people up most (missing passenger_wsgi.py, wrong virtualenv activation, static files 404ing), and how to keep the app updated without breaking it every time you push new code.

What Setup Python App Actually Does

cPanel's Python App tool isn't a generic Python runner — it's a wrapper around Phusion Passenger, the same module that also runs Node.js and Ruby apps on shared and VPS cPanel servers. When you create an app through it, cPanel:

  • Creates a dedicated virtual environment under virtualenv/<domain>/<version>/
  • Adds an Apache/LiteSpeed handler that routes requests for your chosen path to Passenger
  • Generates a passenger_wsgi.py stub in your app's root folder
  • Gives you a "source virtualenv" activation command for SSH sessions

Everything downstream — your Django wsgi.py, Flask app object, installed packages — has to line up with what Passenger expects, or it silently fails and falls back to a generic error page.

Step 1: Create the Application in cPanel

Log in to cPanel and open Setup Python App (search for it if you don't see the icon). Click Create Application and fill in:

  • Python version — pick the newest available (3.11 or 3.12 if your host offers it) unless a dependency pins you to an older release
  • Application root — a folder outside public_html, e.g. myapp (cPanel creates it for you)
  • Application URL — the domain or subdomain that should serve it
  • Application startup file — leave as passenger_wsgi.py
  • Application Entry point — usually application

Click Create. cPanel shows you a command like this near the top of the page — copy it, you'll need it every time you touch this app over SSH:

source /home/username/virtualenv/myapp/3.11/bin/activate && cd /home/username/myapp

Step 2: Upload Your Code and Install Dependencies

Upload your project into the application root via SFTP, Git, or File Manager. Then SSH in, run the activate command cPanel gave you, and install requirements inside that environment — not with a bare pip install, which would hit the system Python and do nothing useful:

source /home/username/virtualenv/myapp/3.11/bin/activate
cd /home/username/myapp
pip install -r requirements.txt

This is the single most common mistake we see: someone SSHs in, runs pip3 install django gunicorn without activating the virtualenv first, then wonders why cPanel still throws ModuleNotFoundError: No module named 'django'. If you don't see (myapp) or similar in your shell prompt, the virtualenv isn't active.

Step 3: Wire Up passenger_wsgi.py

cPanel drops a placeholder passenger_wsgi.py in your app root. For Flask, replace it with something like:

import sys, os
sys.path.insert(0, os.path.dirname(__file__))

from myapp import app as application

For Django, point it at your project's WSGI application instead:

import os, sys
sys.path.insert(0, os.path.dirname(__file__))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

Passenger looks specifically for a variable named application in this file. Name it anything else and you'll get a 500 with nothing useful in the browser — you have to check the Passenger log to find out why.

Symptom: 500 Error With No Details

Cause: almost always a Python-level exception happening before Passenger can even return a response — a bad import, a missing environment variable, or a settings file that expects a database connection that isn't configured yet.

Fix: check the Passenger log, not the general Apache error log. It's usually at:

/home/username/myapp/passenger.log
# or, depending on config:
~/logs/passenger.log

If it's empty, tail the domain's main error log instead:

tail -50 /home/username/logs/yourdomain.com/error_log

For Django specifically, temporarily set DEBUG = True in settings and reload — but flip it back to False before you forget, since a public Django debug page leaks your settings, installed apps, and often your secret key.

Symptom: Static Files (CSS/JS) Return 404

Cause: Passenger only serves your WSGI app — it has no idea where your static assets live unless you tell Apache/cPanel explicitly. Django's collectstatic output and Flask's static/ folder both need a mapping.

Fix: in Setup Python App, there's a "Static Files" section per application — add a mapping from a URL path like /static/ to the actual folder (e.g. myapp/staticfiles). For Django, run collectstatic first:

python manage.py collectstatic --noinput

Then point the static mapping at STATIC_ROOT. Skipping this step is the #2 reason people think "deployment worked" but the site looks broken — the app runs fine, it's just unstyled.

Symptom: Changes Don't Show Up After You Update Code

Cause: Passenger caches the running application process. Editing files doesn't restart it automatically.

Fix: touch the restart file, or use the "Restart" button in Setup Python App:

touch /home/username/myapp/tmp/restart.txt

If that folder doesn't exist yet, create it first (mkdir -p tmp). Bookmark this — you'll run it constantly during active development.

Environment Variables and Secrets

Don't hardcode database passwords or API keys into settings.py. Setup Python App has an "Environment variables" section per application — add key/value pairs there (e.g. SECRET_KEY, DATABASE_URL) and read them in your app with os.environ.get(...). These are injected into the Passenger process automatically, so you don't need a .env loader for the basics, though python-dotenv still works fine for local dev.

Database Connections

If you're using MySQL/MariaDB via cPanel, create the database and user in MySQL Databases first, then install the right driver inside your activated virtualenv — mysqlclient or PyMySQL for Django, PyMySQL or SQLAlchemy connectors for Flask. A common failure here is forgetting that cPanel-created DB users and names get a cpaneluser_ prefix — double-check the exact strings in MySQL Databases rather than guessing.

Prevention Checklist

RiskHow to Avoid It
Installing packages outside the virtualenvAlways run the "source ... activate" command cPanel gives you before any pip command
500 errors after deployTail passenger.log immediately after any restart, don't wait for a bug report
Stale code after editsTouch tmp/restart.txt (or hit Restart) after every deploy — make it part of your deploy script
Secrets in gitUse cPanel's environment variables panel instead of committing config files
Broken static assetsRun collectstatic and map the static folder before going live, not after

Once it's wired up correctly, a Python app on cPanel is genuinely low-maintenance — no process manager to babysit, since Passenger handles process lifecycle for you. The setup step is just fussier than Node.js or PHP because there are more moving parts (virtualenv path, WSGI entry point, static mapping) that all have to agree with each other.

Frequently Asked Questions

Can I run Django and Flask apps on the same cPanel account?

Yes. Each app you create in Setup Python App gets its own application root, virtualenv, and Python version, so a Django project and a Flask project can coexist on separate subdomains or paths without conflicting.

Does cPanel support ASGI apps like FastAPI?

Passenger's cPanel integration is built around WSGI. FastAPI can still run if you front it with a WSGI-compatible adapter, but for a smoother experience with native ASGI support, a VPS running Uvicorn behind Nginx is usually the better fit.

Why does my app work over SSH but not through the browser?

This usually means you tested it by running python app.py directly instead of going through Passenger. Passenger imports your passenger_wsgi.py file rather than executing your script, so test through the actual domain/URL, and check passenger.log if it fails there specifically.

How do I use a different Python version later?

Open Setup Python App, edit the application, and change the Python version dropdown. cPanel creates a new virtualenv for that version — you'll need to reinstall your requirements.txt into it afterward, since packages don't carry over automatically.

Can I use pip packages that need system libraries (like Pillow or psycopg2)?

Usually yes on cPanel/WHM VPS environments where the underlying system has the required dev headers, but on some shared plans compiled extensions can fail to build. If pip install errors out with missing headers, ask your host whether the needed system package (e.g. libjpeg-dev, libpq-dev) is installed, or use a precompiled wheel where available.