Installing Django on DigitalOcean — Their step-by-step–ahem!–instructions, annotated

This post contains a complete and unedited duplicate of (downloaded 8/6/14)

It was extremely difficult following these instructions. Many steps did not work.

The grey italicized boxes

explain how I got through it. The significant change I made was to configure my virtualenv to use Python 3.4 exclusively, although I can’t know if this is what made it so difficult.

(I’m using "Ubuntu 14 x32", and Django 1.7c2.)

I’m on a Windows machine, using Putty/SSH to shell into the server, and SFTP Net Drive Free so I can edit its files on Windows (see the middle of step 8 for details).

How To Install and Configure Django with Postgres, Nginx, and Gunicorn


This tutorial assumes you have already set up your droplet (VPS) with Debian 7 or a similar distro of Linux (such as Ubuntu). If you have not already done this, please follow the tutorial on setting up a droplet here.

At the end of these pre-requisite instructions, I followed the Ubuntu path, to login as root exactly once, for the purpose of creating the secondary user account.

For convenience, I’ve broken this tutorial into two parts. The first part (steps 1 – 6) covers installation only. If you are a more advanced Django user who just needs help getting things installed you can stop at step 6. If you already have everything installed and just want to know how to configure everything, then skip ahead to step 7. If you feel like you need help from start to finish, then just go through the steps in order and you should have no problems. Let’s get started!

Step One: Update Packages

Before we do anything, we need to make sure that all the packages installed on our VPS are up to date. In order to do this, connect to your desired VPS via SSH and run the following commands:

sudo apt-get update
sudo apt-get upgrade

The first command downloads any updates for packages managed through apt-get. The second command installs the updates that were downloaded. After running the above commands if there are updates to install you will likely be prompted to indicate whether or not you want to install the updates. If this happens, just type “y” and then hit “enter” when prompted.

Step Two: Install and Create Virtualenv

Installing virtualenv is very simple. Just run the command below:

sudo apt-get install python-virtualenv

That’s all there is to it! Now let’s create our virtualenv so we can install Django and other Python packages within it:

    sudo virtualenv /opt/myenv

I’ve changed the previous step to

    sudo virtualenv -p /usr/bin/python3.4 /opt/myenv

in order to force everything to use Python 3.4. Both Python 2.7 and 3.4 are both installed by default but, according to Digital Ocean support, 2.7 cannot be removed. But I need/want 3.4 only. The p switch makes everything installed in the virtualenv use that Python version. Use ls /usr/bin/py* to see which versions are available.

An example of good virtualenv practice, is for each project to have a peer virtualenv folder


where most everything is installed redundantly–even generically useful utilities like the IPython shell. This makes it possible, when used along with the above -p option, for IPython to launch with Python 3.4 in one virtutalenv-project, and 2.7 in another.

I’ve also been told to consider using virtualenvwrapper, although I’ve not gotten around to it. It’s for managing a lot of virtualenvs.

Notice that a new directory “myenv” was created in the “/opt” directory. This is where our virtualenv will live. Make sure to replace “/opt/myenv” with the path to where you want your virtualenv installed. I typically put my env’s in /opt, but this is strictly preference. Some people create a directory named “webapps” at the root of the VPS. Choose whatever method makes the most sense to you.

Step Three: Install Django

Now we need to activate our virtualenv so that when we install Python packages they install to our virtualenv. This is how you activate your virtualenv:

source /opt/myenv/bin/activate

You should now see that “(myenv)” has been appended to the beginning of your terminal prompt. This will help you to know when your virtualenv is active and which virtualenv is active should you have multiple virtualenv’s on the VPS.

With your virtualenv active, we can now install Django. To do this, we will use pip, a Python package manager much like easy_install. Here is the command you will run:

pip install django

This step was a bit of an adventure. I’ve documented the details in this post:

It boils down to this:

    pip install django

is not good enough for two reasons. First, the sudo prefix is required, since (if you’ve followed their instructions) you are not logged in as root. Attempting this without sudo fails with

    Permission denied: ‘/opt/myenv/build’

