Django is a versatile, powerful, efficient and ever-evolving python-based web application framework that you can use to get your web application up and running. It’s a popular web framework and usually comes with a development server used for locally testing your code. If you intend to push your applications to production, then a more robust and secure web server set up will be required.
In this guide, we will take you through the art of professionally deploying and configuring Django in a more efficient and resilient manner. We will install and configure PostgreSQL database in the place of the SQLite database, and integrate the Gunicorn application server which will interface with the applications. Later, we will proceed with setting up the NGINX web server which will provide a reverse proxy channel to the Gunicorn server, thereby providing us with the required performance and security components to serve our applications.
Table of Contents
- 1 Prerequisites
- 2 Objectives
- 3 Getting started
- 4 Creating PostgreSQL database and database user
- 5 Creating a Python Virtual Environment for your Django Project
- 6 Creating and configuring a new Django project
- 7 Adjusting Project files configuration
- 8 Completing initial project setup
- 9 Confirming Gunicorn’s ability to test the Project
- 10 Verify for the presence of Gunicorn Socket file
- 11 Troubling tips
- 12 Conclusion
Prerequisites
Before we get started, let’s perform a quick flight check and ensure we have the following
- A fresh Ubuntu 18.04 server instance
- A non-root user with sudo privileges. In this guide we shall use
james
- SSH access to the server
Objectives
Here is a quick walkthrough of what we intend to achieve. We are going to install Django within a virtual environment. This allows for projects to be handled separately from the host environment. We shall then embark on installation and configuration of Gunicorn application server which will interface the application and translate requests in HTTP protocol to Python calls which our application can process. Finally, we will set up Nginx which is a high-performance web server with a myriad of security components which will boost the security of our applications.
Getting started
To start off, we will begin by updating our Ubuntu repositories
$ sudo apt-get update
Next, we will download and install the requisite software packages for all the applications we are going to configure. These will include the pip package manager, PostgreSQL, and Nginx.
If you are running Django with Python3, the syntax will be
$ sudo apt-get install python3-pip python3-dev libpq-dev postgresql postgresql-contrib nginx
If your system runs Python2, the command will be:
$ sudo apt-get install python-pip python-dev libpq-dev postgresql postgresql-contrib nginx
Sample output
The command will install pip package manager, all the necessary Python development files required to build Gunicorn, Nginx web server, Postgres database and the crucial libraries required to interact with the database engine.
After successful installation of all the required software packages, its time now to create a database and a user for the Django web application
Creating PostgreSQL database and database user
Let’s now jump right in and create a database and a user for Django application
Log in to Postgres and execute the command
$ sudo -u postgres psql
You will drop to the Postgres prompt as seen in the output
Create a database for your project. In this case, the database name is “project”.
postgres=# CREATE DATABASE project;
Sample output
NOTE! All commands in PostgreSQL need to be terminated with a semi-colon.
After successful creation of the database, create a user for the Django project and be sure to assign a secure Password. In this case, the user is projectuser
Replace the ‘password’ attribute with your own strong Password.
postgres=# CREATE USER projectuser WITH PASSWORD 'password';
Sample output
To confirm to Django’s set up parameters for database connectivity, we are going to:
- Set default encoding to UTF-8
- Set isolation scheme to ‘read committed’ value
- Set timezone to UTC
Execute the commands below to fulfill the setup requirements
postgres=# ALTER ROLE projectuser SET client_encoding TO 'utf8';
postgres=# ALTER ROLE projectuser SET default_transaction_isolation TO 'read committed';
postgres=# ALTER ROLE projectuser SET timezone TO 'UTC';
Let’s now grant database access to our newly created user
postgres=# GRANT ALL PRIVILEGES ON DATABASE project TO projectuser;
Sample output
Now you can exit from the Postgres environment.
postgres=# \q
Creating a Python Virtual Environment for your Django Project
For easier management of our application, we are going to install the required Python prerequisites in a virtual environment.
But first, let’s install the virtual environment
If your system is running Python3, upgrade pip by executing
$ sudo -H pip3 install --upgrade pip
For a system running on Python2 run
$ sudo -H pip install --upgrade pip
Sample Output
Thereafter, install the virtual environment using pip
$ sudo -H pip install virtualenv
Sample Output
With our virtual environment in place, we are now going to create and move into it
$ mkdir ~/project
$ cd ~/project
Now create a Python virtual environment
virtualenv projectenv
Sample output
This creates a directory called projectenv
within project directory.
Before installing Python requirements, activate the virtual environment
$ source projectenv/bin/activate
Sample output
Notice how the prompt changes to (projectenv)james@ubuntu: ~/project$
With the virtual environment active, let’s install Gunicorn, Django, and Psycopg2 Postgres adapter using pip.
$ pip install django gunicorn psycopg2-binary
Sample Output
Creating and configuring a new Django project
At this point, we shall instruct Django to install project files in the project directory that we already created. A second level directory will be created alongside a management script.
To achieve this execute the command below.
$ django-admin.py startproject project ~/project
Our project directory, in this case, ~/project
should have the following contents
manage.py - Django’s Python management script
project - directory containing Django’s project package
projectenv - Virtual environment directory that was earlier created
Sample output
Adjusting Project files configuration
We are going to open the settings file
vim ~/project/settings.py
Scroll down and be on the lookout for the ‘ALLOWED HOSTS’ attribute. Within the square brackets, specify the server’s IP address and append the ‘localhost’ attribute.
Sample output
Next, locate the ‘DATABASES’ section. Adjust the settings to conform to the PostgreSQL database information. This includes the database name, user and password of the user.
Sample output
Next, scroll down and specify the location of the static files. This is important so that Nginx can seamlessly handle requests for these items. From the snippet below, static files will be placed in a directory called ‘static’
Save and Exit.
Completing initial project setup
Let us now migrate the database scheme to our PostgreSQL database
~/project/manage.py makemigrations
Sample output
~/project/manage.py migrate
Sample output
Next, we are going to create a super user for the Django project by executing the following command
~/project/manage.py createsuperuser
You will be prompted for a username, email, password as shown below
Sample output
Upon successful creation of the superuser, we can now collect static content into the directory location
~/project/manage.py collectstatic
Sample output
These static files will be placed in the ‘static’
directory in the project folder.
To test the development server, we are going to allow a port, in this case, port 8000 which will be used for accessing the application via a web browser.
To open the port, we will execute
sudo ufw allow 8000
Finally, start the Django development server in the virtual environment by running
~/project/manage.py runserver 0.0.0.0:8000
Open your web browser and visit your server’s address
In this case, our server’s address is
https://38.76.11.180/
You should be able to see the following Django’s index page
Sample output
Let’s now append /admin at the end of the URL to get to the login page
https://38.76.11.180/admin
Sample output
Provide the credentials you provided when creating the superuser account and hit ‘Login’
This will take us to the Django’s Admin panel
Sample output
Great! Now that we have confirmed that Django is up and running, let’s hit CTRL + C
on the terminal to exit the application
Confirming Gunicorn’s ability to test the Project
Before exiting the virtual environment, let’s verify that our Gunicorn application server can serve the Django.
While still in the project directory, let’s load the WSGI module
gunicorn --bind 0.0.0.0:8000 project.wsgi
This fires up Gunicorn on the same interface and port that the Django server was running on. You can go back and verify that the Django application is running on the web browser.
With that done hit CTRL + C to stop the application and run the deactivate command to exit the virtual environment.
Having tested that the Gunicorn application server can serve our Django application, it’s time to implement a more robust avenue of starting and stopping the application server.
We are going to create a systemd service file with Gunicorn using the following command
$ sudo vim /etc/systemd/system/gunicorn.service
Starting with the [Unit] section, paste the following content
[Unit]
Description=gunicorn daemon
After=network.target
This section specifies dependencies and metadata
Next, create the [service]
section.
In this section, we shall specify the user and group that the process should run under. In this case, the user is root and the group is www-data. The group is specified as www-data so that Nginx can seamlessly communicate with Gunicorn.
The full path to Gunicorn executable will then be indicated. Since Nginx is installed within the same server, we will bind it to a Unix socket.
Paste the following content
[Service]
User=james
Group=www-data
WorkingDirectory=/home/james/project
ExecStart=/home/james/project/projectenv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/home/james/project/project.sock project.wsgi:application
Finally, create [Install] section and append the following lines
[Install]
WantedBy=multi-user.target
Great! Now our systemd service file is complete.
Save and close the text editor. Restart Gunicorn service and enable it to start on boot
$ sudo systemctl start gunicorn
$ sudo systemctl enable gunicorn
To check the status of gunicorn run
$ systemctl status gunicorn
Sample output
Verify for the presence of Gunicorn Socket file
Now that we have verified that Nginx is up and running, let us verify the existence of the project.sock file in our project directory
$ ls -l /home/james/project
Sample output
NOTE:
If project.sock file is missing, this is an indicator that gunicorn was not able to start correctly.
Additionally, you can check the gunicorn logs by executing the command below
$ sudo journalctl -u gunicorn
There are a number of reasons why Nginx could not have created the project.sock file. Some include
- Project files being owned by root user instead of sudo user
- The working directory within the
/etc/systemd/system/gunicorn.service
not pointing to the project directory. - Incorrect configurations in the ExecStart directive
If all configurations are okay, then you should not get any errors after running
$ sudo journalctl -u gunicorn
Once you make changes to the /etc/systemd/system/gunicorn.service
file, ensure that you reload the daemon service for the changes to take effect
$ sudo systemctl daemon-reload
$ sudo systemctl restart gunicorn
Configuring Nginx to direct traffic to Gunicorn
The last phase in this guide is configuring Nginx web server to channel web traffic to Gunicorn service
We shall create and open a new server block in sites-available
directory
$ sudo vim /etc/nginx/sites-available/project
Begin by specifying that this block should be listening to port 80 and should respond to the server’s IP address.
server {
listen 80;
server_name server_domain_or_IP;
}
Next, we shall instruct Nginx to ignore any issues with locating the favicon. Additionally, we will tell it where to locate static assets collected in ~/project/static
directory
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /home/james/project;
}
Lastly, we will create a block location / { }
which will match the rest of the requests.
Within this location, we shall define proxy_params file and later direct traffic to the socket created by the Gunicorn process
location / {
include proxy_params;
proxy_pass https://unix:/home/james/project/project.sock;
}
The final configuration file should look something like this
Save and Exit the configuration file
Let’s Enable the file by linking it to the sites-enabled directory
$ sudo ln -s /etc/nginx/sites-available/project /etc/nginx/sites-enabled
Next, let us test our configuration for any errors
$ sudo nginx -t
If all went well, the output should be as shown below
After testing the Nginx configuration
$ sudo systemctl restart nginx
Now that we no longer need access to the development server on port 8000, let us remove the rule on the firewall.
$ sudo ufw delete allow 8000
Let us also allow port 80
$ sudo ufw allow 'Nginx Full'
You should now be able to go to your server’s domain or IP address to view your application without specifying port 8000
Troubling tips
1.Server displaying default Nginx page
If Nginx displays the default page instead of proxying to the Django application, you need to check the /etc/nginx/sites-available/project
file and ensure your server’s IP or domain name is correctly indicated. The default page implies that Nginx wasn’t able to match your request and instead was falling back on /etc/nginx/sites-available/default
2. 502 Bad gateway error instead of Django application
A 502 error is an indication that Nginx web server was not able to successfully proxy the request. To accurately troubleshoot the problem, check out the logs in the error log file as shown and this could give you a clue as to what could be wrong.
$ sudo tail -F /var/log/nginx/error.log
3. “Could not connect to server” Connection refused
An error you may come across when trying to access some certain components in your web browser is
OperationalError at /admin/login/
could not connect to server: Connection refused
Is the server running on host "localhost" (127.0.0.1) and accepting
TCP/IP connections on port 5432?
This shows that the Django application is unable to connect to the Postgres database
Ensure that Postgres is running by executing
$ sudo systemctl status postgresql
If it’s not running, start it and enable it to start on boot
$ sudo systemctl start postgresql
$ sudo systemctl enable postgresql
If you are still having issues, ensure that database settings in
~/myproject/myproject/settings.py
are correct.
For further troubleshooting, examine the following logs
Nginx process logs sudo journalctl -u nginx
Nginx access logs sudo tail -f /var/log/nginx/access.log
Nginx error logs sudo less /var/log/nginx/error.log
Gunicorn Application logs sudo journalctl -u gunicorn
As you make changes to the configuration files, ensure that you restart them for the changes to take effect.
If you decide to make changes to the gunicorn systemd service file, make sure you reload the daemon and restart gunicorn
$ sudo systemctl daemon-reload
$ sudo systemctl restart gunicorn
When you make changes to the Nginx server block configuration test the config
$ sudo nginx -t && sudo systemctl restart nginx
Conclusion
Thank you for taking the time in this article. In this article, we set up a Django project in a virtual environment and set up Gunicorn to translate client requests and Nginx to proxy traffic to our Django application.
I did exactly the same thing but gave me a 502 bad gateway or invalid http_host_header
At this stage I shouldn’t be able to see django starting page since nginx is not configured yet. This is confusing.
“””
Open your web browser and visit your server’s address
In this case, our server’s address is
https://38.76.11.180/
You should be able to see the following Django’s index page”
“””
Great Tutorial, thanks. You saved my day.