Linux Server setup


The open-source Django web framework is written in Python and is maintained by the Django Software Foundation. It follows the Model View Presenter (MVP) paradigm.

As mentioned before, we will create a virtual environment with the python3-venv module to encapsulate the application in it. This way, multiple Django installations can run independently without affecting each other or their dependencies.

As a database we use PostgreSQL.

The project directory in my example is /var/www/

Next steps:

Create virtual environment

We create a virtual environment with the command python3 followed by the parameter -m (module name), i.e. venv and a folder name or a path:

__$ python3 -m venv /var/www/

The folder vm1 is created automatically and in it also other folders and files.

Let's take a quick look:

__$ ls -la /var/www/


drwxrwxr-x 6 tom tom 4096 Jan  4 13:08 .
drwxr-xr-x 7 tom tom 4096 Jan  3 20:52 ..
drwxrwxr-x 2 tom tom 4096 Jan  4 13:08 bin
drwxrwxr-x 2 tom tom 4096 Jan  4 13:08 include
-rw-rw-r-- 1 tom tom  143 Jan  3 20:05 index.htm
drwxrwxr-x 3 tom tom 4096 Jan  4 13:08 lib
lrwxrwxrwx 1 tom tom    3 Jan  4 13:08 lib64 -> lib
-rw-rw-r-- 1 tom tom   70 Jan  4 13:08 pyvenv.cfg
drwxrwxr-x 3 tom tom 4096 Jan  4 13:08 share

Start and disable virtual environment


The command activate has been created in the bin subdirectory as an executable file. In order to work in the virtual environment, it must first be started:

__$ source /var/www/

After activation, the name of the virtual environment appears in brackets in front of the user name:

(vm1) tom@srv1:~$


In the activated state, the virtual environment can be terminated or exited with the deactivate command:

(vm1) tom@srv1:~$ deactivate

I leave out the symbolism for the activated state (vm1) tom@srv1:~$ from now on, so that the commands can be copied without effort!

Install Django

To install Django, we must be in an enabled virtual environment:

__$ source /var/www/

For the installation we need the package management pip, which we had already installed on this Python, pip, PostgreSQL.

In the enabled state, we install the latest version of Django:

__$ python3 -m pip install django

We can have a module list created with freeze. Without specifying a file name, the requirements.txt file is created. To make a visual association with the virtual environment recognizable, I specify the file name vm1.pip:

__$ python3 -m pip freeze > /var/www/

This file is now located next to the virtual environment folder:

__$ ls -la /var/www/


drwxrwxr-x 3 tom tom 4096 Jan  4 14:59 .
drwxr-xr-x 7 tom tom 4096 Jan  3 20:52 ..
drwxrwxr-x 7 tom tom 4096 Jan  4 15:00 vm1
-rw-rw-r-- 1 tom tom   69 Jan  4 14:59 vm1.pip

Let's briefly print the contents of vm1.pip:

__$ less /var/www/

Installed modules and dependencies of the Django application (created with freeze):



Django would thus be installed in the virtual environment, but not yet executable. To do this, we must first create a Django project.

Create Django project

Django provides a few Python scripts that help us create a Django instance. We will get a directory structure and configuration files that we will edit step by step.

For the sake of simplicity, let's change to the appropriate directory with cd:

__$ cd /var/www/

The shell after the directory change:


Also these prefixes tom@srv1:/var/www/$ from the shell I leave out from now on!

We create a project with startproject followed by an instance name of our choice. In my case app1:

__$ django-admin startproject app1

This created the app1 folder for us. Let's take a quick look inside:

__$ ls -la app1

Contents of /var/www/

drwxrwxr-x 2 tom tom 4096 Jan  4 15:00 app1
-rwxrwxr-x 1 tom tom  660 Jan  4 15:00

Let's also take a look at the app1/app1 folder:

__$ ls -la app1/app1

Contents of /var/www/