Second, using pip without an explicit path, installs Django outside of the virtualenv. While it installs without error, it also ignores the virtualenv’s Python version, as discussed in the previous box.

The correct command is

    sudo /opt/myenv/bin/pip install django

You now have Django installed in your virtualenv! Now let’s get our database server going.

Step Four: Install PostgreSQL

Most Django users prefer to use PostgreSQL as their database server. It is much more robust than MySQL and the Django ORM works much better with PostgreSQL than MySQL, MSSQL, or others.

Since we don’t need our virtualenv active for this part, run the following command to deactivate:


This will always deactivate whatever virtualenv is active currently. Now we need to install dependencies for PostgreSQL to work with Django with this command:

sudo apt-get install libpq-dev python-dev

Now that you have done this, install PostgreSQL like so:

sudo apt-get install postgresql postgresql-contrib

PostgreSQL is now installed on your machine and ready to roll.

Step Five: Install NGINX

NGINX is an incredibly fast and light-weight web server. We will use it to serve up our static files for our Django app. To install it just run this command:

sudo apt-get install nginx

Keep in mind that you still need to start NGINX, but we will go over this in when we start configuring our VPS.

Step Six: Install Gunicorn

Gunicorn is a very powerful Python WSGI HTTP Server. Since it is a Python package we need to first activate our virtualenv to install it. Here is how we do that:

source /opt/myenv/bin/activate

Make sure you see the added “myenv” at the beginning of your terminal prompt. With your virtualenv now active, run this command:

pip install gunicorn

The correct command is

    sudo /opt/myenv/bin/pip install gunicorn

Gunicorn is now installed within your virtualenv.

If all you wanted was to get everything installed, feel free to stop here. Otherwise, please continue for instructions on how to configure everything to work together and make your app accessible to others on the web.

Step Seven: Configure PostgreSQL

Let’s start off our configuration by working with PostgreSQL. With PostgreSQL we need to create a database, create a user, and grant the user we created access to the database we created. Start off by running the following command:

sudo su - postgres

Your terminal prompt should now say “postgres@yourserver”. If this is the case, then run this command to create your database:

createdb mydb

Your database has now been created and is named “mydb” if you didn’t change the command. You can name your database whatever you would like. Now create your database user with the following command:

createuser -P

The correct command is

    createuser -P nameofuser

and, despite the next sentence, there are no prompts aside from the password.

You will now be met with a series of 6 prompts. The first one will ask you for the name of the new user. Use whatever name you would like. The next two prompts are for your password and confirmation of password for the new user. For the last 3 prompts just enter “n” and hit “enter”. This just ensures your new users only has access to what you give it access to and nothing else. Now activate the PostgreSQL command line interface like so:


Finally, grant this new user access to your new database with this command:


You now have a PostgreSQL database and a user to access that database with. Now we can install Django and set it up to use our new database.

Exit out of the database with \q

When exiting, I got this error, but I’m not certain it’s critical:

    could not save history to file “/var/lib/postgresql/.psql_history”: No such file or directory

Before proceeding to step eight, exit out of the postgres user (which you switched to at the start of step seven) with exit.

Step Eight: Create a Django Project

In order to go any further we need a Django project to test with. This will allow us to see if what we are doing is working or not. Change directories into the directory of your virtualenv (in my case /opt/myenv) like so:

cd /opt/myenv

Now make sure your virtualenv is active. If you’re unsure then just run the following command to ensure you’re activated:

source /opt/myenv/bin/activate

With your virtualenv now active, run the following command to start a new Django project: startproject myproject

Again, must be preceded with sudo, and the full path to must be provided:

    sudo /opt/myenv/bin/ startproject myproject

(I can’t get it to work with just (without the path), even if /opt/myenv/bin is in $PATH.)

You should see a new directory called “myproject” inside your virtualenv directory. This is where our new Django project files live.

In order for Django to be able to talk to our database we need to install a backend for PostgreSQL. Make sure your virtualenv is active and run the following command in order to do this:

pip install psycopg2

