Recently, I learned about the NGINX Unit and decided to try it on my DjangoTricks website. Unit is a web server developed by people from NGINX, with pluggable support for Python (WSGI and ASGI), Ruby, Node.js, PHP, and a few other languages. I wanted to see whether it's really easy to set it up, have it locally on my Mac and the remote Ubuntu server, and try out the ASGI features of Django, allowing real-time communication. Also, I wanted to see whether Django is faster with Unit than with NGINX and Gunicorn. This article is about my findings.
Unit service uses HTTP requests to read and update its configuration. The configuration is a single JSON file that you can upload to the Unit service via a command line from the same computer or modify its values by keys in the JSON structure.
Normally, the docs suggest using the curl
command to update the configuration. However, as I am using Ansible to deploy my Django websites, I wanted to create a script I could later copy to other projects. I used Google Gemini to convert bash commands from the documentation to Ansible directives and corrected its mistakes.
The trickiest part for me was to figure out how to use Let's Encrypt certificates in the simplest way possible. The docs are extensive and comprehensible, but sometimes, they dig into technical details that are unnecessary for a common Django developer.
Also, it's worth mentioning that the Unit plugin version must match your Python version in the virtual environment. It was unexpected for me when Brew installed Python 3.12 with unit-python3
and then required my project to use Python 3.12 instead of Python 3.10 (which I used for the DjangoTricks website). So I had to recreate my virtual environment and probably will have problems later with pip-compile-multi
when I prepare packages for the production server, still running Python 3.10.
Below are the instructions I used to set up the NGINX Unit with my existing DjangoTricks website on Ubuntu 22.04. For simplicity, I am writing plain Terminal commands instead of analogous Ansible directives.
Follow the installation instructions from documentation to install unit
, unit-dev
, unit-python3.10
, and whatever other plugins you want. Make sure the service is running.
Create a temporary JSON configuration file /var/webapps/djangotricks/unit-config/unit-config-pre.json
, which will allow Let's Encrypt certbot to access the .well-known
directory for domain confirmation:
{
"listeners": {
"*:80": {
"pass": "routes/acme"
}
},
"routes": {
"acme": [
{
"match": {
"uri": "/.well-known/acme-challenge/*"
},
"action": {
"share": "/var/www/letsencrypt/$uri"
}
}
]
}
}
Install it to Unit:
$ curl -X PUT --data-binary @/var/webapps/djangotricks/unit-config/unit-config-pre.json \
--unix-socket /var/run/control.unit.sock http://localhost/config
If you make any mistakes in the configuration, it will be rejected with an error message and not executed.
Create Let's Encrypt certificates:
$ certbot certonly -n --webroot -w /var/www/letsencrypt/ -m hello@djangotricks.com \
--agree-tos --no-verify-ssl -d djangotricks.com -d www.djangotricks.com
Create a bundle that is required by the NGINX Unit:
cat /etc/letsencrypt/live/djangotricks.com/fullchain.pem \
/etc/letsencrypt/live/djangotricks.com/privkey.pem > \
/var/webapps/djangotricks/unit-config/bundle1.pem
Install certificate to NGINX Unit as certbot1
:
curl -X PUT --data-binary @/var/webapps/djangotricks/unit-config/bundle1.pem \
--unix-socket /var/run/control.unit.sock http://localhost/certificates/certbot1
Create a JSON configuration file /var/webapps/djangotricks/unit-config/unit-config.json
which will use your SSL certificate and will serve your Django project:
{
"listeners": {
"*:80": {
"pass": "routes/main"
},
"*:443": {
"pass": "routes/main",
"tls": {
"certificate": "certbot1"
}
}
},
"routes": {
"main": [
{
"match": {
"host": [
"djangotricks.com",
"www.djangotricks.com"
],
"uri": "/.well-known/acme-challenge/*"
},
"action": {
"share": "/var/www/letsencrypt/$uri"
}
},
{
"match": {
"host": [
"djangotricks.com",
"www.djangotricks.com"
],
},
"action": {
"pass": "applications/django"
}
},
{
"action": {
"return": 444
}
}
]
},
"applications": {
"django": {
"type": "python",
"path": "/var/webapps/djangotricks/project/djangotricks",
"home": "/var/webapps/djangotricks/venv/",
"module": "djangotricks.wsgi",
"environment": {
"DJANGO_SETTINGS_MODULE": "djangotricks.settings.production"
},
"user": "djangotricks",
"group": "users"
}
}
}
In this configuration, HTTP requests can only be used for certification validation, and HTTPS requests point to the Django project if the domain used is correct. In other cases, the status "444 - No Response" is returned. (It's for preventing access for hackers who point their domains to your IP address).
In the NGINX Unit, switching between WSGI and ASGI is literally a matter of changing one letter from "w" to "a" in the line about the Django application module, from:
"module": "djangotricks.wsgi",
to:
"module": "djangotricks.asgi",
I could have easily served the static files in this configuration here, too, but my STATIC_URL
contains a dynamic part to force retrieval of new files from the server instead of the browser cache. So, I used WhiteNoise to serve the static files.
For redirection from djangotricks.com
to www.djangotricks.com
, I also chose to use PREPEND_WWW = True
setting instead of Unit directives.
And here, finally, installing it to Unit (it will overwrite the previous configuration):
$ curl -X PUT --data-binary @/var/webapps/djangotricks/unit-config/unit-config.json \
--unix-socket /var/run/control.unit.sock http://localhost/config
DjangoTricks is a pretty small website; therefore, I couldn't do extensive benchmarks, but I checked two cases: how a filtered list view performs with NGINX and Gunicorn vs. NGINX Unit, and how you can replace NGINX, Gunicorn, and Huey background tasks with ASGI requests using NGINX Unit.
First of all, the https://www.djangotricks.com/tricks/?categories=development&technologies=django-4-2 returned the HTML result on average in 139 ms on NGINX with Gunicorn, whereas it was on average 140 ms with NGINX Unit using WSGI and 149 ms with NGINX Unit using ASGI. So, the NGINX Unit with WSGI is 0.72% slower than NGINX with Gunicorn, and the NGINX Unit with ASGI is 7.19% slower than NGINX with Gunicorn.
However, when I checked https://www.djangotricks.com/detect-django-version/ how it performs with background tasks and continuous Ajax requests until the result is retrieved vs. asynchronous checking using ASGI, I went on average from 6.62 s to 0.75 s. Of course, it depends on the timeout of the continuous Ajax request, but generally, a real-time ASGI setup can improve the user experience significantly.
Although NGINX Unit with Python is slightly (unnoticeably) slower than NGINX with Gunicorn, it allows Django developers to use asynchronous requests and implement real-time user experience. Also, you could probably have a Django website and Matomo analytics or WordPress blog on the same server. The NGINX Unit configuration is relatively easy to understand, and you can script the process for reusability.
Cover Image by Volker Meyer.
This year DjangoCon Europe happened in Copenhagen, Denmark, at a very creative and special place AFUK - Academy for Untamed Creativity. Surrounded by artistic souls, we learned more about web technologies, got to know each other at ecologic reusable disposable cups of coffee, enjoyed delicious authentic food, and socialized with Django-branded beverages. As always, there was also a party with people drinking IPA (not to be confused with API). And at the weekend Django developers were solving bugs for Django and related open-source software at the coding sprints.
Here I would like to present you with the top 5 talks that I liked most of all.
Adam Johnson (@AdamChainz) was talking about special response headers that tell browsers to treat data of the website more securely and which Django settings are responsible for those headers.
Mikey Ariel (@ThatDocsLady) was talking about the necessity of documentation and what to write there.
Sigurd Ljødal (@sigurdlj) was talking about advanced Django ORM use cases.
Markus Holtermann (@m_holtermann) was talking about logging structured data to log files instead of the traditional plain text messages.
Joachim Jablon (@Ewjoachim) and Stéphane Angel (@twidi) were talking about the best practices developing large-scale Django projects.
You can see all talk descriptions, video records and some slides at the official conference website.
Amazing conference photos were taken by Bartek Pawlik.
I was also really astonished how effective were the talk summaries written by rixx almost in real time.
Cover photo by Bartek Pawlik.
"DjangoCon, why is everybody wearing this t-shirt?" wondered the security guys in the airport of Florence, Italy, in the beginning of April. The reason for that was DjangoCon Europe 2017 happening there for a week, full of interesting talks in an exciting location.
What I liked, was that the conference was not only about technical novelties in Django world, but also about human issues that programmers deal with in everyday life.
According to a manifest, the conference had a goal to strengthen the Django community and to shape responsible attitude towards the works done with Django.
We have to build stronger communities including everyone who wants to participate without discrimination. Although, at first, it might be difficult as people have biases, i.e. prejudices for or against one person or group; by being emphatic we can accept and include everyone no matter what is their gender identity or expression, sexual orientation, ethnicity, race, neurodiversity, age, religion, disabilities, geographical location, food diversities, body size, or family status.
Valuing diversity and individual differences is the key for a healthy, positive and successful community, that empowers its members and helps them grow stronger and happier.
Information technology companies (Apple, Alphabet, Microsoft, Amazon, and Facebook) are among the most traded companies in the world. IT connects people and their things, automates processes, stores and treats historical data. Usually you don't need many physical resources to start an IT business. Software developers have a power to shape the future, but should use this power responsibly:
With this, great responsibility is upon us: to make the future a better place, to make the future more evenly distributed, across gender gaps and discriminations, breaking economical, political and geographical barriers.
From the technical point of view, I liked several ideas mentioned in the conference:
startproject
has a parameter --template
for that where you can pass a URL to a zip file.EXPLAIN ANALYZE ...
SQL command to find the bottlenecks of your database queries. You can usually fix them by adding indexes.Finally, thanks to the organizers for making this conference as great as it was. The city was beautiful, the food and coffee was delicious, the location for the talks was impressive. Looking forward to the next DjangoCon Europe!
Cover photo by Aidas Bendoraitis