A quick guide to getting a Django – Uwsgi – Nginx server up on Ubuntu 16.04 (AWS EC2)

I’ve wasted quite some trying to get the server up for a new project everytime; I end up reading from multiple tutorials picking up what works. I thought I’d write a simple guide and share it.

Ubuntu Stuff

ssh ubuntu@your-aws-instance-public-ip -i key.pem
cd ~

After logging in, let’s get all the required stuff installed.

sudo apt-get update
sudo apt-get install python-dev
sudo apt-get install python-pip

It is a good idea to work with a virtual environment. If you don’t know what it is, you can think of it as another shell inside the shell where we can install packages that won’t get installed system-wide. You can jump in and out of this virtualenv anytime.

mkdir our-project
cd our-project
pip install virtualenv

Let’s start the virtualenv and enter it.

virtualenv venv
source venv/bin/activate

Django Stuff

Once we’re here, we need need to get the django files in place. For sake of simplicity, I’ll make a new Django project. If you have already setup one, skim through this step and you’d only have to install your requirements.

pip install django
django-admin.py startproject hello

If you had an existing project, you would be installing it from a requirements.txt file like this.

pip install -r requirements.txt

To test if this installation worked, we need to do a runserver on port 8000 (make that this port is open to public: you can do this by adding an inbound rule for port 8000 on the security group for your EC2 Instance) and open it in the browser.

cd hello
python manage.py runserver 0.0.0.0:8000

Now if you fire up, http://your-aws-instance-public-ip:8000, your Django project should load up.

Uwsgi Stuff

Now we’ve got a Django project that runs inside the virtualenv. We need Uwsgi to serve django to the web instead of the lightweight development server, that we just ran using manage.py runserver command. If the thought of running the runserver command on a screen passes your mind, drop it. The dev server with django is terribly lightweight and highly insecure, and absolutely cannot scale.

deactivate

We just came out of the virtualenv (you can notice that the prompt to the left on your command screen changes) and we’ll install uwsgi now system-wide because, we’ll be running the server from the root user.

sudo pip install uwsgi

Let’s run the server using uwsgi with the same config. This command does the same thing a manage.py runserver would do.

uwsgi --http :8000 --home PATH/TO/THE/VIRTUALENV --chdir /PATH/TO/THE/DJANGO/PROJECT/FOLDER/CONTAINING/MANAGE.PY/FILE -w YOUR-PROJECT-NAME.wsgi

So in our case, the command would be:

uwsgi --http :8000 --home /home/ubuntu/our-project/venv  --chdir /home/ubuntu/our-project/hello -w hello.wsgi

Now if you fire up http://your-aws-instance-public-ip:8000, your Django website should show up in the browser.

We need to run this in the ‘background’ (ah, well you could probably run it and screen it but there are better ways) so we’re going to achieve that next.

The way we will do it is by using Ubuntu’s systemd, which gets pid 1 (the first process to run after booting up) and this is fully supported for versions 15.04 and beyond. We will let it initliase our uwsgi process.

To store our config options, we need to create an ‘ini’ file which will contain all the uwsgi config details (like which virtualenv to use, where is the home folder, etc arguments we passed while executing the command to run the server).

sudo mkdir /etc/uwsgi/sites
sudo vim /etc/uwsgi/sites/hello.ini

We’ll load the file with the config details.

[uwsgi]

chdir = /home/ubuntu/our-project/hello #same as above
home = /home/ubuntu/our-project/venv #same as above
module = hello.wsgi:application #same as above

master = true
processes = 5 #more processes, more computing power

socket = /run/uwsgi/hello.sock #SOCKET_LOC
chown-socket = ubuntu:www-data #user and user's group
chmod-socket = 660 
vacuum = true #delete the socket after process ends
harakiri = 30 #respawn the process if it takes more than 30 secs
Press ESC
Type :wq
Enter

You’ll notice that we did not mention any port like 8000 as we did before. We’re going to be routing this via a socket file instead of a port as this is more optimal. There is no difference, only that whatever requests were routed to port 8000, would now be required to go via the socket file.

You can test if this works by running the following command.

uwsgi --ini /etc/uwsgi/sites/hello.ini

If this works fine, you’ll see a couple of lines and status that 5 or some number of processes have been spawned.

Now, we need to let systemd (Ubuntu’s service manager) take care of this. So we will create a special service that will make sure our server is running.

Overall, this would be:

Ubuntu's SystemD --call-> Service we create --execute-> Uwsgi ini --run-> Our Django Project
sudo vim /etc/systemd/system/uwsgi.service

Paste the following stuff into it

[Unit]
Description=uWSGI Emperor service

