Django + Gunicorn + Nginx Setup

Prior to gunicorn and nginx, i’ve tried to user apache + mod_wsgi and failed. Still don’t know why it fail. After 3 days trying, I give up. I have to look for another method, I try using gunicorn dan nginx, and It took me only 2 days for gunicord + nginx setup to works. ūüôā

Here is condition on my Django + Gunicorn + Nginx setup:

  • I use CentOS 7 on my server
  • I create new user for running django (in my case /home/myuser)
  • I install python 3.7.3, django, pandas, django_extensions, virtual environment, gunicorn dan nginx
  • My django project folder is /home/myuser/myproject
  • I put all my django app (created using ‘python manage.py startapp myapp apps/myapp’) in /home/myuser/myproject/apps

Here is some of reference I use:

Here is my setup:

/home/myuser/myproject/wsgi.py

import os
import sys

from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
sys.path.append('/home/myuser/myproject/apps')

application = get_wsgi_application()

/etc/nginx/nginx.conf

user  nginx; 
worker_processes  1; 

error_log  /var/log/nginx/error.log warn; 
pid        /var/run/nginx.pid; 


events { 
   worker_connections  1024; 
} 


http { 
   include       /etc/nginx/mime.types; 
   default_type  application/octet-stream; 

   log_format  main  '$remote_addr - $remote_user [$time_local] "$request" ' 
                     '$status $body_bytes_sent "$http_referer" ' 
                     '"$http_user_agent" "$http_x_forwarded_for"'; 

   access_log  /var/log/nginx/access.log  main; 

   sendfile        on; 
   #tcp_nopush     on; 

   keepalive_timeout  65; 

   #gzip  on; 

   include /etc/nginx/conf.d/*.conf; 
}

/etc/systemd/system/gunicorn.service

setup for gunicorn to be run as service.

[Unit] 
Description=gunicorn service 
After=network.target 

[Service] 
User=myuser 
Group=myuser 
WorkingDirectory=/home/myuser/myproject/ 
ExecStart=/home/myuser/app1/bin/gunicorn --access-logfile - --workers 3 --bind unix:/home/myuser/myproject/myproject.sock myproject.wsgi:application --log-level debug 

[Install] 
WantedBy=multi-user.target

don’t forget to enable service using:

systemctl enable gunicorn

/home/myuser/myproject/myproject/setting.py

import os 

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 

SECRET_KEY = '6sp(_!o*NOT_MY_REAL_KEY_OF_COURSE*uu5o(9q1+ir=:-)r' # change: my key

DEBUG = True # to be changed on production

ALLOWED_HOSTS = ['192.168.0.2','myuser.mydomain.com'] #change: my domain


# Application definition 

INSTALLED_APPS = [ 
   'django.contrib.admin', 
   'django.contrib.auth', 
   'django.contrib.contenttypes', 
   'django.contrib.sessions', 
   'django.contrib.messages', 
   'django.contrib.staticfiles', 
   'django.contrib.humanize', 
   'django_extensions', 
   'myproject', 
   'myproject.templatetags', # delete:my custom tags
   'login', # delete:my app
   'weeklyreport', # delete:my app
]
MIDDLEWARE = [ 
   'django.middleware.security.SecurityMiddleware', 
   'django.contrib.sessions.middleware.SessionMiddleware', 
   'django.middleware.common.CommonMiddleware', 
   'django.middleware.csrf.CsrfViewMiddleware', 
   'django.contrib.auth.middleware.AuthenticationMiddleware', 
   'django.contrib.messages.middleware.MessageMiddleware', 
   'django.middleware.clickjacking.XFrameOptionsMiddleware', 
] 

ROOT_URLCONF = 'myproject.urls' 

TEMPLATES = [ 
   { 
       'BACKEND': 'django.template.backends.django.DjangoTemplates', 
       'DIRS': ['templates'], 
       'APP_DIRS': True, 
       'OPTIONS': { 
           'context_processors': [ 
               'django.template.context_processors.debug', 
               'django.template.context_processors.request', 
               'django.contrib.auth.context_processors.auth', 
               'django.contrib.messages.context_processors.messages', 
           ], 
       }, 
   }, 
] 

WSGI_APPLICATION = 'myproject.wsgi.application' 


DATABASES = { 
   'default': { #change: my database setup
       'ENGINE': 'django.db.backends.mysql', 
       'NAME': 'mydb1', 
       'USER': 'myuseruser', 
       'PASSWORD': 'my_db_password', 
       'HOST': '192.168.0.2', 
       'OPTIONS': { 
           'read_default_file': '/etc/my.cnf.d/client.cnf', 
           'init_command': "SET sql_mode='STRICT_TRANS_TABLES'; SET SESSION binlog_format = 'ROW';", 
       },
        'jgmotordb': { #delete: my database setup
       'ENGINE': 'django.db.backends.mysql', 
       'NAME': 'mydb2', 
       'USER': 'myuseruser', 
       'PASSWORD': 'my_db_password', 
       'HOST': '192.168.0.2', 
       'OPTIONS': { 
           'read_default_file': '/etc/my.cnf.d/client.cnf', 
           'init_command': "SET sql_mode='STRICT_TRANS_TABLES'; SET SESSION binlog_format = 'ROW';", 
           } 
       }
   } 
}

AUTH_PASSWORD_VALIDATORS = [ 
   { 
       'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 
   }, 
   { 
       'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 
   }, 
   { 
       'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 
   }, 
   { 
       'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 
   }, 
] 

LANGUAGE_CODE = 'en-us' 
TIME_ZONE = 'UTC' 
USE_I18N = True 
USE_L10N = True 
USE_TZ = True 

STATIC_URL = '/static/' # my static folder
STATICFILES_DIRS = [ 
   os.path.join(BASE_DIR, '0static'),  # my static folder (before for 'collectstatic' command)
] 
STATIC_ROOT = os.path.join(BASE_DIR, 'static') 
AUTHENTICATION_BACKENDS = ( 'myproject.backend.custombackend', ) #delete: my custom backends

It’s my django project. Therefor, you might find my setup is not ideal, but it works! ūüôā (and it took me a week to make it works).

I learn that, most of tutorials use apt /¬†apt-get for installation. Since I use CenOS (which use yum as default) I find it annoying. ūüė¶

Please leave a comment for any suggestion or if you find this post helpful.