Running (almost) anything on Nginx with uWSGI
This is a sort of follow-up post to Running Trac on Nginx with Phusion Passenger.
As my hosting needs have slightly changed lately I now have no immediate need for running Ruby, Rails or Rack applications. I’m lately using more and more web applications but they are based on different technologies.
The current list looks like:
- cgit (C, CGI)
- ownCloud (PHP, FCGI or php-fpm)
- Piwik (PHP, FCGI or php-fpm)
- Roundcube (PHP, FCGI or php-fpm)
- Trac (Python, FCGI or WSGI)
- Firefox Sync Server (Python, FCGI or WSGI)
From the list above, Phusion Passenger currently only supports Trac. Also it doesn’t seem to work with the Firefox Sync Server as its standard installation recommends virtualenv and Passenger always uses the system-wide Python installation unless you want to apply ugly hacks (loading another interpreter from a running Python script).
So overall Phusion Passenger did not look like a good match for my needs anymore and I started looking for something else that could handle as many technologies as possible without me needing to setup a different application server for each technology (and learning a different configuration syntax for each of them).
Enter uWSGI
It turns out that there’s already something that does handle most (if not all) of the above mentioned languages or frameworks and it’s called uWSGI. Despite its name, uWSGI supports far more than running Python-based UWSGI applications thanks to its plugin system and a truckload of plugins that are part of the standard distribution. This also includes Rack-based applications (probably includes Rails as well), PHP and even plain old CGI.
Unfortunately uWSGI is not yet available in Debian Squeeze (stable) but it’s part of Debian Wheezy (testing). Because Wheezy is currently in feature freeze and has proven to be “stable enough” on all my Linux boxes I’ve decided to use Debian Wheezy for my new root-server.
Installing uWSGI on Debian Wheezy
Installing uWSGI is just a matter of apt-get install uwsgi-core
and then
add any of uwsgi-plugin-foo
packages to the mix.
I started with uwsgi-plugin-python
and uwsgi-plugin-cgi
.
For some reason Debian does not package the PHP plugin for uWSGI, fortunately building it manually is not that much work:
- Install packages
libphp5-embed
andphp5-dev
- Fetch uwsgi package sources using
apt-get source uwsgi
- Copy
debian/buildconf/uwsgi-plugin.ini.in
todebian/buildconf/uwsgi-plugin.ini
and replace@@curdir@@
with the absolute path to the source directory - Execute
python uwsgiconfig.py --plugin plugins/php debian/buildconf/uwsgi-plugin.ini
to build the plugin - The resulting
plugin_php.so
can now be copied to/usr/lib/uwsgi/plugins/
Please note that every time libphp5-embed
received an update so far I also
had to rebuild the plugin. I don’t know exactly why that’s the case but I guess
it’s a trick to make me submit a patch to the Debian uWSGI packager(s) ;)
Configuring uWSGI and Nginx
In the following I’ll show a few configuration examples how I got different applications and languages working with uWSGI and Nginx.
Because all this is on Debian Wheezy every uWSGI app creates a socket
at /var/run/uwsgi/app/APPNAME/socket
. For more information about the default
configuration of an application just check /usr/share/uwsgi/conf/default.ini
and
uWSGI Configuration Options.
In my examples all uWSGI apps run as their own user instead of www-data
.
This is of course not needed but should be a little bit more secure. For even
more separation it would probably make sense to add chrooting and setting POSIX
Capabilites as documented in the
Securing uWSGI
section of uWSGI documentation but I’ll omit that to keep the examples small.
Cgit on Nginx with uWSGI
Getting a CGI application like cgit working is quite easy, at least if there’s only one CGI binary to execute.
/etc/uwsgi/apps-available/cgit.ini
[uwsgi]
plugins = cgi
chown-socket = www-data:www-data
uid = cgit
gid = cgit
processes = 1
threads = 8
cgi = /usr/local/lib/cgi-bin/cgit.cgi
For Nginx it’s just a matter of adding a new virtual host and pointing it to the socket created by the above configuration:
/etc/nginx/sites-available/com.example.git.conf
server {
root /srv/www/com.example.git;
server_name git.example.com;
location / {
try_files $uri @cgit;
}
location @cgit {
include uwsgi_params;
uwsgi_modifier1 9;
uwsgi_pass unix:/var/run/uwsgi/app/cgit/socket;
}
}
Trac on Nginx with uWSGI
Setting up Trac is very similar, except that defining the entry point of a Python-based WSGI application works a bit different:
/etc/uwsgi/apps-available/trac.ini
[uwsgi]
plugins = python
chown-socket = www-data:www-data
uid = trac
gid = trac
env = TRAC_ENV=/srv/trac/projectname
module = trac.web.main:dispatch_request
Adding a virtual host to Nginx is also only a few lines:
/etc/nginx/sites-available/com.example.trac.conf
server {
root /srv/www/com.example.trac;
server_name trac.example.com;
location / {
include uwsgi_params;
uwsgi_pass unix:/var/run/uwsgi/app/trac/socket;
}
}
Piwik on Nginx with uWSGI
Getting Piwik/Matomo running is easy in terms of uWSGI and shows
little changes except for the plugins
key:
/etc/uwsgi/apps-available/trac.ini
[uwsgi]
plugins = php
chown-socket = www-data:www-data
uid = piwik
gid = piwik
cheaper = 1
processes = 4
Now the Nginx configuration is a bit more elaborate. Parts of it are based on François Lefèvre’s post about Piwik Hardening with Nginx and PHP-FPM and perusio’s piwik-nginx config example at GitHub.
/etc/nginx/sites-available/com.example.trac.conf
server {
listen 80;
root /srv/www/com.example.piwik;
server_name piwik.example.com;
index piwik.php;
# For administrative access
location = /index.php {
include uwsgi_params;
uwsgi_modifier1 14;
uwsgi_pass unix:/var/run/uwsgi/app/piwik/socket;
allow 127.0.0.1; # only via ssh tunnel
deny all;
}
# For public access
location = /piwik.php {
include uwsgi_params;
uwsgi_modifier1 14;
uwsgi_pass unix:/var/run/uwsgi/app/piwik/socket;
}
# Any other attempt to access PHP files is forbidden
location ~* ^.+\.php$ {
return 403;
}
# Redirect to the root if attempting to access a txt file.
location ~* (?:DESIGN|(?:gpl|README|LICENSE)[^.]*|LEGALNOTICE)(?:\.txt)*$ {
return 302 /;
}
# Disallow access to several helper files.
location ~* \.(?:bat|html?|git|ini|sh|svn[^.]*|txt|tpl|xml)$ {
return 404;
}
# Disallow access to directories
location ~ ^/(config|core|lang|misc|tmp)/ {
deny all;
}
}
Good bye Apache and mod-fcgid
uWSGI is a real breeze to configure. Combining it with Nginx gave me an IMHO pretty clean solution for separating web applications from eachother and having a single point for configuring apps regardless of what technology they use.
Especially the configuration part got me sold on uWSGI after I looked back at all the hoops I had to jump through in the past with apache fcgid/fastcgi modules, suexec, php-fpm and numerous wrapper-scripts to get fastcgi and suexec to play nicely together.
Finally I also have gained easier control over the number of spawned processes, something which wasn’t so straightforward with mod-fcgid and php5-cgi.