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.pystub 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
| Risk | How to Avoid It |
|---|---|
| Installing packages outside the virtualenv | Always run the "source ... activate" command cPanel gives you before any pip command |
| 500 errors after deploy | Tail passenger.log immediately after any restart, don't wait for a bug report |
| Stale code after edits | Touch tmp/restart.txt (or hit Restart) after every deploy — make it part of your deploy script |
| Secrets in git | Use cPanel's environment variables panel instead of committing config files |
| Broken static assets | Run 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.
