Run Django + uwsgi + nginx + sqlite3 on Sakura VPS CentOS 7

10 minute read

Introduction

We are building an environment by installing each software version on the 1G plan of Sakura VPS.

name version
CentOS 7.8.2003 (Core)
python 3.6.8
django 3.1
uwsgi 2.0.19.1
nginx 1.16.1
sqlite 3.30.0

Set up Sakura VPS

As a prerequisite, it is assumed that the standard OS CentOS7 x86_64 is installed and the initial setting guide for the server in the startup guide is completed.
In addition, enable the Web in the server initial setting guide 6 “Let’s set the packet filter”.

Install Django

Check the Django site (https://www.djangoproject.com/) for instructions on how to install it.
You need Python to use Django, so first check the version of Python you have installed.

$ python --version
Python 2.7.5

According to the FAQ “Which version of Python can I use Django with?”, Django that can be used with Python 2.7.5 is 1.11. However, since 1.11 is no longer supported on April 1, 2020, Python I’ll use 3.

$ python3 --version
-bash: python3: command not found

Python 3 is not installed, so install it.

$ sudo yum -y install python3

Check the version of Python 3 installed.

$ python3 --version
Python 3.6.8

Since it’s Python 3.6.8, you can use up to the latest Django 3.1.
Now that Python is ready, install Django.

$ sudo pip3 install django

Check the version of Django you have installed.

$ django-admin --version
3.1

For the time being, let’s create a project and check if it works.

$ django-admin startproject mysite
$ cd mysite
$ python3 manger.py runserver

Somehow an error has occurred.

Watching for file changes with StatReloader
Exception in thread django-main-thread:
Traceback (most recent call last):
  File "/usr/lib64/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "/usr/lib64/python3.6/threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "/usr/local/lib64/python3.6/site-packages/django/utils/autoreload.py", line 53, in wrapper
    fn(*args, **kwargs)
  File "/usr/local/lib64/python3.6/site-packages/django/core/management/commands/runserver.py", line 110, in inner_run
    autoreload.raise_last_exception()
  File "/usr/local/lib64/python3.6/site-packages/django/utils/autoreload.py", line 76, in raise_last_exception
    raise _exception[1]
  File "/usr/local/lib64/python3.6/site-packages/django/core/management/__init__.py", line 357, in execute
    autoreload.check_errors(django.setup)()
  File "/usr/local/lib64/python3.6/site-packages/django/utils/autoreload.py", line 53, in wrapper
    fn(*args, **kwargs)
  File "/usr/local/lib64/python3.6/site-packages/django/__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/usr/local/lib64/python3.6/site-packages/django/apps/registry.py", line 114, in populate
    app_config.import_models()
  File "/usr/local/lib64/python3.6/site-packages/django/apps/config.py", line 211, in import_models
    self.models_module = import_module(models_module_name)
  File "/usr/lib64/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 678, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/usr/local/lib64/python3.6/site-packages/django/contrib/auth/models.py", line 2, in <module>
    from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
  File "/usr/local/lib64/python3.6/site-packages/django/contrib/auth/base_user.py", line 48, in <module>
    class AbstractBaseUser(models.Model):
  File "/usr/local/lib64/python3.6/site-packages/django/db/models/base.py", line 122, in __new__
    new_class.add_to_class('_meta', Options(meta, app_label))
  File "/usr/local/lib64/python3.6/site-packages/django/db/models/base.py", line 326, in add_to_class
    value.contribute_to_class(cls, name)
  File "/usr/local/lib64/python3.6/site-packages/django/db/models/options.py", line 206, in contribute_to_class
    self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
  File "/usr/local/lib64/python3.6/site-packages/django/db/__init__.py", line 28, in __getattr__
    return getattr(connections[DEFAULT_DB_ALIAS], item)
  File "/usr/local/lib64/python3.6/site-packages/django/db/utils.py", line 214, in __getitem__
    backend = load_backend(db['ENGINE'])
  File "/usr/local/lib64/python3.6/site-packages/django/db/utils.py", line 111, in load_backend
    return import_module('%s.base' % backend_name)
  File "/usr/lib64/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "/usr/local/lib64/python3.6/site-packages/django/db/backends/sqlite3/base.py", line 70, in <module>
    check_sqlite_version()
  File "/usr/local/lib64/python3.6/site-packages/django/db/backends/sqlite3/base.py", line 67, in check_sqlite_version
    raise ImproperlyConfigured('SQLite 3.8.3 or later is required (found %s).' % Database.sqlite_version)
django.core.exceptions.ImproperlyConfigured: SQLite 3.8.3 or later is required (found 3.7.17).

The error message says that I am requesting SQLite version 3.8.3 or later, but I found 3.7.17.
Let’s actually check the version of SQLite.

$ sqlite3 -version
3.7.17 2013-05-20 00:56:22 118a3b35693b134d56ebd780123b7fd6f1497668

Certainly the version is 3.7.17, which is old, so it is necessary to upgrade.

Upgrade SQLite

Download and install the source code from the SQLite site.

$ cd ~/
$ wget https://www.sqlite.org/2020/sqlite-autoconf-3330000.tar.gz
$ tar zxvf sqlite-autoconf-3330000.tar.gz
$ cd sqlite-autoconf-3330000
$ ./configure
$ make
$ sudo make install

Check the version of SQLite you have installed.

$ /usr/local/bin/sqlite3 -version
3.33.0 2020-08-14 13:23:32 fca8dc8b578f215a969cd899336378966156154710873e68b3d9ac5881b0ff3f

Since the version of SQLite has been upgraded, specify the library path in LD_LIBRARY_PATH and try again.

$ cd ~/mysite
$ LD_LIBRARY_PATH=/usr/local/lib python3 manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
August 20, 2020 - 18:26:21
Django version 3.1, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

This time it seems that it was able to be executed properly.
Let’s check if we can access the server.

$ curl -i http://127.0.0.1:8000
HTTP/1.1 200 OK
Date: Thu, 20 Aug 2020 18:35:41 GMT
Server: WSGIServer/0.2 CPython/3.6.8
Content-Type: text/html
X-Frame-Options: DENY
Content-Length: 16351
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin


<!doctype html>

<html>
    <head>
...

Since it is troublesome to specify LD_LIBRARY_PATH every time, register the shared library path with ldconfig.
First, check the current registration status.

$ ldconfig -p | grep sqlite
        libsqlite3.so.0 (libc6,x86-64) => /lib64/libsqlite3.so.0

Add the configuration file under /etc/ld.so.conf.d.

/etc/ld.so.conf.d/sqlite3.33.0.conf


/usr/local/lib

Update your registration details.

$ sudo ldconfig

Check the registration status again.

$ ldconfig -p | grep sqlite
        libsqlite3.so.0 (libc6,x86-64) => /usr/local/lib/libsqlite3.so.0
        libsqlite3.so.0 (libc6,x86-64) => /lib64/libsqlite3.so.0
        libsqlite3.so (libc6,x86-64) => /usr/local/lib/libsqlite3.so

Now you don’t have to specify LD_LIBRARY_PATH.

install uwsgi

Install uwsgi for Web Server Gateway Interface.

$ sudo pip3 install uwsgi

Somehow an error has occurred.

    In file included from plugins/python/python_plugin.c:1:0:
    plugins/python/uwsgi_python.h:2:20: fatal error: Python.h: No such file or directory
     #include <Python.h>
                        ^
    compilation terminated.
    In file included from plugins/python/pyutils.c:1:0:
    plugins/python/uwsgi_python.h:2:20: fatal error: Python.h: No such file or directory
     #include <Python.h>
                        ^
    compilation terminated.

    ----------------------------------------
Command "/usr/bin/python3 -u -c "import setuptools, tokenize;__file__='/tmp/pip-build-evpbzqre/uwsgi/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" install --record /tmp/pip-9qkvdn3u-record/install-record.txt --single-version-externally-managed --compile" failed with error code 1 in /tmp/pip-build-evpbzqre/uwsgi/

The cause is that python3-devel is not installed, so install it.

$ sudo yum -y install python3-devel

Install uwsgi again.

$ sudo pip3 install uwsgi

I installed it without any problem, so check the version of uwsgi.

$ uwsgi --version
2.0.19.1

Prepare a test script that just returns Hello World to check the operation.

test.py


def application(env, start_response):
    start_response('200 OK', [('Content-Type','text/html')])
    return [b"Hello World"]

Run the test script.

$ uwsgi --http :8000 --wsgi-file test.py

Access the local host on port number 8000 to see if it’s running.

$ curl -i localhost:8000
HTTP/1.1 200 OK
Content-Type: text/html

Hello World

It worked fine, so let’s run Django.

$ cd ~/mysite
$ uwsgi --http :8000 --module mysite.wsgi

Somehow an error has occurred.

*** Starting uWSGI 2.0.19.1 (64bit) on [Sun Aug 23 00:36:53 2020] ***
compiled with version: 4.8.5 20150623 (Red Hat 4.8.5-39) on 22 August 2020 13:27:06
os: Linux-3.10.0-1127.18.2.el7.x86_64 #1 SMP Sun Jul 26 15:27:06 UTC 2020
nodename:server name
machine: x86_64
clock source: unix
detected number of CPU cores: 2
current working directory: /home/username/mysite
detected binary path: /usr/local/bin/uwsgi
!!! no internal routing support, rebuild with pcre support !!!
*** WARNING: you are running uWSGI without its master process manager ***
your processes number limit is 3881
your memory page size is 4096 bytes
detected max file descriptor number: 1024
lock engine: pthread robust mutexes
thunder lock: disabled (you can enable it with --thunder-lock)
uWSGI http bound on :8000 fd 4
spawned uWSGI http 1 (pid: 11759)
uwsgi socket 0 bound to TCP address 127.0.0.1:40228 (port auto-assigned) fd 3
Python version: 3.6.8 (default, Apr  2 2020, 13:34:55)  [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]
*** Python threads support is disabled. You can enable it with --enable-threads ***
Python main interpreter initialized at 0x165d4f0
your server socket listen backlog is limited to 100 connections
your mercy for graceful operations on workers is 60 seconds
mapped 72904 bytes (71 KB) for 1 cores
*** Operational MODE: single process ***
Traceback (most recent call last):
  File "./mysite/wsgi.py", line 16, in <module>
    application = get_wsgi_application()
  File "/usr/local/lib64/python3.6/site-packages/django/core/wsgi.py", line 12, in get_wsgi_application
    django.setup(set_prefix=False)
  File "/usr/local/lib64/python3.6/site-packages/django/__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/usr/local/lib64/python3.6/site-packages/django/apps/registry.py", line 114, in populate
    app_config.import_models()
  File "/usr/local/lib64/python3.6/site-packages/django/apps/config.py", line 211, in import_models
    self.models_module = import_module(models_module_name)
  File "/usr/lib64/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "/usr/local/lib64/python3.6/site-packages/django/contrib/auth/models.py", line 2, in <module>
    from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
  File "/usr/local/lib64/python3.6/site-packages/django/contrib/auth/base_user.py", line 48, in <module>
    class AbstractBaseUser(models.Model):
  File "/usr/local/lib64/python3.6/site-packages/django/db/models/base.py", line 122, in __new__
    new_class.add_to_class('_meta', Options(meta, app_label))
  File "/usr/local/lib64/python3.6/site-packages/django/db/models/base.py", line 326, in add_to_class
    value.contribute_to_class(cls, name)
  File "/usr/local/lib64/python3.6/site-packages/django/db/models/options.py", line 206, in contribute_to_class
    self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
  File "/usr/local/lib64/python3.6/site-packages/django/db/__init__.py", line 28, in __getattr__
    return getattr(connections[DEFAULT_DB_ALIAS], item)
  File "/usr/local/lib64/python3.6/site-packages/django/db/utils.py", line 214, in __getitem__
    backend = load_backend(db['ENGINE'])
  File "/usr/local/lib64/python3.6/site-packages/django/db/utils.py", line 111, in load_backend
    return import_module('%s.base' % backend_name)
  File "/usr/lib64/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "/usr/local/lib64/python3.6/site-packages/django/db/backends/sqlite3/base.py", line 70, in <module>
    check_sqlite_version()
  File "/usr/local/lib64/python3.6/site-packages/django/db/backends/sqlite3/base.py", line 67, in check_sqlite_version
    raise ImproperlyConfigured('SQLite 3.8.3 or later is required (found %s).' % Database.sqlite_version)
django.core.exceptions.ImproperlyConfigured: SQLite 3.8.3 or later is required (found 3.7.17).
unable to load app 0 (mountpoint='') (callable not found or import error)
*** no app loaded. going in full dynamic mode ***
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI worker 1 (and the only) (pid: 11758, cores: 1)

The error message says that I am requesting SQLite version 3.8.3 or later, but I found 3.7.17.
For some reason, the same message is displayed even though the version should have been upgraded earlier.
The cause is that uwsgi has RPATH set.

$ objdump -p /usr/local/bin/uwsgi | grep RPATH
  RPATH                /usr/lib64

If RPATH is set, shared libraries will only be loaded from the specified directory.
Add / usr / local / lib to RPATH and recreate uwsgi.

$ sudo pip3 uninstall uwsgi
$ sudo LDFLAGS="-L/usr/local/lib -Wl,-rpath,/usr/local/lib" pip3 install uwsgi

Check what happened to RPATH.

$ objdump -p /usr/local/bin/uwsgi | grep RPATH
  RPATH                /usr/local/lib:/usr/lib64

Try running it again.

$ cd ~/mysite
$ uwsgi --http :8000 --module mysite.wsgi

I was able to execute it without any problems.

install nginx

Install the web server nginx.

$ sudo yum -y install nginx

Check the version of nginx you have installed.

$ nginx -v
nginx version: nginx/1.16.1

Prepare the configuration file.
Replace the server name with http: // servername / for the server name with your own username for the username.

nginx:/etc/nginx/conf.d/mysite.conf


upstream django {
  server 127.0.0.1:8000;
}

server {
  listen 80;

  server_name server name;

  location / {
    uwsgi_pass django;
    include /home/username/mysite/uwsgi_params;
  }
}

A parameter file for uwsgi is prepared, so copy it to the project.

$ cp /etc/nginx/uwsgi_params ~/mysite

Now that everything is ready, restart nginx.

$ sudo systemctl restart nginx

Try running Django.

$ cd ~/mysite
$ uwsgi --socket :8000 --module mysite.wsgi

It seems that it can be executed, so try accessing http: // server name /.
django7.png
Somehow an error has occurred.
The cause is that it was executed without setting ALLOWED_HOSTS.
Set ALLOWED_HOSTS.

mysite/settings.py


ALLOWED_HOSTS = ['server name']

After setting, try again.

django8.png
The page where the rocket is flying is displayed.
The rocket page is displayed because DEBUG is True, so let’s change it to False.

mysite/settings.py


DEBUG = False

After setting, try again.
django9.png
This time nothing is displayed.
Since the top page is not prepared yet, I will display the management page instead.
http://サーバー名/admin/にアクセスします。
django10.png
Since static files such as images are not prepared, only characters are displayed.
Prepare a static file.
First, prepare a directory to store static files.

$ sudo mkdir /usr/share/nginx/html/static
$sudo chown username/usr/share/nginx/html/static

Set Django to store static files.

mysite/settings.py


STATIC_URL = '/static/'

STATIC_ROOT = '/usr/share/nginx/html/static'

Collect static files in the storage location.

$ python3 manage.py collectstatic

Set the storage destination of the static file on the nginx side as well.

nginx:/etc/nginx/conf.d/mysite.conf


  location /static {
    alias /usr/share/nginx/html/static;
  }

  location / {

After changing the server settings, restart the server for application.

$ sudo systemctl restart nginx

Now that you’re ready, run it and try to access it.
django11.png
It was displayed.
Finally, let’s change it to access with unix socket.

nginx:/etc/nginx/conf.d/mysite.conf


upstream django {
  server unix:///home/username/mysite/mysite.sock;
#  server 127.0.0.1:8000;
}

Restart nginx for the settings to apply.

$ sudo systemctl restart nginx

Run it and try to access it.
django12.png
502 Bad Gateway has occurred.
Let’s look at the error log.

$ sudo cat /var/log/nginx/error.log
2020/08/31 03:41:54 [crit] 12700#0: *2 connect() to unix:///home/username/mysite/mysite.sock failed (13: Permission denied) while connecting to upstream, ...

You can see that Permission denied has occurred.
The cause is that nginx does not have permission to access the user’s directory.
Add nginx to your group of users so that nginx can access it.

$ sudo usermod -aG username nginx
$ chmod 750 /home/username
$ sudo systemctl restart nginx

Try running it again to access it.
django11.png
This time it was displayed.

As mentioned above, I was able to run Django + uwsgi + nginx + sqlite on CentOS 7 of Sakura VPS.