Steps to Deploy Django with Postgres, Nginx, and Gunicorn on Ubuntu 18.04
What’s the difference between a proxy server and a reverse proxy server?
Show all

Deploying Django application to a production server

12 mins read

If you have been developing your web application with Django on a development server and wondered, how I’m going to put this into production on a real server?

In this post we are going to set up our awesome Django app on an ubuntu server ( 17.10.1) with Nginx, gunicorn, and MySQL, don’t worry about those terms, I will explain what they are.

some outline of what we are going to do.

  • Installing necessary packages.
  • Initial server setups.
  • Preparing our Django app for production.
  • Setting up gunicorn.
  • Setting up Nginx.

Before we get started my terminal is working under /home/masoud directory so change yours to that to follow up with me. Of course change masoud to your username

Okay, I think we’ve had enough of the boring intro, lets’s do some real work.

Installing necessary packages.

To follow along you should have a fresh instance of your ubuntu server with a non root user created, if you don’t have access to a real server somewhere don’t worry you can install a copy of ubuntu 17.10 on your local machine or virtual machine and follow along, I’m using one myself, it will do just fine.

to get started we need to install some packages, run the following commands, and answer the questions to install them

$ sudo apt-get update
$ sudo apt-get install nginx mysql-server python3-pip python3-dev libmysqlclient-dev ufw virtualenv

I am using python3 on this tutorial but if you like python2 don’t worry you can always run the following commands, with just the 3 removed

$ sudo apt-get update
$ sudo apt-get install nginx mysql-server python-pip python-dev libmysqlclient-dev ufw virtualenv

Wait! what did we just install?


This is not a Nginx tutorial but basically, Nginx is the webserver that handles the requests from the browser and serves web pages in response to browser requests. There are many other software like this such as apache and Lighttpd, if you get familiar with things I suggest you go over and do research on these because you might have a change of mind depending on your needs.

We’ve also installed mysql-server and libmysqlclient-dev, these packages will install MySQL on your ubuntu and some MySQL development files that are required to make things work. Again you could go with some other options such as PostgreSQL, they all work fine, it all depends on your app requirements but avoid SQLite on a production server if you know your site is going to get heavy traffic.

We’ve also installed python development files and python package manager (pip) which you must be familiar with as you are developing with Django. If you haven’t used it, it’s used to install python packages. We’re going to use a virtual environment so we installed virtualenv. Virtual environments are a way we use to isolate one project package from the other to avoid conflicts, that’s because each app will be running in its environment. So you can use the old version of this package on one app and a new version of the same package together within the same server without conflicts. Cool isn’t it?

One last package is ufw, this is the firewall software, we are going to use it to control at what ports should we be expecting the request to come from and block all other ports, In a few seconds we are going to do that.

Initial server setup.

First, we are going to set up the firewall, for now, we are going to block all ports and allow only ports 8800 and 3306 for testing, this will later be changed when everything is set up ready. On your terminal type the following commands

$ sudo ufw default deny
$ sudo ufw allow 8800
$ sudo ufw allow 22
$ sudo ufw enable

Note that I also allowed port 22, that’s in case you are connecting to the remote server with ssh while going along with the article. To see the status of the ufw type sudo ufw status. Now that the firewall is up and running let’s setup MySQL.

run the following command to setup MySQL on our server

$ mysql_secure_installation

follow the steps and make sure you disallow remote login unless you have plans for it, setup a new root user password. After setting up MySQL we need to create the database that our Django app is going to use, I’m going to call my app awesome so the database name is going to be awesome, to do that run the following commands.

$ mysql -u root -p 
mysql> CREATE DATABASE awesome CHARACTER SET 'utf8';
mysql> CREATE USER masoud;
mysql> GRANT ALL ON awesome.* TO 'masoud'@'localhost' IDENTIFIED BY 'secret';
mysql> quit

we have created our database and a user masoud and gave him all access to our database, this is good because masoud can not access other databases on our system.

so now we have our firewall setup, MySQL is set and we already have one database on it, let’s make our awesome Django app and configure it to use the database we have just created. We said we are going to use a virtual environment on our app so how do we do that? this way,

$ virtualenv venv
$ source venv/bin/activate
(env) $ pip install mysqlclient django==1.11

I called my virtual environment venv, you can name it anything and it will still work, to know that things went fine you will see the name of your virtual environment in brackets before the money sign on your terminal. note that I installed Django too, you should also install it too but replace that 1.11 with the Django version your app is running on. I assume you have your Django app already so I’m not going to focus only on necessary parts. For good practice put your Django app under the same directory as your virtual environment folder, if you are following along with me put the folder containing your virtual environment under the home in the <username> folder. mine is under /home/masoud and it’s called webapp. My awesome app has the following structure ( the default one).

            |- venv/
            |- AWESOME_PROJECT/
                |- awesome/
                |- AWESOME/

AWESOME_PROJECT is the project name and awesome is the app name. ofcourse it has all those models, url, and forms files I just didn’t put them as they are not necessary here.

on your settings file change the following settings.

