When migrating from runserver to Gunicorn, receive error django.core.exceptions.ImproperlyConfigured

Problem description:

When migrating from runserver to Gunicorn and running command “gunicorn -c gunicorn.conf.py cloudmonitor.wsgi.application”, receive error ModuleNotFoundError: No module named ‘cloudmonitor’


I reviewed the link https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/gunicorn/ but I am not understanding the error when running the command.

I configured a config file for Gunicorn and put it in the same directory as manage.py.

Environment information: 

Django 5.1.0
Gunicorn 23.0.0
Nginx Reverse Proxy
RHEL 8 Virtual Machine

[user@server01 cloudmonitor]$ ls -la
total 48
drwxr-xr-x 3 user Users 4096 Aug 14 16:25 .
drwxr-xr-x 12 user Users 4096 Aug 14 16:08 …
-rw-r–r-- 1 user Users 417 Jul 25 02:49 asgi.py
-rw-r–r-- 1 user Users 1079 Jul 26 23:41 db_test.py
-rw-r–r-- 1 user Users 4201 Aug 14 13:45 gunicorn.conf.py
-rw-r–r-- 1 user Users 0 Jul 25 02:49 init.py
drwxr-xr-x 2 user Users 4096 Aug 14 16:17 pycache
-rw-r–r-- 1 user Users 6868 Aug 14 16:09 settings.py
-rw-r–r-- 1 user Users 180 Aug 14 03:25 ssl_test.py
-rw-r–r-- 1 user Users 1379 Aug 8 10:49 urls.py
-rw-r–r-- 1 user Users 414 Aug 14 16:25 wsgi.py



asgi.py

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault(‘DJANGO_SETTINGS_MODULE’, ‘cloudmonitor.settings’)

application = get_asgi_application()



wsgi.py

import os

from django.core.wsgi import get_wsgi_application
os.environ.setdefault(‘DJANGO_SETTINGS_MODULE’, ‘cloudmonitor.settings’)
application = get_wsgi_application()


Settings.py

import os
import environs

from pathlib import Path

from environs import Env

env = Env()

env.read_env()

BASE_DIR = Path(file).resolve().parent.parent

SECRET_KEY = env.str(“SECRET_KEY”)

DEBUG = env.bool(“DEBUG”, default=False)

ALLOWED_HOSTS = [‘192.168.46.69’, ‘localhost’, ‘127.0.0.1’]

Application definition

INSTALLED_APPS = [
‘authentication’,
‘dashboard’,
‘rest_framework’,
‘django_filters’,
‘corsheaders’,
‘django.contrib.admin’,
‘django.contrib.auth’,
‘django.contrib.contenttypes’,
‘django.contrib.sessions’,
‘django.contrib.messages’,
‘django.contrib.staticfiles’,
]

CORS_ORIGIN_ALLOW_ALL = True

MIDDLEWARE = [
‘django.middleware.security.SecurityMiddleware’,
‘django.contrib.sessions.middleware.SessionMiddleware’,
‘corsheaders.middleware.CorsMiddleware’,
‘django.middleware.common.CommonMiddleware’,
‘django.middleware.csrf.CsrfViewMiddleware’,
‘django.contrib.auth.middleware.AuthenticationMiddleware’,
‘django.contrib.messages.middleware.MessageMiddleware’,
‘django.middleware.clickjacking.XFrameOptionsMiddleware’,
]

ROOT_URLCONF = ‘cloudmonitor.urls’

WSGI_APPLICATION = ‘cloudmonitor.wsgi.application’


Other lines in settings.py omitted.

Do you have a logging configuration defined in your Django settings.py?. Check the docs for logging

What do you mean, you migrated Django to Gunicorn?
Add commands and error messages.

I did have a log path and log handlers defined in settings.py, removed them and still getting the same error output when running “gunicorn cloudmonitor.wsgi --log-file -”

Moving away from the Django runserver to production using Gunicorn. The command and error message is already in the post.

Are you running gunicorn from the same directory that your manage.py file is located in? The current directory should be the same whether you’re running manage.py or gunicorn.

What is that gunicorn.conf.py file?

Are you running gunicorn from the same directory that your manage.py file is located in?

Yes, this is correct.

The Gunicorn config file I wrote is below using the Gunicorn doc at Settings — Gunicorn 23.0.0 documentation.

import gunicorn
import multiprocessing
import os
#
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
#
#  Define HTTPd settings
#  https://docs.gunicorn.org/en/stable/settings.html
#
#  The socket to bind. A string of the form: HOST, HOST:PORT, unix:PATH, fd://FD. An IP is a valid HOST.
#  Default: ['127.0.0.1:8000']
bind = "192.168.46.69:9445"
daemon = 'True'
#  A comma-separated list of directories to add to the Python path. Default: None
pythonpath = ''
#  Front-end’s IPs from which allowed to handle set secure headers. (comma separated)
#  Set to * to disable checking of front-end IPs
#  By default, the value of the FORWARDED_ALLOW_IPS environment variable. If it is not defined,
#  the default is "127.0.0.1,::1".
#  https://docs.gunicorn.org/en/stable/settings.html#forwarded-allow-ips
forwarded_allow_ips = '*'
#  Define HTTPd worker CPU count to be used
#  Max = virtual CPUs allocated to guest OS
workers = multiprocessing.cpu_count() * 2 + 1
#
default_proc_name = 'gunicorn_httpd'
#
#  SSL/TLS Certifcate file location
certfile = "/opt/gunicorn_httpd/certs"
keyfile = "/opt/gunicorn_httpd/certs/private"
#  CA certificates file
ca_certs = ""
#  Force Client/Server to use TLS 1.3
def ssl_context(conf, default_ssl_context_factory):
    import ssl
    context = default_ssl_context_factory()
    context.minimum_version = ssl.TLSVersion.TLSv1_3
    return context
