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
Vatsal Kanakiya
Great guide! Exactly what I’ve been looking for. Thanks!
Anand
uwsgi folder is not found in /etc.
P
This is excellent! Thank you very much.
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.