#1 Setup the Remote Host

Let's prepare the remote host first. When you start with a new server, you need to create a non-root user account, setup hostname and most importantly take care of ssh access.

I will skip the obvious - don't run Django in production with built-in web server, don't use DEBUG=True in production... you already should have heard that many times.

There are couple of scenarios to consider, for example deploy database on different computer or use S3 as media storage or deploy static files to some CDN. But the truth is, that even the absolute minimum deployment with everything (database, application, static files) located on single computer is complex enough for beginning, why complicate it more?

So, in this guide, we will aim to move our Django based project on single remote computer. The anatomy of simplest Django application in production will look like in picture below.


Our production toolset:

  1. Nginx (web server)
  2. PostgreSQL (database)
  3. Django (the main protagonist)
  4. Gunicorn (application server)

The numbered order corresponds to the order in which we will install them. In this lesson we not going through any of those 4 points. In this lesson we will take care of something, which needs care - even before you start whole deployment adventure. We will take care to setup of our SSH right way.

SSH with Public Key Authentication

On your rented VPS you have either root access or maybe some other privileged user. You must make sure you have public key authentication instead of password based. There two reasons for this:

  • It is more secure
  • It saves you time on typing password every time you login

Also you may consider to configure your ssh to alias your server (I will explain below what I mean); boths of these steps are necessary because you will be SSHing a lot into remote machine, typing one character less will make a big difference. So, instead of:

$ ssh ubuntu@     # and then password
$ ssh root@mydomain.com # and then password

You will type:

$ ssh u  # ! right, just letter 'u' and no password; it will use public key authentication instead

At this point you are root user logged into remote machine. I will skip morale talk why you must not run commands as root. Instead, create another user and add it to sudo group.

root@localhost:~$ useradd -m -s /bin/bash -G sudo john  # add user john, with home directory and with bash as default bash and include him in sudo group.
root@localhost:~$ passwd john   # set password for john user

Double check that user john was created and he belongs to sudo group:

root@localhost:~$ cat /etc/passwd   # user john will be listed at the bottom of the file
root@localhost:~$ groups john  # should list two groups: john and sudo

So far so good. But we still don't use public key authentication for user john. Let's solve that first. The idea is very simple, we create on your local computer a pair of files, one is secret file (private key), and the other one you can share with everybody (public key). We will copy public key file to remote host (using ssh-copy-id command) thus telling remote host to allow log in of any user with private key which corresponds to this public key.

$ ssh-keygen -f ~/.ssh/johnk   # generate public/private keys named identified as johnk, to ~/.ssh folder
$ Generating public/private rsa key pair.
$ Enter passphrase (empty for no passphrase): # leave passphrase empty
$ ssh-copy-id -i ~/.ssh/johnk john@<remote-ip>  # copy public key identified with johnk to remote machine john@<remote-ip>

You will be asked password for user john from remote host. And then you will be able to ssh john@<remote-ip> without typing password (ssh will authenticate your account based on your private key).

And last step, which is extremely important - is to disable password based authentication on the remote server. Edit /etc/ssh/sshd_config. Change following settings:

PubkeyAuthentication yes
PermitRootLogin no
# Disable password based authentication
PasswordAuthentication no
ChallengeResponseAuthentication no
MaxAuthTries    2

Restart you sshd server with sudo systemctl restart sshd for changes to take effect on the server.

Type Less with SSH Host Aliasing

And finally, create a file ~/.ssh/config on your local computer with following content (i.e configure your local ssh client):

Host jo
    ForwardAgent yes 
    Hostname <remote-ip>  # replace with actual IP
    User john
    IdentityFile /home/<whatever-user-your-local-machine-has>/.ssh/johnk

Above ssh configuration is really cool, it enables you to just type ssh jo and ssh will login you into remote host john@<remote-ip> without asking password! Obviously instead of jo can be any name you like. Even more, if you use more remote machines, you can configure something like this:

Host *
    IdentityFile /home/<whatever-user-your-local-machine-has>/.ssh/johnk
     ForwardAgent yes

Host jo
    Hostname <remote-ip>  # replace with actual IP
    User john

Host db
    Hostname <ip-or-dns-of-db-server> 
    User johndb

Host ap1
    Hostname <ip-or-dns-of-some-app-server> 
    User appuser

Didn't we plan to deploy a Django application ? Sure! This is our goal, but don't rush. There are many steps in between and each step needs to be executed properly. We just accomplished one step, but extremely important one - we configured our local ssh client. Now, let's give remote machine a proper name. We will name remote machine - john-systems. To accomplish this, use systemd's command hostnamectl

$ sudo hostnamectl set-hostname john-systems

If you log out and log in again (or alternatively enter a new bash by typing... bash), you will notice that prompt changed to: john@john-systems.

Now, let's take care of computer DNS record. Instead of referring to it by IP we will give it a DNS name. For that, you need to have a domain registered with registrars like namecheap. I will use as example my own domain dglte.net. You need to add a so called A Record (via web interface of your registrar). In my example I will map production-demo.dglte.net to external IP address of my VPS server . When done, you will need to way up to 24 hours for DNS server to update. At any point in time, you can use dig command to check if A record maps to your IP address:

$ dig production-demo.dglte.net -t A

When DNS records were updated, dig command's response will be something like:


#1 Setup the Remote Host

Let's prepare the remote host first. When you start with a new server, you need to create a non-root user account, setup hostname and most importantly take care of ssh access.

#2 Install and Configure Nginx

Next we focus on webserver. For this course I chose nginx as webserver. In this part of the course I will explain where nginx fits in whole production picture and how it relates to django application in general.

#3 Automation with Ansible

Ansible is a fantastic tool to have in your arsenal, it is practical, simple to use and a huge time saver. In this part of the course I introduce you to main concepts of this awesome tool. Don't skip it.

#4 Database

PostgreSQL - is the database of choice for this course. PostgreSQL is an extremely powerful database. The best part is that it is easy to setup and in this episode I will show you how.

#5 The Heart of it - Gunicorn + Django!

Gunicorn + Django + Nginx is the killer combo for production environment. It is extremely important to understand how they interact and how they relate to each other. In just 12 minutes you will learn the core of any production environment.

#6 Ansible Roles for Gunicorn and Application

In this unit we complete our ansible playbook. We will add two more ansible roles: one for gunicorn and one for django application. Here I assume that you completed previous lesson so that I will skip gunicorn details and I will focus only on ansible related material.


Without HTTPS (secured http) your application is not production ready. In this lesson you will learn how to secure your web application with certbot (Let's Encrypt Client)