#  Whether to perform SSL handshake on socket connect (see stdlib ssl module’s)
do_handshake_on_connect = 'true'
#
#  SSL Cipher suite to use, in the format of an OpenSSL cipher list.
#  By default gunicorn uses the default cipher list from Python’s ssl module, which contains ciphers considered strong
#  at the time of each Python release.
#ciphers =
#  Require client certificate
#  Whether client certificate is required (see stdlib ssl module’s)
#  0=no client verification, 1=ssl.CERT_OPTIONAL, 2=ssl.CERT_REQUIRED
cert_reqs = "2"
#
#  ######HTTP SECURITY#######
#
#  The maximum size of HTTP request line in bytes. A server needs this value to be large enough to hold any of its
#  resource names, including any information that might be passed in the query part of a GET request. Value is a
#  number from 0 (unlimited) to 8190.
#  Default = 4096
limit_request_line = '8190'
#
#  Limit the number of HTTP headers fields in a request.
# This parameter is used to limit the number of headers in a request to prevent DDOS attack. Used with the
# limit_request_field_size it allows more safety. By default, this value is 100 and can’t be larger than 32768.
#limit_request_fields = ''
#  Limit the allowed size of an HTTP request header field.
#  Value is a positive number or 0. Setting it to 0 will allow unlimited header field sizes.
#  Default = 8190
#limit_request_field_size = ''
#
#  Default: {'X-FORWARDED-PROTOCOL': 'ssl', 'X-FORWARDED-PROTO': 'https', 'X-FORWARDED-SSL': 'on'}
#  https://docs.gunicorn.org/en/stable/settings.html#secure-scheme-headers
#secure_scheme_headers = {''}
#
#  https://docs.gunicorn.org/en/stable/run.html#django
#  A WSGI application path in pattern $(MODULE_NAME):$(VARIABLE_NAME)
#wsgi_app =
#
#  Logging configuration
accesslog = "/var/log/httpd/httpd_access.log"
#
disable_redirect_access_to_syslog = 'False'
#
#  https://docs.gunicorn.org/en/stable/settings.html#access-log-format
#  Default: '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
#access_log_format = ''
#
errorlog = "/var/log/httpd/httpd_error.log"
#
#  Valid level names are: 'debug' 'info' 'warning' 'error' 'critical'
loglevel = 'info'
#
#  Address to send syslog messages. Default: 'udp://localhost:514'
syslog = 'False'
syslog_addr = 'udp://localhost:514'
syslog_facility = 'user'

Based upon your directory listing up top, this gunicorn.conf.py is in the wrong directory, because gunicorn is looking in the current directory for this file, but this file is in a subdirectory relative to where you are running gunicorn from.

/home/user/cloudmonitor/cloudmonitor

sudo gunicorn -c gunicorn.conf.py cloudmonitor.wsgi.application
sudo tail -f /var/log/httpd/httpd_error.log
[2024-08-15 02:01:37 -0400] [1756924] [INFO] Starting gunicorn 23.0.0
[2024-08-15 02:01:37 -0400] [1756924] [INFO] Listening at: https://192.168.46.69:9450 (1756924)
[2024-08-15 02:01:37 -0400] [1756924] [INFO] Using worker: sync
[2024-08-15 02:01:37 -0400] [1756925] [INFO] Booting worker with pid: 1756925
[2024-08-15 02:01:37 -0400] [1756925] [ERROR] Exception in worker process
Traceback (most recent call last):
  File "/home/jparker/.local/lib/python3.11/site-packages/gunicorn/arbiter.py", line 608, in spawn_worker
    worker.init_process()
  File "/home/jparker/.local/lib/python3.11/site-packages/gunicorn/workers/base.py", line 135, in init_process
    self.load_wsgi()
  File "/home/jparker/.local/lib/python3.11/site-packages/gunicorn/workers/base.py", line 147, in load_wsgi
    self.wsgi = self.app.wsgi()
                ^^^^^^^^^^^^^^^
  File "/home/jparker/.local/lib/python3.11/site-packages/gunicorn/app/base.py", line 66, in wsgi
    self.callable = self.load()
                    ^^^^^^^^^^^
  File "/home/jparker/.local/lib/python3.11/site-packages/gunicorn/app/wsgiapp.py", line 57, in load
    return self.load_wsgiapp()
           ^^^^^^^^^^^^^^^^^^^
  File "/home/jparker/.local/lib/python3.11/site-packages/gunicorn/app/wsgiapp.py", line 47, in load_wsgiapp
    return util.import_app(self.app_uri)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jparker/.local/lib/python3.11/site-packages/gunicorn/util.py", line 370, in import_app
    mod = importlib.import_module(module)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.11/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1126, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1126, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1140, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'cloudmonitor'
[2024-08-15 02:01:37 -0400] [1756925] [INFO] Worker exiting (pid: 1756925)
[2024-08-15 02:01:37 -0400] [1756924] [ERROR] Worker (pid:1756925) exited with code 3
[2024-08-15 02:01:37 -0400] [1756924] [ERROR] Shutting down: Master
[2024-08-15 02:01:37 -0400] [1756924] [ERROR] Reason: Worker failed to boot.