Deploying Django on VPS is an easy task if and only if you know some system administration tasks. You may think on which I have written this statement. But believe me, it’s my personal realization when I deploy my personal blog (made on Django). Let’s go back.

Previously, my personal site was on WordPress, a PHP based CMS. It is free, Open Source and the top blogging CMS.. Everything was going right. But one day, I thought I am a Python lover and neither a PHP geek nor I have ever loved it. So why should I run my blog on WordPress? I didn’t get any suitable answer from myself. So I hit upon a plan and moved to Django.

Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design. Built by experienced developers, it takes care of much of the hassle of Web development, so you can focus on writing your app without needing to reinvent the wheel. It’s free and open source.

Probably Django is the best web framework. Django is written in Python. So I have got the full control of my blog. Currently, it is hosted on my personal VPS running on Ubuntu Server 16.04.1 LTS. Let’s start the deployment.

Update Server Link to heading

We have to make sure that the server is updated to its latest stable release. So connect to your server via SSH, become root by typing su or sudo su if necessary and run this command:

$ do-release-upgrade

This will upgrade your current Ubuntu to a newer stable release. If you aren’t confident about doing this or already using the latest one, then just skip it. Also be noted, I recommend a fresh installation of latest Ubuntu and to back up all necessary data. But you can always try these at your old setup at your own risk.

If you are already using the latest version of Ubuntu, then we will run a command to get latest packages list from repositories:

$ apt update

This command may show you some upgradable packages, let’s upgrade them:

$ apt upgrade -y

Got it? Great! Now we have updated our server.

Upload Project Link to heading

Now we will upload our project to our server. We can keep them anywhere on the server. But as /home is allocated with most of the space generally, we will keep them on /home. Let’s make a folder named www:

$ mkdir /home/www

We will upload our project in this folder. Let’s assume that our project name is **maateen **and the app in it is named myblog. Obviously, maateen is a folder. We can upload it with FileZilla via SFTP. To install FileZilla, just run the command:

$ apt install filezilla -y

Hope, you can upload the project by yourself. Do some Google search for the tutorial. Be noted that I am using Git to put my project on server. You can do so.

Install WebServer Link to heading

Now sit and relax. You have to take a decision. There are some good webservers on market. Most of them are free and opensource while paid webserver like LiteSpeed is also available. NginX, Apache, Lighttpd, Cherokee are the best among free webservers. But I like NginX most. It is faster, lighter and secured. Let’s install it with this command:

$ apt install nginx -y

After installing NginX, you need to set it up. For myself, my domain is already pointing to my server. You should set up DNS server and update your nameserver so that your domain point your server. We will do some tweaks in NginX configuration with this command:

$ nano /etc/nginx/nginx.conf

Paste all contents from below and save it. Actually, your nginx.conf file should look like below:

user www-data;
worker_processes 1;
pid /run/;