[Service]
ExecStartPre=/bin/bash -c 'mkdir -p /run/uwsgi; chown ubuntu:www-data /run/uwsgi' #make the folder where we'll store our socket file and have the right user/group permissions
ExecStart=/usr/local/bin/uwsgi --emperor /etc/uwsgi/sites #this the command to execute on start
Restart=always #make sure the server is running
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all

[Install]
WantedBy=multi-user.target
ESC
:wq

This gist of what we pasted is simple, the service will execute this line everytime it comes up and make sure it is up. You could even fire it up on the terminal to see that it runs the server for you. The only special thing here is the –emperor. The emperor mode checks a particular folder (in our case, sites) for .ini files and fires each of them (our hello.ini is sitting there) making it useful if we have multiple websites.

/usr/local/bin/uwsgi --emperor /etc/uwsgi/sites

Now let’s tell the systemd to run our service.

sudo systemctl restart uwsgi

If you want to make sure, you could do a htop and see the number of processes (search for uwsgi) you wanted to spawn + 1 (for master) are running.

So uwsgi is running. But we need to get it to appear when an HTTP request comes in. For that, we’re going to use Nginx.

Nginx Stuff

Nginx is a lightweight server and we’ll use it as a reverse proxy. What we’re trying to achieve is this:

WWW.DOMAIN.COM <--> NGINX <--Talk to the hello.sock --> UWSGI <--hello.wsgi--> DJANGO

You would wonder, why introduce Nginx in between and why not have uwsgi handle requests directly. You could let Uwsgi run directly on port 80 but Nginx has many benefits (full discussion here) which makes it desirable.

Let’s install Nginx.

sudo apt-get install nginx
sudo service nginx start

If you hit the http://your-public-ec2-address, you will see a Nginx welcome page because Nginx is listening to port 80 (the default http port) according to its default configuration.

Nginx has two directories, sites-available and sites-enabled. Nginx looks for all conf files in the sites-enabled folder and configures the server according to it. So let’s create a conf file to connect the browser request to the uwsgi server we are running.

sudo vim /etc/nginx/sites-available/hello

Paste the following into it.

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com your-ec2-public-ip-address(if you don't have a domain);

    location = /favicon.ico { access_log off; log_not_found off; }
    client_max_body_size 20M;

    location / {
        include         uwsgi_params;
        uwsgi_pass      unix:/run/uwsgi/hello.sock; #SAME as #SOCKET_LOC in the hello.ini
    }
}

You can let server_name be a subdomain or multiple domains where you want to serve the website. The uwsgi_pass must point to the socket file we had our uwsgi ini file create. You can configure the Nginx conf far more and add a lot of things, I’ve only added some basic stuff.

We need to add this to sites-enabled directory, in order to be picked up by Nginx. We can create a symlink to the file.

sudo ln -s /etc/nginx/sites-available/hello /etc/nginx/sites-enabled/

That’s all. Now restart nginx and you’re all set.

sudo service nginx restart

Now if you have configured your domain, and added the same domain to nginx conf, your website should load on the domain or if you added the ip, then on http://your-ec2-public-ip. If you’re confused about configuring the domain to the address, read on.

Domain configuration stuff

This is fairly straightforward. You need to add a simple record to your DNS records. If you’ve bought your domain with popular domain name sellers like GoDaddy or Name.com, you’d have some panel to manage DNS settings.

Get to it and add an ‘A Record’ with ‘@’ or a blank host (and another record with ‘www’) and point it to ‘your-ec2-public-ip’ and everything should work then! If you want to run a subdomain, then instead of ‘@’, enter the subdomain name. This is telling the domain that incase anybody requests something at that url, just forward the request to the IP where we have our NGINX server listening and waiting to respond.

Django Config Stuff

You could run into 400 or 502 errors when trying to serve if you’re running with DEBUG = False and have not set ALLOWED_HOSTS in settings.

You need to have allowed hosts configured to allow those domains. You could allow everything,

ALLOWED_HOST = ['*'] #INCASE you want allow every host but this may turn out to be unsafe

Or allow the domains we configured in the nginx conf,

ALLOWED_HOST = ['yourdomain.com','www.yourdomain.com','your-ec2-public-ip']

And finally, we’re live with our django website.

Reference
1. DigitalOcean

4 Comments

  1. Michael

    Thanks for this guide, worked like a charm for me!
    Just a little remark: Is this line correct? ExecStart=/usr/local/bin/uwsgi
    Shouldn’t the virtual env path be used instead of /usr/local?

    I had to change it that way.

Leave a Reply

Your email address will not be published. Required fields are marked *