Another rough patch.

This should be the correct command:

    sudo /opt/myenv/bin/pip install psycopg2

But it fails with

    ./psycopg/psycopg.h:30:20: fatal error: Python.h: No such file or directory

If I had to guess, I would say this is because I started virtualenv with the -p option.

The command that ultimately worked:

    sudo apt-get install python-psycopg2

Change directories into the new “myproject” directory and then into it’s subdirectory which is also called “myproject” like this:

cd /opt/myenv/myproject/myproject

Edit the file with your editor of choice:


The correct command is, no surprise,

    sudo nano

If you are more comfortable editing files on Windows, you can instead use SFTP Net Drive (as mentioned at the top of this document) to mount this remote folder as a drive. On my computer, it’s drive Q:\. Using this, I edit the files on Windows and, every time a file is saved, it is silently FTP’d back to the server. I understand it “works beautifully, or fails miserably”. So far, I’ve only seen the beautiful.

There is one change necessary to make this possible.

All of these files are owned by root. But as per the pre-requisite instructions, you log in as root exactly once, in order to create a secondary user account that has the same permissions as root, with the exception that sudo must be prefixed before every command.

SFTP Net Drive has no way to pass through the sudo prefix. It is therefore necessary to either change the files’

– Permisions (chmod) such that the secondary user has write permissions without sudo, or
– Ownership (chown) to the secondary user.

The first option opens up the files to more users than just the secondary user, so option two is the obivous choice:

    chown -R secondaryuser /opt/myenv/myproject/