DEBUG = False
     'default': {
         'ENGINE': 'django.db.backends.mysql',
         'OPTIONS': {
             'sql_mode': 'traditional',
         'NAME': 'awesome',
         'USER': 'masoud',
         'PASSWORD': 'secret',
         'HOST': 'localhost',
         'PORT': '3306', 
# direcotries for static filesSTATIC_URL='/static/'
STATIC_ROOT=os.path.join(BASE_DIR, 'static/')MEDIA_URL='/media/'
MEDIA_ROOT=os.path.join(BASE_DIR, 'media/')

some settings are new and some are just adjusted, for example DEBUG=False in order to prevent Django to expose errors to our users. We also added allowed hosts ALLOWED_HOST=[’’,] you need to put your domain name there if you have purchased one or your server IP address. We also changed the database dictionary field and added our own, this will shift our project from using SQLite to MySQL database, if you observe you will see we have put our database info. Lastly, we added our static files URL where our CSS will live and media files URL where the uploaded images and other media files will live. That’s all you need to set up on your Django app, after that run the migration commands.

(env) $ python makemigrations
(env) $ python migrate
(env) $ python collectstatic

you can run the app on the development server to test if things are still working fine.

(env) $ python runserver

On this part, we are going to setup gunicorn first and lastly Nginx so both of us can go to sleep.

I realized I say Nginx and gunicorn a lot what are they anyway?

Nginx is a web server that serves static files to the browser’s request, static files such as HTML, CSS, and images but if the request requires some logic that your python code executes, that’s where gunicorn comes in. Gunicorn connects to your python code through the web server gateway interface (WSGI). The WSGI was designed to provide a way for the server to interact with your python code.

So how this configuration work is that, when a request from the browser comes, Nginx checks if the request is for static or dynamic content. If the request is for static content nginx serves them to the browser but if the request is for dynamic content and requires some logic to be done, it passes the request to gunicorn, and through WSGI gunicorn sends the request to your python code, and python code will do the logic and provide the response. The response is sent back to Nginx and hence the browser receives it.

We need Nginx to serve static content and gunicorn to serve dynamic content, though that is not the only thing that Nginx does, there are a lot more things that Nginx does such as acting as a reverse proxy server, you can visit here what is nginxto know more.

setting up gunicorn

while still in the virtual environment install gunicorn with the following command

(env) $ pip install gunicorn

if the development server is still running kill it change the directory to your project folder and run the following command to run your site with gunicorn

(env) $ gunicorn --bind AWESOME.wsgi:application

remember I told you that gunicorn uses wsgi to interact with your python code, now that is what we just did. We pointed gunicorn to the WSGI located under AWESOME folder in the AWESOME_PROJECT directory. If you look you will see the file called and inside there will be a variable called application, that is where we pointed gunicorn to and now it’s interacting with our python code. If you visit admin page you will notice that there will be no CSS, that is because Nginx is not run yet.

kill gunicorn and exit the virtual environment with the following command

(env) $ deactivate

to finish up gunicorn setup we need to create gunicorn service file so that it runs when the system starts.

$ sudo nano /etc/systemd/system/gunicorn.service

paste the following configurations

Description=gunicorn service
ExecStart=/home/masoud/webapp/env/bin/gunicorn --access-logfile - --workers 3 --bind unix:/home/masoud/webapp/AWESOME_PROJECT/awesome.sock AWESOME.wsgi:application

Of course, you need to change some variables to match your project’s location on your server and the user on your server.

on the User field change masoud to your username, on the WorkingDirectory put the absolute path of your project’s directory, also on the ExecStrat change the directories to match yours. You should make sure you change the variables right otherwise you will find yourself in trouble. Type the following command to start gunicorn.

$ sudo systemctl enable gunicorn.service
$ sudo systemctl start gunicorn.service
$ sudo systemctl status gunicorn.service

If you run into trouble, in most cases you will just type the following command to see what went wrong

$ journalctl -u gunicorn

if you changed something restart gunicorn with the following command

$ sudo systemctl daemon-reload
$ sudo systemctl restart gunicorn

if you are interested to know more about what’s on the config file we just created, visit this documentation gunicorn docsI encourage you to read that, it’s a better way to know what you’re doing.

Configuring Nginx

To configure nginx we need to create a config file that will tell Nginx how to respond to a client’s request. The file will live under Nginx’s sites-available folder.

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

you can change awesome to another name, paste the following configurations

server {
       listen 80;    
       location = /favicon.ico {access_log off;log_not_found off;} 
       location = /static/ {
         root /home/masoud/webapp/AWESOME_PROJECT/AWESOME;    
       }       location = /media/ {
         root /home/masoud/webapp/AWESOME_PROJECT/AWESOME;
       location = / {
         include proxy_params;
         proxy_pass http://unix:/home/masoud/webapp/AWESOME_PROJECT/AWESOME.sock;

change the directories to match yours, the first line tells Nginx to listen on port 80 and the second provides it with the servers IP address to respond to, put the IP address of your server or the DNS address, each location variable tells Nginx where to find files for different requests, the first tells Nginx where to find favicon.ico and ignore errors if it could not find it, the second and third tells Nginx where to find static and media files respectfully, the last tells Nginx to match all other requests. You can visit this link to know more about those settings, nginx setup. If you’ve set up everything correctly run the following command

$ sudo ln -s /etc/nginx/sites-available/awesome /etc/nginx/sites-enabled

that will add a link to our new configuration file and that way Nginx will recognize it. Run the following command to test your configurations

$ sudo nginx -t

if everything worked fine restart Nginx with the following command

$ sudo systemctl restart nginx

Before it’s over we need to change our firewall rules and add port 80 because it is currently blocked. Type the following commands to do that

$ sudo ufw delete allow 8800
$ sudo ufw allow 'Nginx Full'

if everything worked until now, you should be good to go. You can now go to your server IP or domain name to see your application.


Amir Masoud Sefidian
Amir Masoud Sefidian
Machine Learning Engineer

Comments are closed.