Python App Hosting
WHost lets you host Python web applications — Django, Flask, FastAPI and similar — behind Gunicorn/Uvicorn application servers and an Nginx reverse proxy.
Python hosting is the free Python Hosting plugin: activate it once under Settings → Plugins. While it is inactive, the Python Apps menu entry is hidden in both panels and every Python app endpoint answers 503 PLUGIN_DISABLED.
Supported Stack
/usr/bin/python3.X)Admin Panel
Creating an Application
Tools → Python Apps → New Python App opens a chooser:
- Smart Deploy (recommended) — upload an archive or point to a path on the server; the project is analyzed and deployed in one run. See Smart Deploy.
- Create Blank App — creates the runtime scaffold only, with the form below; you add the code and run the setup steps yourself.
| Field | Description | Example |
|---|---|---|
| Account | Account the application will belong to | siteowner |
| App Name | Unique identifier: starts with a lowercase letter, then lowercase letters, digits, _ or - (2–31 characters) |
turkuaz |
| Display Name | Human-readable name | Turkuaz Site Management |
| Domain | A domain of the account — its primary domain, an addon domain or a subdomain (picking the account fills in its primary domain) | turkuaz.example.com |
| Python Version | One of the versions installed on the server | 3.12 |
| Framework | Application type | Django |
| App Server | Gunicorn (WSGI) or Uvicorn (ASGI) | Gunicorn (WSGI) |
| Entry Point | WSGI/ASGI entry point (module.path:callable) |
turkuaz.wsgi:application |
| Workers | Worker count (1–16, and no more than the account's Python Workers / App limit) | 2 |
| Worker Class | Sync, Threaded (gthread) or Uvicorn Worker (ASGI) — used by Gunicorn | Sync |
| WebSocket Support | Enable for ASGI applications | Off |
| Auto Restart | Automatically restart on crash | On |
During creation the system automatically:
- Creates the application directory
/home/{user}/{app}/and a virtualenv - Installs Gunicorn (for Uvicorn apps:
uvicorn[standard]and Gunicorn) - Defines a systemd service and a logrotate rule
- Configures the Nginx reverse proxy
- Starts the service — until the code is in place it fails to boot (see Typical Setup Steps)
Managing an Application
From the ⋮ menu on the table row, or the detail screen opened by clicking the application name:
Row menu:
- Edit App (display name, domain, entry point, workers, worker class, WebSocket support, auto restart)
- Packages / Environment Variables / Logs / manage.py — open the detail screen on that tab
- Start (when not running) / Stop (when running) / Restart
- Delete App (requires confirmation) — removes the service, the Nginx configuration, the virtualenv, the application directory
/home/{user}/{app}/with its code, and the app's log files
Detail screen (5 tabs):
All Applications View
The Python Apps page in the admin panel lists every application across all accounts in a single table. Narrow the view with search (app name, display name, account, domain), the status filter (Running / Stopped / Failed), or the Python version filter (shown when the apps use more than one version).
Client Panel
Creating an Application
Tools → Python Apps → Create App opens the same chooser (Smart Deploy or Create Blank App). The blank-app form matches the admin form without the Account selector — the application is created under the signed-in user's account — and without Worker Class (change it later with Edit App).
Managing an Application
The client panel has the same row menu and the same five detail tabs. Differences from the admin panel:
- The Packages tab can also install and uninstall packages and install
requirements.txt(see Pip Package Management). - The manage.py list also offers
createsuperuserandshell(see Django Commands). - The list has a search box (app name, display name, domain) but no status or version filter.
Smart Deploy
Available in both panels from the chooser:
- Source — Upload Archive (
.zip/.tar.gz, up to 250 MB) or Server Path (a folder or an archive inside the account's home directory; use SSH/SFTP and this mode for larger projects). - Analyze — detects the framework, app server, Python version, entry point, static paths and the environment variables the code reads, and names missing system libraries.
- Deploy — review the detected values and the options (Overwrite Existing, Include .env Files, Run migrate, Run collectstatic), then deploy. The pipeline copies the files to
/home/{user}/{app}/, creates the app, installsrequirements.txt, runsmigrateandcollectstaticfor Django when selected, and starts the app; the dialog streams the log live.
Leaving DB_NAME, DB_USER and DB_PASSWORD empty (for a project that reads them) creates a MySQL database and user during the deploy and passes their credentials to the app. A database dump or media folder inside the uploaded project is detected in both panels; uploading a separate SQL dump or media archive works in the admin panel only.
Pip Package Management
Detail screen → Packages tab. The admin panel lists the installed packages; installing and removing them is done in the client panel (or through the API).
/home/{user}/{app}/requirements.txtAccepted package name formats: django, django==6.0.1, gunicorn>=22.0, uvicorn[standard]
Environment Variables
Detail screen → Environment Variables tab
Keys use letters, digits and _ and do not start with a digit; values are single-line, up to 16,384 characters. After saving, the application is automatically restarted if it is running.
The variables are written to /home/{user}/{app}/.env (0600) and handed to the application's service; a variable with an empty value is left out. manage.py commands — from the tab, the API or Smart Deploy — run outside that service and see these variables only when the project reads .env itself (for example with python-decouple or django-environ).
Common variables:
DJANGO_SETTINGS_MODULE = myapp.settings.production
SECRET_KEY = your-secure-random-key
DATABASE_URL = mysql://user:pass@localhost/dbname
DEBUG = false
ALLOWED_HOSTS = example.com
Viewing Logs
Detail screen → Logs tab
- Error Log: application errors and tracebacks
- Access Log: HTTP requests (IP, path, status code)
- Pick the log type from the dropdown; click Refresh to reload. The tab shows the last 200 lines.
Log files: /home/{user}/logs/{app}-error.log and {app}-access.log
Django Commands (manage.py)
Detail screen → manage.py tab (Django projects — the application directory must contain manage.py)
Pick a command from the dropdown and click Run. The command runs as the account's user in the application directory, with a 5-minute limit; the exit code, output and errors are shown on screen.
migrateApply database migrations (runs with --noinput)collectstaticCollect static files (runs with --noinput)showmigrationsShow migration statuscheckCheck the project for issuesThe client panel's list also shows createsuperuser and shell, and the API accepts both, but they need an interactive terminal, which a panel or API run does not provide. Create the Django admin user over SSH instead, when the account has shell access: cd ~/{app} && ~/venvs/{app}/bin/python manage.py createsuperuser.
Typical Setup Steps
To host a new Django application as a blank app (Smart Deploy covers steps 4–9 in one run):
- Create an account — Accounts → Create Account
- Add a domain (only if the app uses a domain other than the account's primary domain) — Domains → Add Addon Domain or Add Subdomain
- Create a database — Databases → Create Database
- Create the Python application — Python Apps → New Python App → Create Blank App
- Upload the application code — via FTP, File Manager or SSH/SFTP into
/home/{user}/{app}/ - Install dependencies — Packages → Install from requirements.txt (client panel, or
POST …/pip/requirementsthrough the API) - Set environment variables — Environment Variables → DATABASE_URL, SECRET_KEY, etc.
- Run migrations — manage.py → migrate
- Collect static files — manage.py → collectstatic
- Create an admin user —
manage.py createsuperuserover SSH (it needs an interactive terminal)
The service was started when the app was created and fails to boot until the code is in place. With Auto Restart on, systemd retries it every 5 seconds, so it comes up once the code, packages and variables are ready and the application becomes reachable on the domain; otherwise start it from the ⋮ menu.
Updating Application Code
To update an existing application:
- Upload the new code (FTP / File Manager / SSH)
- If there are new dependencies → Packages → Install from requirements.txt (client panel or API)
- If there are new migrations → manage.py → migrate
- If static files changed → manage.py → collectstatic
- Restart
Plan Limits
Hosting plans expose two limits for Python applications:
| Limit | Description | Default |
|---|---|---|
| Max Python Apps | Maximum number of applications per account | 0 (unlimited) |
| Python Workers / App | Most workers a single application may be given (0 = unlimited) | 4 |
Set these under Plans → Edit Plan → Package Limits; the account create and edit pages carry the same fields. The check reads the account's own values, so a plan change reaches an existing account through a package change or its edit page. Going over a limit is refused with 403 PYTHON_APP_LIMIT or 403 PYTHON_WORKER_LIMIT.
File Layout
Creating a Python application produces the following layout on the server:
/home/{user}/
├── {app}/ ← Application code
│ ├── manage.py
│ ├── gunicorn.conf.py ← Generated automatically
│ ├── .env ← Environment variables (0600)
│ └── ...
├── venvs/{app}/ ← Virtualenv
│ ├── bin/python
│ ├── bin/pip
│ └── bin/gunicorn ← and bin/uvicorn for Uvicorn apps
└── logs/
├── {app}-error.log
└── {app}-access.log
/etc/systemd/system/
└── whost-pyapp-{user}-{app}.service
/etc/nginx/sites-available/ ← /etc/nginx/conf.d/ on AlmaLinux, Rocky Linux, CentOS Stream
└── {domain}-pyapp-{app}.conf
/etc/logrotate.d/
└── whost-pyapp-{user}-{app} ← log rotation, 14 compressed copies kept
/run/whost/{user}/
└── {app}.sock ← Unix socket
API Reference
All endpoints live under the /api/v1/ prefix.
Admin Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /accounts/{user}/python-apps |
List applications |
| POST | /accounts/{user}/python-apps |
Create an application |
| GET | /accounts/{user}/python-apps/{app} |
Detail |
| PUT | /accounts/{user}/python-apps/{app} |
Update |
| DELETE | /accounts/{user}/python-apps/{app} |
Delete |
| POST | /accounts/{user}/python-apps/{app}/start |
Start |
| POST | /accounts/{user}/python-apps/{app}/stop |
Stop |
| POST | /accounts/{user}/python-apps/{app}/restart |
Restart |
| GET | /accounts/{user}/python-apps/{app}/status |
Status |
| GET | /accounts/{user}/python-apps/{app}/pip |
Package list |
| POST | /accounts/{user}/python-apps/{app}/pip/install |
Install a package |
| POST | /accounts/{user}/python-apps/{app}/pip/uninstall |
Uninstall a package |
| POST | /accounts/{user}/python-apps/{app}/pip/requirements |
Install from requirements.txt |
| GET | /accounts/{user}/python-apps/{app}/env |
Get environment variables |
| PUT | /accounts/{user}/python-apps/{app}/env |
Set environment variables |
| GET | /accounts/{user}/python-apps/{app}/logs |
Get logs |
| POST | /accounts/{user}/python-apps/{app}/manage |
Run a manage.py command |
| POST | /accounts/{user}/python-apps/analyze |
Smart Deploy: analyze an uploaded archive (multipart, up to 250 MB) or a server_path |
| POST | /accounts/{user}/python-apps/migration/upload |
Smart Deploy: stage an SQL dump and/or media archive for the next deploy |
| POST | /accounts/{user}/python-apps/deploy |
Smart Deploy: start the pipeline; returns a task_id |
| GET | /accounts/{user}/python-apps/deploy/{task_id} |
Deploy task status and its events |
| GET | /accounts/{user}/python-apps/deploy/{task_id}/events |
Live deploy log (Server-Sent Events) |
| GET | /system/python/versions |
Installed Python versions |
| GET | /system/python/apps |
All applications |
Client Endpoints
Client endpoints live under the /client/python-apps/ prefix and authenticate with the account's session cookie (or an API key bound to the account). The account name is not supplied — it is taken from that identity.
| Method | Path | Description |
|---|---|---|
| GET | /client/python-apps |
My applications |
| POST | /client/python-apps |
Create an application |
| GET | /client/python-apps/{app} |
Detail |
| PUT | /client/python-apps/{app} |
Update |
| DELETE | /client/python-apps/{app} |
Delete |
| POST | /client/python-apps/{app}/start |
Start |
| POST | /client/python-apps/{app}/stop |
Stop |
| POST | /client/python-apps/{app}/restart |
Restart |
| GET | /client/python-apps/{app}/status |
Status |
| GET | /client/python-apps/{app}/pip |
Package list |
| POST | /client/python-apps/{app}/pip/install |
Install a package |
| POST | /client/python-apps/{app}/pip/uninstall |
Uninstall a package |
| POST | /client/python-apps/{app}/pip/requirements |
Install from requirements.txt |
| GET | /client/python-apps/{app}/env |
Get environment variables |
| PUT | /client/python-apps/{app}/env |
Set environment variables |
| GET | /client/python-apps/{app}/logs |
Get logs |
| POST | /client/python-apps/{app}/manage |
Run a manage.py command |
| POST | /client/python-apps/analyze |
Smart Deploy: analyze an uploaded archive (multipart, up to 250 MB) or a server_path |
| POST | /client/python-apps/deploy |
Smart Deploy: start the pipeline; returns a task_id |
| GET | /client/python-apps/deploy/{task_id} |
Deploy task status and its events |
| GET | /client/python-apps/deploy/{task_id}/events |
Live deploy log (Server-Sent Events) |
| GET | /client/python-apps/versions |
Installed Python versions |
Our support team is here around the clock for anything you can't find above.