Find the database settings and edit them to look like this:

    'default': {
        # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
        # Or path to database file if using sqlite3.
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'mydb',

        # The following settings are not used with sqlite3:

        'USER': 'myuser',
        'PASSWORD': 'password',
        'HOST': 'localhost',  # Empty for localhost through domain sockets or
                              #'' for localhost through TCP.
        'PORT': '',           # Set to empty string for default.

The values of NAME, USER, and PASSWORD must be as set in previous steps.

Save and exit the file. Now move up one directory so your in your main Django project directory (/opt/myenv/myproject).

cd /opt/myenv/myproject

Activate your virtualenv if you haven’t already with the following command:

source /opt/myenv/bin/activate

With your virtualenv active, run the following command so that Django can add it’s initial configuration and other tables to your database:

python syncdb

Another mess, which I’ve documented in this post:

The end result is this: Before running syncdb, execute the following:

sudo apt-get build-dep python-psycopg2
sudo /opt/myenv/bin/pip install psycopg2

You should see some output describing what tables were installed, followed by a prompt asking if you want to create a superuser. This is optional and depends on if you will be using Django’s auth system or the Django admin.

Step Nine: Configure Gunicorn

Gunicorn configuration is very specific to your applications needs. I will briefly go over running Gunicorn here with some different settings.

First lets just go over running Gunicorn with default settings. Here is the command to just run default Gunicorn:

gunicorn_django --bind

In Django 1.7c2, this command is both deprecated and fails. Same with the two below. In addition, because of how the browser request makes its way to gunicorn and ultimately Django, the url and port is wrong.

The correct command/url/port is

    gunicorn -b django_test.wsgi:application

In all my 47 minutes of experience with nginx and gunicorn, this is my understanding: The browser url, is first handled by nginx. nginx then passes the request through to the localhost (, at port 8001. After running the above command, that is being watched by gunicorn. Gunicorn then translates the request as necessary for Python and Django.

Since nginx is configured with no port, the browser can actually call just

Be sure to replace “” with your domain, or the IP address of your VPS if you prefer. Now go to your web browser and visit and see what you get. You should get the Django welcome screen.

If you look closely at the output from the above command however, you will notice only one Gunicorn worker booted. What if you are launching a large-scale application on a large VPS? Have no fear! All we need to do is modify the command a bit like so:

gunicorn_django --workers=3 --bind

Now you will notice that 3 workers were booted instead of just 1 worker. You can change this number to whatever suits your needs.

Since we ran the command to start Gunicorn as root, Gunicorn is now running as root. What if you don’t want that? Again, we can alter the command above slightly to accomodate:

gunicorn_django --workers=3 --user=nobody --bind

If you want to set more options for Gunicorn, then it is best to set up a config file that you can call when running Gunicorn. This will result in a much shorter and easier to read/configure Gunicorn command.

You can place the configuration file for gunicorn anywhere you would like. For simplicity, we will place it in our virtualenv directory. Navigate to the directory of your virtualenv like so:

cd /opt/myenv

Now open your config file with your preferred editor (nano is used in the example below):

sudo nano

Add the following contents to the file:

command = '/opt/myenv/bin/gunicorn'
pythonpath = '/opt/myenv/myproject'
bind = ''
workers = 3
user = nobody

As stated in the comments below the original document (in October 2013), the nobody value needs to be surrounded by single (or double) quotes:


I have also added the following (and created the necessary directory):

    error_logfile = “/opt/myenv/myproject/logs/gunicorn_errors.log”
    log_level = “debug”

Save and exit the file. What these options do is to set the path to the gunicorn binary, add your project directory to your Python path, set the domain and port to bind Gunicorn to, set the number of gunicorn workers and set the user Gunicorn will run as.

In order to run the server, this time we need a bit longer command. Enter the following command into your prompt:

/opt/myenv/bin/gunicorn -c /opt/myenv/ myproject.wsgi

Must be prefixed with sudo. It can be executed in or out of the virtualenv.

You will notice that in the above command we pass the “-c” flag. This tells gunicorn that we have a config file we want to use, which we pass in just after the “-c” flag. Lastly, we pass in a Python dotted notation reference to our WSGI file so that Gunicorn knows where our WSGI file is.

Running Gunicorn this way requires that you either run Gunicorn in its own screen session (if you’re familiar with using screen), or that you background the process by hitting “ctrl + z” and then typing “bg” and “enter” all right after running the Gunicorn command. This will background the process so it continues running even after your current session is closed. This also poses the problem of needing to manually start or restart Gunicorn should your VPS gets rebooted or were it to crash for some reason. To solve this problem, most people use supervisord to manage Gunicorn and start/restart it as needed. Installing and configuring supervisord has been covered in another article which can be found here.

Lastly, this is by no means an exhaustive list of configuration options for Gunicorn. Please read the Gunicorn documentation found at for more on this topic.

Step Ten: Configure NGINX

Before we get too carried away, let’s first start NGINX like so:

sudo service nginx start

Since we are only setting NGINX to handle static files we need to first decide where our static files will be stored. Open your file for your Django project and edit the STATIC_ROOT line to look like this:

    STATIC_ROOT = "/opt/myenv/static/"

This path can be wherever you would like. But for cleanliness, I typically put it just outside my Django project folder, but inside my virtualenv directory.

Now that you’ve set up where your static files will be located, let’s configure NGINX to handle those files. Open up a new NGINX config file with the following command (you may replace “nano” with your editor of choice):

sudo nano /etc/nginx/sites-available/myproject

You can name the file whatever you would like, but the standard is typically to name it something related to the site that you are configuring. Now add the following to the file:

    server {

        access_log off;

        location /static/ {
            alias /opt/myenv/static/;

        location / {
            proxy_set_header X-Forwarded-Host $server_name;
            proxy_set_header X-Real-IP $remote_addr;
            add_header P3P 'CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"';

In the location block, "alias" can be changed to "root".
Both work.

If the url in your browser is, with no url directory, this is received by nginx, which forwards it to the localhost, The gunicorn server, as configured in step 9, is listening to that address. Since gunicorn was started with

    /opt/myenv/bin/gunicorn -c /opt/myenv/ myproject.wsgi

It knows to pass this request onto the Django project named "myproject". Django generates the page, passes it back: through Gunicorn, nginx, and then to the browser.

If, however, the url ends with /static, it instead is totally handled by nginx, which gets the appropriate file from the hard-coded static directory, as configured into the location block.

(If you’re not seeing the page you’re expecting, flush the browser’s cache!)

Save and exit the file. The above configuration has set NGINX to serve anything requested at from the static directory we set for our Django project. Anything requested at will proxy to localhost on port 8001, which is where we told Gunicorn to run. The other lines ensure that the hostname and IP address of the request get passed on to Gunicorn. Without this, the IP address of every request becomes and the hostname is just your VPS hostname.

Now we need to set up a symbolic link in the /etc/nginx/sites-enabled directory that points to this configuration file. That is how NGINX knows this site is active. Change directories to /etc/nginx/sites-enabled like this:

cd /etc/nginx/sites-enabled

Once there, run this command:

sudo ln -s ../sites-available/myproject

This will create the symbolic link we need so that NGINX knows to honor our new configuration file for our site.

Additionally, remove the default nginx server block:

sudo rm default

We need to restart NGINX though so that it knows to look for our changes. To do this run the following:

sudo service nginx restart

And that’s it! You now have Django installed and working with PostgreSQL and your app is web accessible with NGINX serving static content and Gunicorn serving as your app server. If you have any questions or further advice, be sure to leave it in the comments section.

Easy peasy lemon squeezy!

(Thanks to a whole lot of people on and #ubuntu for the hand-holding and head-patting)


19 thoughts on “Installing Django on DigitalOcean — Their step-by-step–ahem!–instructions, annotated

  1. jviyer

    great stuff. appreciate your taking the time to do this. I have not finished the steps in the original article when I stumbled upon your article in the comments. I am going to try with this.

    1. aliteralmind Post author

      You’re welcome. This was a really difficult process that consumed about five days of my life, but once I got through it, I haven’t had any problems.

      Please let me know if it works for you!

      1. jviyer

        During django installation, I got the following message:

        (v2)jv@pugo:~$ sudo /home/jv/v2/bin/pip install django
        Downloading/unpacking django
        Downloading Django-1.7.1.tar.gz (7.5Mb): 7.5Mb downloaded
        Running egg_info for package django

        warning: no previously-included files matching ‘__pycache__’ found under directory ‘*’
        warning: no previously-included files matching ‘*.py[co]’ found under directory ‘*’
        Installing collected packages: django
        Running install for django

        warning: no previously-included files matching ‘__pycache__’ found under directory ‘*’
        warning: no previously-included files matching ‘*.py[co]’ found under directory ‘*’
        changing mode of build/scripts-3.2/ from 644 to 755
        changing mode of /home/jv/v2/bin/ to 755
        Installing django-admin script to /home/jv/v2/bin
        Successfully installed django
        Cleaning up…
        and during gunicorn install:
        sudo /home/jv/v2/bin/pip install gunicorn
        Downloading/unpacking gunicorn
        Downloading gunicorn-19.1.1.tar.gz (385Kb): 385Kb downloaded
        Running egg_info for package gunicorn

        warning: no previously-included files matching ‘*.pyc’ found under directory ‘docs’
        warning: no previously-included files matching ‘*.pyo’ found under directory ‘docs’
        warning: no previously-included files matching ‘*.pyc’ found under directory ‘tests’
        warning: no previously-included files matching ‘*.pyo’ found under directory ‘tests’
        warning: no previously-included files matching ‘*.pyc’ found under directory ‘examples’
        warning: no previously-included files matching ‘*.pyo’ found under directory ‘examples’
        Installing collected packages: gunicorn
        Running install for gunicorn

        warning: no previously-included files matching ‘*.pyc’ found under directory ‘docs’
        warning: no previously-included files matching ‘*.pyo’ found under directory ‘docs’
        warning: no previously-included files matching ‘*.pyc’ found under directory ‘tests’
        warning: no previously-included files matching ‘*.pyo’ found under directory ‘tests’
        warning: no previously-included files matching ‘*.pyc’ found under directory ‘examples’
        warning: no previously-included files matching ‘*.pyo’ found under directory ‘examples’
        Installing gunicorn_paster script to /home/jv/v2/bin
        Installing gunicorn script to /home/jv/v2/bin
        Installing gunicorn_django script to /home/jv/v2/bin
        File “/home/jv/v2/lib/python3.2/site-packages/gunicorn/workers/”, line 64
        yield from self.wsgi.close()
        SyntaxError: invalid syntax

        Successfully installed gunicorn
        Cleaning up…
        by the way I had created the virtual env using your guide prior to the above steps

        sudo virtualenv -p /usr/bin/python3 /home/jv/v2
        [sudo] password for jv:
        Running virtualenv with interpreter /usr/bin/python3
        New python executable in /home/jv/v2/bin/python3
        Also creating executable in /home/jv/v2/bin/python
        Installing distribute……………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………done.
        Installing pip……………done.

  2. jviyer

    upto this part succeded;
    The end result is this: Before running syncdb, execute the following:

    sudo apt-get build-dep python-psycopg2
    sudo /opt/myenv/bin/pip install psycopg2
    after that when I do
    Save and exit the file. Now move up one directory so your in your main Django project directory (/opt/myenv/myproject).

    cd /opt/myenv/myproject

    Activate your virtualenv if you haven’t already with the following command:

    source /opt/myenv/bin/activate

    With your virtualenv active, run the following command so that Django can add it’s initial configuration and other tables to your database:

    python syncdb
    ^^^^^^^^^^^^^^^^^^^^^^^^^^fails here with :
    (v2)jv@pugo:~/v2/myproject$ python syncdb
    Traceback (most recent call last):
    File “”, line 10, in
    File “/home/jv/v2/lib/python3.2/site-packages/django/core/management/”, line 385, in execute_from_command_line
    File “/home/jv/v2/lib/python3.2/site-packages/django/core/management/”, line 345, in execute
    File “/home/jv/v2/lib/python3.2/site-packages/django/conf/”, line 46, in __getattr__
    File “/home/jv/v2/lib/python3.2/site-packages/django/conf/”, line 42, in _setup
    self._wrapped = Settings(settings_module)
    File “/home/jv/v2/lib/python3.2/site-packages/django/conf/”, line 94, in __init__
    mod = importlib.import_module(self.SETTINGS_MODULE)
    File “/usr/lib/python3.2/importlib/”, line 124, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
    File “/usr/lib/python3.2/importlib/”, line 807, in _gcd_import
    File “/usr/lib/python3.2/importlib/”, line 821, in _gcd_import
    File “/usr/lib/python3.2/importlib/”, line 436, in load_module
    return self._load_module(fullname)
    File “/usr/lib/python3.2/importlib/”, line 141, in decorated
    return fxn(self, module, *args, **kwargs)
    File “/usr/lib/python3.2/importlib/”, line 330, in _load_module
    code_object = self.get_code(name)
    File “/usr/lib/python3.2/importlib/”, line 423, in get_code
    self.set_data(bytecode_path, data)
    File “/usr/lib/python3.2/importlib/”, line 481, in set_data
    OSError: [Errno 13] Permission denied: ‘/home/jv/v2/myproject/myproject/__pycache__’

    ========================= Tried again with sudo and got this:=====================
    (v2)jv@pugo:~/v2/myproject$ sudo python syncdb
    [sudo] password for jv:
    Traceback (most recent call last):
    File “”, line 8, in
    from import execute_from_command_line
    ImportError: No module named
    So I am stuck now at this step.

  3. Prateem Shrestha

    Hey! Thank goodness for this post! I’m extremely grateful! Everything worked out fine for me. I think the one thing I really want to know is how I could go about setting up environment variables for use in my python app. Thanks for any help!

      1. Prateem Shrestha

        Thanks for the reply! One more question– do you know if it’s about this straightforward if I want to do this with Python3? Do you think I’ll have any dependency issues?

  4. Pingback: LinuxLuigi

  5. Pingback: Django 1.7 + Python 3 + NGINX + PostgreSQL + Gunicorn | LinuxLuigi

  6. Pingback: The most important applications I use on Windows, for programming and otherwise | aliteralmind — Computer Programming Blog

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s