-rw-rw-r-- 1 tom tom  385 Jan  4 15:00
-rw-rw-r-- 1 tom tom    0 Jan  4 15:00
-rw-rw-r-- 1 tom tom 3213 Jan  4 15:00
-rw-rw-r-- 1 tom tom  746 Jan  4 15:00
-rw-rw-r-- 1 tom tom  385 Jan  4 15:00


In the in the ALLOWED_HOSTS section we need to store the domain name and the internal address:

__$ nano /var/www/

This section then looks like this for me:

Excerpt from /var/www/

. . .
ALLOWED_HOSTS = ['', '']
. . .

Save and close (CTRL+s, CTRL+x).

Configure database

For PostgreSQL support, Django requires the Psycopg module. We install the adapter with pip:

__$ pip install psycopg2-binary

Then we edit the in the DATABASES section:

__$ nano /var/www/

And enter the connection data to our PostgreSQL database. This section should then look like this:

Excerpt from /var/www/

. . .
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'django_db',
        'USER': 'tom',
        'Password': 'tom123',
        'HOST': 'localhost',
        'PORT': '5432',
. . .

Save anf close (CTRL+s, CTRL+x).

Test Django instance

With the script we can launch the Django app and check the previous configuration. If the virtual environment has been deactivated in the meantime, you can reactivate it with (source bin/activate).

__$ python app1/ 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 migrate' to apply them.
January 20, 2022 - 20:50:57
Django version 4.0, using settings 'app1.settings'
Starting development server at
Quit the server with CONTROL-C.

You should see the message System check identified no issues (0 silenced) , because it says that no unexpected system error occurred. However, components of the web framework have not yet been migrated to the database (unapplied migration(s)). We'll take care of that in the next section. Before that we terminate the service with CTRL+c.

Migrate data

We run again with the migrate parameter:

__$ python app1/ migrate


Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK

Now the Django service should run correctly. We start the service again:

__$ python app1/ runserver

The output should then look like this:

Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
January 20, 2022 - 21:20:24
Django version 4.0, using settings 'app1.settings'
Starting development server at
Quit the server with CONTROL-C.

Django is now running as an internal service on Next, we need to let our Nginx server (reverse proxy) communicate there.

Configure Nginx for Django

We had already created the Nginx server block for the subdomain in the chapter Subdomain Server Block (dev).

Nginx is responsible at this point for enforcing HTTPS and encrypting the connection for the subdomain. Now we need to modify the server block so that communication through the domain is routed to and from the Django service.

__$ sudo nano /etc/nginx/sites-available/

The Nginx configuration should eventually look like this:


# force https
server {
  listen      80;
  return      301 https://$server_name$request_uri;

# main block
server {
  listen      443 ssl http2;
  listen      [::]:443 ssl http2;

  root        /var/www/;
  index       index.htm;

  location / {
    proxy_http_version 1.1;
    proxy_cache_bypass $http_upgrade;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_set_header x-real-ip $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Host  $host;
    proxy_set_header X-Forwarded-Port  $server_port;
    proxy_set_header Referer           $http_referer;
    proxy_pass_header content-security-policy;

  ssl_certificate /etc/letsencrypt/live/;
  ssl_certificate_key /etc/letsencrypt/live/;
  ssl_trusted_certificate /etc/letsencrypt/live/;

  # dhparam
  ssl_dhparam /etc/ssl/certs/dhparam.pem;

  # HSTS
  add_header Strict-Transport-Security "max-age=31536000";

Mainly the location block has been changed. A few Proxy Direktiven have been added there. Quite decisive here is proxy_pass

Damit die Konfiguration von Nginx übernommen wird, überprüfen wir die neuen Einstellungen und starten den Dienst neu:

__$ sudo nginx -t
__$ sudo systemctl restart nginx

If the Nginx configuration is ok, this feedback should come:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Launch and test Django App

We launch the Django app again:

__$ python app1/ runserver

Now we can access the domain through a browser and the Django debug page should appear.

Delete virtual environment

How to delete a venv environment is mentioned here only for the sake of completeness.

If still enabled, we exit the virtual environment deactivate:

__$ deactivate

Afterwards it is enough to delete the folder with rm:

__$ rm -rf /var/www/