NGINX Unit is a fully dynamic application server that can serve multiple languages as well as multiple versions of each language. It’s dynamic in the sense that you use the RESTful JSON API to make changes to its configuration in memory, without service disruption or configuration reloads.
In my presentation at NGINX Conf 2018 in October, I showed how to configure a new application in an existing production environment. Specifically, with WordPress running on PHP, I deployed a Python application that uses the Django framework. I also showed show how you can load configuration both from a file and as specified with an argument to an API call.
This blog includes all of the commands and configuration code I used in the demo, to make it easier for you to adapt to your own deployment.
For the demo at NGINX Conf, I had the following software installed:
root
privilege, or equivalent access via sudo
(which we use where necessary)I also had PHP and WordPress installed as the existing application.
Change into the directory where we’re creating our Django project:
$ cd /var/www/
Use the django-admin
startproject
command to initialize the new project. We’re calling it djapp.
$ sudo django-admin startproject djapp
Change into the project directory:
$ cd djapp
Use the manage.py
script to migrate the database for the project, which is necessary for a newly created project. Django uses SQLite by default, and I accept the default in the demo, but you can use any database that meets your project’s needs.
The manage.py
script is installed by the django-admin
command we ran in Step 2; it performs the same commands and accepts the same arguments as django-admin
, but automatically derives and uses some project‑specific settings, which is helpful. For details, see the Django documentation.
$ sudo python3 manage.py migrate
Although it’s not strictly necessary for a sample project like this one, we recommend that you create a Django superuser identity:
$ sudo python3 manage.py createsuperuser
Change into the subdirectory that contains the settings.py file, which was created by the django-admin
startproject
command in Step 2.
$ cd /var/www/djapp/djapp
Using your preferred text editor, open settings.py. Here we’re using nano
:
$ sudo nano settings.py
Find the ALLOWED_HOSTS
line and add in the domain name, hostname, or IP address for the application:
ALLOWED_HOSTS = ['domain-name']
Also add the following line at the end of the file, to name the directory that stores all static content served by the application (see Step 9).
STATIC_ROOT = '/var/www/djapp/djapp/static'
Change back to the main project directory (where manage.py resides).
$ cd ..
Run the manage.py
collectstatic
command to collect all static files located in the Django project and put them into the STATIC_ROOT
location defined in Step 7.
$ sudo python3 manage.py collectstatic
By default, Django itself serves the static content for a project, but NGINX Open Source and NGINX Plus offer superior performance. Here we configure NGINX Plus, but you can use NGINX Open Source except for one feature noted below.
Change directory to /etc/nginx/conf.d, the conventional location for function‑specific (or in our case, application‑specific) HTTP configuration files:
$ cd /etc/nginx/conf.d
Create a file called django.conf (again, we’re using nano
):
$ sudo nano django.conf
Insert the following configuration, which enables caching.
The configuration also includes two features that are exclusive to NGINX Plus. Uncomment the relevant directives if you are using NGINX Plus and want to take advantage of the features:
status_zone
directive. I’m assuming that the NGINX Plus API is enabled elsewhere in the configuration.health_check
directive.One thing to note is that in the demo at NGINX Conf I specified the IP address of my local machine as the second argument to the proxy_set_header
directive. In a production environment, it makes more sense to use the $host
variable as shown below.
# Upstream group for the backend (NGINX Unit running the Python application)
upstream django_unit {
zone django_unit 64k;
server 127.0.0.1:8000;
}
server {
listen 8080;
# Uncomment to collect metrics if using NGINX Plus and the NGINX Plus API
#status_zone django;
# enable caching
proxy_cache django_cache;
proxy_cache_valid 200 60m;
# root directory for static files
root /var/www/djapp/djapp;
# proxy to the NGINX Unit backend
location / {
proxy_pass http://django_unit;
# Second argument must match your production hostname and the value of
# ALLOWED_HOSTS in settings.py
proxy_set_header Host $host;
# Uncomment to enable active health checks if using NGINX Plus
#health_check;
}
# Location for the static files collected from Django and served by
# NGINX Plus; can be empty (as here), because it inherits the value of the
# 'root' directive from its parent block
location /static {
}
}
Check the configuration for syntactic validity:
$ sudo nginx –t
After fixing any errors, reload the configuration:
$ sudo nginx -s reload
To finish up, we need to configure NGINX Unit to serve the requests to the application.
Run this curl
command to display the current NGINX Unit configuration, which is for WordPress running on PHP. I don’t show the output here, but the WordPress configuration appears in Step 6 below, along with the Python application’s configuration, which we’re about to add.
Note that I use sudo
for the curl
command, which you may not need to do for most curl
commands. Here it’s necessary because to access the UNIX socket we need the read‑write permission that root
has on it.
$ sudo curl --unix-socket /run/control.unit.sock http://localhost/config/
Change to the directory for NGINX Unit configuration files.
Keep in mind that these files are optional and just a convenient way to load collections of configuration without typing all the data as an argument to a call to the NGINX Unit API. Because the content of the files is uploaded through the API (like all configuration data), NGINX Unit does not know about file locations and cannot automatically read files as it starts (unlike NGINX Open Source and NGINX Plus). Instead, NGINX Unit saves its runtime state in a separate directory.
$ cd /etc/unit
Create a file called django.config (again, we’re using nano
):
$ sudo nano django.config
Add the following JSON, which represents our Python application.
{
"type": "python",
"processes": 5,
"module": "djapp.wsgi",
"path": "/var/www/djapp"
}
Run this curl
command to load the JSON contained in django.config as a new application object to be managed by NGINX Unit, called djapp:
$ sudo curl -X PUT --data-binary @/etc/unit/django.config --unix-socket /run/control.unit.sock http://localhost/config/applications/djapp
In this command:
PUT
method creates a new NGINX Unit configuration object at the location named by the final argument (the URL). See the final bullet below.--data-binary
argument tells curl
to load the contents of django.config exactly as provided, preserving newlines and carriage returns, and not doing processing of any kind.--unix-socket
argument defines where the NGINX Unit API is listening. (We use the sudo
command because we’re using the default owner of the socket, root
.)config
is the top‑level NGINX Unit configuration object, applications
the parent for application objects, and djapp
the name of the new application object.Define the listener object for the application. Rather than loading a file of configuration data as in Step 4, we define the data directly on the curl
command line, specifying that the djapp
application listens on port 8000.
$ sudo curl -X PUT --data-binary '{"application":"djapp"}' --unix-socket /run/control.unit.sock 'http://localhost/config/listeners/*:8000'
Repeat the curl
command from Step 1 to display the NGINX Unit configuration, which now includes our Python application, djapp, highlighted in orange:
$ sudo curl --unix-socket /run/control.unit.sock http://localhost/config/{
"listeners": {
"127.0.0.1:8090": {
"application": "script_index_php"
},
"127.0.0.1:8091": {
"application": "direct_php"
},
"*:8000": {
"application": "djapp"
}
},
"applications": {
"script_index_php": {
"type": "php",
"processes": {
"max": 20,
"spare": 5
},
"user": "www-data",
"group": "www-data",
"root": "/var/www/wordpress",
"script": "index.php"
},
"direct_php": {
"type": "php",
"processes": {
"max": 5,
"spare": 0
},
"user": "www-data",
"group": "www-data",
"root": "/var/www/wordpress",
"index": "index.php"
},
"djapp": {
"type": "python",
"processes": 5,
"module": "djapp.wsgi",
"path": "/var/www/djapp"
}
}
}
In this post we started with NGINX Unit running PHP applications for WordPress in production, and added a Python application. In the demo, I use the NGINX Plus dashboard to show that there is no disruption to the existing applications when a new application is added, but you can use any system‑monitoring tool, such as the ps
command, for that purpose. The dynamic nature of NGINX Unit configuration saves resources for your running applications, and ensures zero downtime on new deployments and smooth transition between application versions.
To learn more, visit unit.nginx.org.
"This blog post may reference products that are no longer available and/or no longer supported. For the most current information about available F5 NGINX products and solutions, explore our NGINX product family. NGINX is now part of F5. All previous NGINX.com links will redirect to similar NGINX content on F5.com."