Skip to main content
Version: 2.0.11

How to Deploy a Django App with Gunicorn

This guide shows how to deploy a Django project on OpenPanel. Django runs with Gunicorn in its own Python container, and OpenPanel's web server serves it on your domain with a free SSL certificate.

INFORMATION

Python applications are available in OpenPanel Enterprise. The feature must be enabled for your hosting plan.


Step 1: Prepare the Project for Production​

Assume a project called mysite:

example.com/
├── manage.py
├── requirements.txt
└── mysite/
├── settings.py
├── urls.py
└── wsgi.py

requirements.txt:

Django==5.1.*
gunicorn==23.0.0
whitenoise==6.7.0
mysqlclient==2.2.*
python-dotenv==1.0.*

Use psycopg[binary] instead of mysqlclient for PostgreSQL.

settings.py​

import os
from pathlib import Path
from dotenv import load_dotenv

BASE_DIR = Path(__file__).resolve().parent.parent
load_dotenv(BASE_DIR / ".env")

SECRET_KEY = os.environ["SECRET_KEY"]
DEBUG = os.environ.get("DEBUG", "0") == "1"

ALLOWED_HOSTS = ["example.com", "www.example.com"]
CSRF_TRUSTED_ORIGINS = ["https://example.com", "https://www.example.com"]

# OpenPanel terminates SSL and forwards requests to Gunicorn over HTTP
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
USE_X_FORWARDED_HOST = True

DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"HOST": "mysql", # "mariadb" for MariaDB, "postgres" for PostgreSQL
"PORT": "3306",
"NAME": os.environ["DB_NAME"],
"USER": os.environ["DB_USER"],
"PASSWORD": os.environ["DB_PASSWORD"],
}
}

# Static files are served by WhiteNoise from inside Django
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
MIDDLEWARE.insert(1, "whitenoise.middleware.WhiteNoiseMiddleware") # right after SecurityMiddleware

Then create .env next to manage.py (never commit it to Git):

SECRET_KEY=generate-a-long-random-string
DEBUG=0
DB_NAME=user_mysite
DB_USER=user_mysite
DB_PASSWORD=strong-password

Step 2: Create the Database​

In OpenPanel → MySQL → Database Wizard, create a database and a user with all privileges, and put the names into .env.

Inside OpenPanel, databases are reached by service name, never localhost:

DatabaseHOSTPORT
MySQLmysql3306
MariaDBmariadb3306
PostgreSQLpostgres5432

Step 3: Upload the Code​

Add the domain in OpenPanel → Domains, then upload the project to the domain folder (/var/www/html/example.com/) with the File Manager or FTP - or deploy it from Git in the next step.


Step 4: Create the Python Application​

Go to OpenPanel → Websites → Install App → Setup Python Application:

FieldValue
Namemysite
Domainexample.com
Port8000
Startup Filemanage.py
Custom Startup Commandsee below
Versiona Python version supported by your Django release, e.g. 3.12
Run Install✅ Yes
Git repositoryoptional, https:// URL
CPU / Memorye.g. 1 core, 1 GB

Install Python Application form with the application details, domain, startup file and advanced options Install Python Application form with the application details, domain, startup file and advanced options

Custom Startup Command - runs migrations, collects static files, then starts Gunicorn:

python manage.py migrate --noinput && python manage.py collectstatic --noinput && gunicorn mysite.wsgi:application --bind 0.0.0.0:8000 --workers 3

Click Start Installation, then open https://example.com.

TIP

Because migrations and collectstatic run on every start, deploying a new version is just: upload or git push, then Restart the app. With a Git repository set, the container pulls the latest commit of the default branch on each start.


Step 5: Create an Admin User​

Open OpenPanel → Containers → Terminal, select the mysite container and run:

python manage.py createsuperuser

Then log in at https://example.com/admin/.

The web terminal is an Enterprise feature and must be enabled for your plan - see Terminal.


Background Tasks​

  • Scheduled commands (e.g. python manage.py clearsessions): add a cron job.

  • Celery workers: enable the account's Redis service and use it as the broker (redis://redis:6379/0). Then start the worker in the background from the same startup command, before Gunicorn:

    python manage.py migrate --noinput && python manage.py collectstatic --noinput && (celery -A mysite worker -l info &) && gunicorn mysite.wsgi:application --bind 0.0.0.0:8000 --workers 3

    Add celery and redis to requirements.txt, and give the app enough memory for both processes.


Media Files (User Uploads)​

WhiteNoise serves static files only. For files uploaded by users (MEDIA_ROOT), either store them on object storage (e.g. S3 with django-storages) or serve them from Django for small sites. Keep MEDIA_ROOT inside the project folder so the files are included in backups.


Troubleshooting​

ProblemFix
502 Bad GatewayGunicorn isn't running or listens on the wrong address. Check the app Logs; bind to 0.0.0.0:8000.
DisallowedHostAdd the domain to ALLOWED_HOSTS.
CSRF verification failed on formsAdd https://yourdomain to CSRF_TRUSTED_ORIGINS and set SECURE_PROXY_SSL_HEADER.
Redirect loop with SECURE_SSL_REDIRECTSet SECURE_PROXY_SSL_HEADER as above - OpenPanel already redirects HTTP to HTTPS.
No CSS in the admincollectstatic didn't run, or WhiteNoise middleware is missing.
mysqlclient fails to installUse a full Python version from the dropdown (not a custom -slim image), or switch to PyMySQL.