Deploy a Python/Django App in cPanel: Setup Guide
If you've only ever deployed WordPress or a Node app through cPanel, "Setup Python App" feels like a different planet: a virtualenv you didn't create yourself, a passenger_wsgi.py file you're expected to write, and a 500 error that gives you nothing to go on. Here's how Python hosting actually works in cPanel, and how to fix the errors that trip people up most when getting Django (or Flask) running.
Where cPanel's Python support comes from
cPanel doesn't run your Python app directly. It hands the job to Phusion Passenger, the same application server that runs Node and Ruby apps on shared hosting. When you use Setup Python App (under Software in cPanel), it creates an isolated virtual environment for that one app, wires up a Passenger config behind the scenes, and expects a file called passenger_wsgi.py to expose your app as a WSGI callable named application. Get that contract wrong and Passenger just fails silently.
Step 1: Create the application
- In cPanel, go to Software → Setup Python App → Create Application.
- Pick a Python version (available versions depend on what your host has compiled with EasyApache/CloudLinux SCL - check what's offered before assuming the newest is there).
- Set the Application root, e.g.
myproject- keep it outsidepublic_html, cPanel handles the routing for you. - Set the Application URL - this can be your domain, a subdomain, or a path.
- Leave Application startup file as
passenger_wsgi.pyand Application Entry point asapplicationunless you have a specific reason to change them.
Click Create. cPanel now shows you an activation command for the virtualenv it just built - copy it, you'll need it in the next step.
Step 2: Activate cPanel's virtualenv before installing anything
This is where most Python deployments go wrong on day one. Don't just SSH in and run pip install django - that installs into the system Python, not the isolated environment Passenger will actually use. Run the exact command cPanel gave you:
source /home/yourusername/virtualenv/myproject/3.11/bin/activate
cd /home/yourusername/myproject
You'll see the prompt change to show the venv name. Everything you pip install from here goes into that isolated environment.
Step 3: Install your dependencies
pip install -r requirements.txt
For a fresh Django project that's usually just pip install django plus whatever you actually use (psycopg2-binary, whitenoise, etc.). You do not need gunicorn or uwsgi - Passenger is the WSGI server here, and running your own on top of it just adds a broken extra hop.
Step 4: Write passenger_wsgi.py correctly
For Django, bridge to its own WSGI application instead of reinventing one:
import sys, os
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()
Two things people get wrong here: naming the variable anything other than application (Passenger looks for that exact name and nothing else), and pointing DJANGO_SETTINGS_MODULE at the wrong package path relative to where passenger_wsgi.py actually sits.
Common errors and what's really causing them
| Symptom | Usual cause | Fix |
|---|---|---|
| "We're sorry, but something went wrong" / bare 500 | passenger_wsgi.py throws on import, but Passenger swallows the traceback by default | Open Setup Python App → your app → Logs, or check the stderr log path shown there. That's where the real traceback lives. |
| ModuleNotFoundError: No module named 'django' | Package installed to system Python, not the app's virtualenv | Re-run the source .../activate command, reinstall inside it, then restart the app |
| DisallowedHost at / (Invalid HTTP_HOST header) | Domain isn't in Django's ALLOWED_HOSTS | Add it in settings.py, save, restart the app |
| Django admin loads with no CSS | DEBUG=False and static files were never collected or served | Run collectstatic and serve STATIC_ROOT via whitenoise or a dedicated alias - Passenger won't proxy static requests like runserver does |
| Code changes don't show up after deploy | Passenger caches the running process; cPanel doesn't watch files | Click Restart in Setup Python App, or run touch tmp/restart.txt in the app root over SSH |
Fixing the static files problem properly
The cleanest fix for CSS/JS 404s in production is whitenoise - it lets Django serve its own static files without needing a separate web server config:
pip install whitenoise
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
# ...rest of your middleware
]
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
Then run python manage.py collectstatic and restart. No separate subdomain or symlink juggling needed.
Restart discipline matters more than it does with WordPress
A PHP change on cPanel shows up the moment you save the file. A Python app doesn't - Passenger keeps the process alive until you explicitly tell it to reload. Get in the habit of restarting after every deploy: code changes, settings.py edits, new environment variables, and new pip installs all require it. If you forget, you'll be debugging a bug that was actually fixed three deploys ago.
Prevention checklist
- Pin your
requirements.txtversions - an unpinned Django upgrade mid-project has broken more than one working deployment - Set
DEBUG = Falseand a correctALLOWED_HOSTSbefore going live, not after a customer reports a blank page - Store secrets (SECRET_KEY, database passwords) in the Environment Variables tab of Setup Python App, not hardcoded in settings.py
- Always activate the app's own virtualenv before running pip, never the system Python
- Restart the app after every deploy - no exceptions
- Check the app-specific log path first when something breaks; the generic cPanel Errors page is a second stop, not the first
Once the venv and passenger_wsgi.py are set up correctly, Python apps on cPanel are actually low-maintenance - there's no separate systemd service to babysit, and Passenger restarts crashed workers on its own. The setup step is just unforgiving about getting the contract right the first time.
Frequently asked questions
Can I use Flask instead of Django with Setup Python App?
Yes. The setup is identical: create the app, activate the venv, install Flask, and in passenger_wsgi.py import your Flask app object and assign it to a variable named application, e.g. `from myapp import app as application`.
Do I need Gunicorn or uWSGI on cPanel?
No. Passenger is already acting as the WSGI server for your app. Running Gunicorn on top of it adds an extra process that Passenger isn't expecting to manage, and usually just breaks the routing.
Why does my app work locally but not on cPanel?
Almost always a virtualenv mismatch: packages installed to your local Python but not to the isolated venv cPanel created, or a DEBUG=True locally masking an ALLOWED_HOSTS or static files issue that only shows up in production.
Where do I find error logs for a Python app in cPanel?
Open Setup Python App, select your application, and check the Logs section. It shows the path to the app's stderr/error log where passenger_wsgi.py exceptions actually get written, which is more useful than the general cPanel Errors page.
Does changing requirements.txt take effect automatically?
No. You need to reactivate the app's virtualenv, run pip install -r requirements.txt again, and then restart the app (via the UI or touch tmp/restart.txt) before the new packages are picked up.