An NGINX reverse proxy for an uWSGI Django app, forcing HTTPS with free certs from Let's Encrypt
Published on by nick
When I first started created Django web sites and applications, I used Apache with mod_wsgi
. This was mostly due to the fact that I knew Apache already from running PHP/MySQL applications, running servers like WAMP and LAMP. But as time went on, this became a problem. It was always a pain setting up certs with Apache, the rewrite rules never worked as expected, and its configuration was just overly complicated.
When I discovered nginx and set up a test application, I was blown away by its ease of use, and great power. The configuration was simple and intuitive. Then I thought, "how hard would it be to install and configure an SSL certificate?"
Discovering uWSGI was also a saving grace and mostly a pleasure. Although a bit less smooth and awesome than using nginx, for its performance, it was well worth the trouble. And the two running on concert has several advantages over other Django server/application configurations.
I set out to do this with one of my young applications. About the same time, I heard of Let's Encrypt: a free, automated, and open solution to TLS/SSL certificates. After reading through the documentation, I felt comfortable installing one of these free certificates on a Digital Ocean droplet.
The article is broken down into four sections: 1) set up server, and install packages/tools; 2) clone application repository, install requirements, and confirm configuration; 3) install and configure nginx and uwsgi; 4) install, configure Let's Encrypt, and generate TLS certificates.
To learn how to set up DO droplets, see their documentation. They make it very easy to create a usable virtual machine in minutes. During the set up process, you can create SSH keys to access your droplet via SSH. Once you can SSH into your VM, you need to provision your machine. Obviously, if you are doing this in a staging/production setting, you will want to fully automate this process.
I like to install vim
, git
, python
, python3
, pip
, virtualenvwrapper
, dev tools, etc. Some of these will be installed on your system already. I'll let you worry about this for your servers.
Once this is done, you should clone your application repository. Also, this is a good opportunity to make a sensible directory structure for your application. Something like /home/user/sites
is a good choice. When you are done, assuming you have a requirements file somewhere, you should run:
pip install -r /path/to/requirements.txt
After installing requirements, you need to confirm everything is working. You should be able to sync your database or run some tests to ensure your Django application is working, and everything is in place.
The next step involves making sure nginx is running, configured, and build a custom installation of uwsgi. If nginx is not installed, install it. For uwsgi, however, for best results, I recommend installing a custom build. This is because it requires a plugin for Python 3.3 and Python 3.4; for flexibility and simplicity, you can generate a stripped down version of uwsgi and then run it with multiple version of Python. This is handy if you have multiple development environments, all running different versions of Python in their own virtual environments.
To build uwsgi, first download and unpack the source. I use a location like /usr/local/lib
. Then cd
into the uwsgi directory and run this command:
make PROFILE=nolang
You can alternatively install with pip
:
pip install https://projects.unbit.it/downloads/uwsgi-lts.tar.gz
This will give you a working uwsgi binary. To build the Python plugins, you can run this command (for Python 3.3, for example):
PYTHON=python3.3 ./uwsgi --build-plugin "plugins/python plugin33"
Next you want to create the directory where you will store all your plugins for uwsgi:
mkdir /usr/lib/uwsgi/plugins
Copy or move the plugin over:
mv python33_plugin.so /usr/lib/uwsgi/plugins/
After doing all this, confirm permissions and ownership. Must be inline with the rest of the plugins (root:root 644
). At this point, you can start using this plugin in uwsgi. If you are running an INI file to configure and run uwsgi, add this to that file with something like this:
plugins-dir = /usr/lib/uwsgi/plugins plugin = python34
Here is a complete uwsgi.ini
example file:
[uwsgi] project = project_name user = django base = /home/%(user) virtualenv = %(base)/virtenvs/%(project) module = wsgi.local:application master = true processes = 5 chdir = %(base)/sites/%(project)/%(project) socket = /tmp/%(project).sock chmod-socket = 664 vacuum = true pidfile = /tmp/%(project).pid logto = /var/log/uwsgi/%n.log plugins-dir = /usr/lib/uwsgi/plugins plugin = python34
Now on to the cool stuff... Let's Encrypt is a relatively new system delivering free, automated, and open solution to TLS/SSL certificates. From the Linux Foundation Collaborative Projects, Let's Encrypt offers free TLS certificates for your application and software to help you manage and deploy them. Getting started is relatively easy, and requires just a few steps. First navigate to where you want to store the Let's Encrypt application, and then clone the repository:
git clone https://github.com/letsencrypt/letsencrypt
Next, you need to actually generate the certificates. Assuming you are deploying TLS for your staging site on the domain example.com, you would issue a command like this (using certbot):
# ./letsencrypt-auto certonly --standalone -d staging.example.com ./certbot-auto certonly --standalone -d staging.example.com
Let's Encrypt will launch an application that takes you through some steps, and if this operation succeeds, you will get a message indicating where the fresh certificates are located. Grab these and prepare to configure your web server. The good new here is that nginx is pretty easy to configure. I read a preview of a new book recently, and I tweeted about. I highly recommend this book, either now in this preview state, or when it comes out later this year. It really breaks it down, and explains things in a simple way.
Here is a summary of my nginx configuration to get this running. Please note that I'm no nginx configuration expert; I've just found this works for my purposes. I would recommend doing your own research to really set up your server to be optimal with your circumstances. Note that the https2
requires nginx 1.9.5 and above and you must build nginx with the --with-http_v2_module
configuration parameter.
# nginx configuration template upstream example { server unix://tmp/example.sock; } server { listen 80; server_name example.com www.example.com; return 301 https://www.example.com$request_uri; } server { listen 443 ssl http2; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; location /static { alias /path/to/sites/example/static; } location / { include uwsgi_params; uwsgi_pass example; } }
To be fair, often these sets of instructions don't work out of the box. I'd say this is a more advanced tutorial, and having some experience configuring web servers will serve you (no pun intended) very well. If you have no experience, it might take you several hours to a day to sort through everything and figure it out. Troubleshooting is often part of the game here.
What I would recommend, however, is once you get something that works, automate it! After reading Harry's book, Test Driven Development with Python, I really gained an appreciation of automation and testing. He takes you through automating this kind of setup with Fabric, a great Python tool, and seeing this kind of deployment work automatically is quite beautiful.
Drop me a line...
[email protected]
Follow me on Twitter...
@nicorellius
Share on
Comments
Comments powered by Disqus