Installing Nginx Unit on Ubuntu

Published by at 8th September 2017 9:05 pm

Recently Nginx announced the release of the first beta of Unit, an application server that supports Python, PHP and Go, with support coming for Java, Node.js and Ruby.

The really interesting part is that not only does it support more than one language, but Unit can be configured by making HTTP requests, rather than by editing config files. This makes it potentially very interesting to web developers like myself who have worked in multiple languages - I could use it to serve a Python or PHP web app, simply by making different requests during the setup process. I can see this being a boon for SaaS providers - you could pick up the language from a file, much like the runtime.txt used by Heroku, and set up the application on the fly.

It's currently in public beta, and there are packages for Ubuntu, so I decided to try it out. I've created the Ansible role below to set up Unit on an Ubuntu 16.04 server or VM:

2- name: Install keys
3 apt_key: url= state=present
5- name: Setup main repo
6 apt_repository: repo='deb xenial nginx' state=present
8- name: Setup source rep
9 apt_repository: repo='deb-src xenial nginx' state=present
11- name: Update system
12 apt: upgrade=full update_cache=yes
14- name: Install dependencies
15 apt: name={{ item }} state=present
16 with_items:
17 - nginx
18 - unit
19 - golang
20 - php-dev
21 - php7.0-dev
22 - libphp-embed
23 - libphp7.0-embed
24 - python-dev
25 - python3
26 - python3-dev
27 - php7.0-cli
28 - php7.0-mcrypt
29 - php7.0-pgsql
30 - php7.0-sqlite3
31 - php7.0-opcache
32 - php7.0-curl
33 - php7.0-mbstring
34 - php7.0-dom
35 - php7.0-xml
36 - php7.0-zip
37 - php7.0-bcmath
39- name: Copy over Nginx configuration
40 copy: src=nginx.conf dest=/etc/nginx/sites-available/default owner=root group=root mode=0644

Note the section that copies over the Nginx config file. Here is that file:

1upstream unit_backend {
2 server;
5server {
6 listen 80 default_server;
7 listen [::]:80 default_server ipv6only=on;
8 fastcgi_param HTTP_PROXY "";
10 access_log /var/log/nginx/access.log;
11 error_log /var/log/nginx/error.log;
13 root /var/www/public;
14 index index.php index.html index.htm;
16 server_name server_domain_or_IP;
18 location / {
19 try_files $uri $uri/ /index.php?$query_string;
20 }
22 location ~ \.php$ {
23 try_files $uri /index.php =404;
24 proxy_pass http://unit_backend;
25 proxy_set_header Host $host;
26 }

This setup proxies all dynamic requests to the Unit backend in a similar fashion to how it would normally pass it to PHP-FPM.

There were still a few little issues. It doesn't exactly help that the Nginx package provided with this repository isn't quite the same as the one in Ubuntu by default - not only is it the unstable version, but it doesn't set up the sites-available and sites-enabled folders, so I had to do that manually. I also had an issue with Systemd starting the process (at /run/control.unit.sock) with permissions that didn't allow Nginx to access it. I'm not that familiar with Systemd so I wound up just setting the permissions of the file manually, but that doesn't persist between restarts. I expect this issue isn't that big a deal to someone more familiar with Systemd, but I haven't been able to resolve it yet.

I decided to try it out with a Laravel application. I created a new Laravel app and set it up with the web root at /var/www. I then saved the following configuration for it as app.json:

2 "listeners": {
3 "*:8300": {
4 "application": "myapp"
5 }
6 },
7 "applications": {
8 "myapp": {
9 "type": "php",
10 "workers": 20,
11 "user": "www-data",
12 "group": "www-data",
13 "root": "/var/www/public",
14 "index": "index.php"
15 }
16 }

This is fairly basic, but a good example of how you configure an application with Unit. The listener section maps a port to an application, while the applications section defines an application called myapp. In this case, we specify that the type should be php. Note that each platform has slightly different options - for instance, the Python type doesn't have the index or root options, instead having the path option, which specifies the path to the file.

I then ran the following command to upload the file:

$ curl -X PUT -d @app.json --unix-socket /run/control.unit.sock http://localhost

Note that we send it direct to the Unix socket file - this way we don't have to expose the API to the outside. After this was done, the Laravel app began working as expected.

We can then make a GET request to view the configured applications:

1$ curl --unix-socket /run/control.unit.sock http://localhost/
3 "listeners": {
4 "*:8300": {
5 "application": "saas"
6 }
7 },
9 "applications": {
10 "saas": {
11 "type": "php",
12 "workers": 20,
13 "user": "www-data",
14 "group": "www-data",
15 "root": "/var/www/public",
16 "index": "index.php"
17 }
18 }

It's also possible to update and delete existing applications via the API using PUT and DELETE requests.

Final thoughts

This is way too early to be seriously considering using Unit in production. It's only just been released as a public beta, and it's a bit fiddly to set up. However, it has an enormous amount of promise.

One thing I can't really see right now is whether it's possible to use a virtualenv with it for Python applications. In the Python community it's standard practice to use Virtualenv to isolate the dependencies for individual applications, and it's not clear how I'd be able to go about using this, if it is possible. For deploying Python applications, lack of virtualenv support would be a deal breaker, and I hope this gets clarified soon.

I'd also be curious to see benchmarks of how it compares to something like PHP-FPM. It's quite possible that it may be less performant than other solutions. However, I will keep a close eye on this in future.