events {
    worker_connections 1024;
    multi_accept on;

http {

    # Basic Settings

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    keepalive_requests 100000;
    types_hash_max_size 2048;
    server_tokens off;

    server_names_hash_bucket_size 64;
    server_name_in_redirect off;

    client_body_buffer_size 128k;
    client_max_body_size 10m;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 4k;
    output_buffers 1 32k;
    postpone_output 1460;
    client_header_timeout 3m;
    client_body_timeout 3m;
    send_timeout 3m;

    open_file_cache max=1000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 5;
    open_file_cache_errors off;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # SSL Settings

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
    ssl_prefer_server_ciphers on;

    # Logging Settings

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    # Gzip Settings

    gzip on;
    gzip_disable "msie6";
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_types text/plain text/html text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    # Custom Settings
    proxy_cache_key "$scheme$request_method$host$request_uri$cookie_user$cookie_jessionid";

    # Virtual Host Configs

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;

Let’s set up NginX to play with

$ nano /etc/nginx/sites-available/

Paste all contents from below and save it.

proxy_cache_path /home/nginx/cache/maateen levels=1:2 keys_zone=maateen:10m inactive=60m max_size=1g use_temp_path=off;

upstream maateen_server {
    # fail_timeout=0 means we always retry an upstream even if it failed
    # to return a good HTTP response

    # for UNIX domain socket setups
    server unix:/home/www/maateen/gunicorn.sock fail_timeout=0;

server {
    # use 'listen 80 accept_filter=httpready;' for FreeBSD
    listen 80;

    # set the correct host(s) for your site

    location / {
        # checks for static file, if not found proxy to app
        try_files $uri @proxy_to_en;

    location /static {
        alias /home/www/maateen/myblog/static;
        expires 30d;
      access_log off;
      add_header Pragma public;
      add_header Cache-Control "public";

    location /static/media {
        alias /home/www/maateen/static/media;
        expires 30d;
      access_log off;
      add_header Pragma public;
      add_header Cache-Control "public";

    location @proxy_to_en {
        proxy_cache maateen;
        proxy_cache_revalidate on;
        proxy_cache_min_uses 5;
        proxy_cache_lock on;
        proxy_cache_valid 404 1m;
        proxy_cache_methods GET HEAD POST;
        proxy_no_cache $http_pragma $http_authorization;
        proxy_cache_bypass $cookie_nocache $arg_nocache$arg_comment;
        proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;

        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        add_header X-Cache-Status $upstream_cache_status;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_redirect off;
        proxy_buffering on;

        proxy_pass http://maateen_server;

Install MySql Link to heading

I guess you are using MySQL as database. Me too! Let’s install it:

$ apt install mysql -y

Normally Django won’t work with this set up. It needs some extra packages to work with MySQL. Let’s install them:

$ apt install libmysqlclient-dev python-dev python-MySQLdb -y

For Python3, it should be as follows:

$ apt install libmysqlclient-dev python3-dev python3-MySQLdb -y

That’s all.

Install WSGI Server Link to heading

Now we will set up a Python WSGI server. There are some great servers like Gunicorn, uWSGI, FAPWS etc. Among them, I like Gunicorn most. It is simple and easy to use. Let’s install it:

$ pip install gunicorn

If you are using Python3 like me, then run the following commands:

$ apt install python3-pip -y
$ pip3 install gunicorn

Installation is over. Now we will run Gunicorn and try to visit our site. We have to go to our project directory:

$ cd /home/www/maateen

Now we will run Gunicorn here and see the output by entering our site via browser. Let’s run the command:

$ gunicorn maateen.wsgi --name=maateen --workers=3 --bind=unix:/home/www/maateen/gunicorn.sock --pid /home/www/maateen/ --error-logfile=/home/www/maateen/error_logs.log --log-level=error

To count the workers, just follow this rule: 2n+1 where n is the number of core. If you can see your website by hitting the domain into browser, then everything is right. Otherwise check and recheck.

Automate everything Link to heading

When the server is rebooted or started, the NginX and Gunicorn should start automatically. By default, NginX has that feature. But what about Gunicorn? It can’t start on booting automatically. We can use Gaffer, Procfile, Runit, Supervisor, Upstart, Systemd etc. But I like Runit most. It’s easy to set up. Let’s install it:

$ apt install runit
$ mkdir -p /etc/sv/maateen && mkdir -p /etc/service/maateen
$ nano /etc/sv/maateen/run

Paste the following things in it.


cd /home/www/maateen
exec gunicorn maateen.wsgi --name=maateen --workers=3 --bind=unix:/home/www/maateen/gunicorn.sock --pid /home/www/maateen/ --error-logfile=/home/www/maateen/error_logs.log --log-level=error

Let’s complete the automation:

$ ln -s  /etc/sv/maateen  /etc/service/maateen

Final Touch Link to heading

Reboot the server and wait till it wakes up again.

$ reboot

Now try to browse your site. Hope everything is okay. If not, please check you have followed me properly. Still not okay? Let me know via comment.

N.B.: I haven’t gave it a proof-reading. If I do any mistake, please let me know.