Running Nagios3 under Nginx & FastCGI

Wednesday, July 7th, 2010

It is quite possible to run Nagios3’s web interface directly from Nginx and a FastCGI server, rather than having to involve a web application server like Apache. This is useful if you want to preserve memory on your machine, for example.

First of all, we ask Nginx to serve the static files for the Nagios web interface. In Debian/Ubuntu, these live in /usr/share/nagios3/htdocs and /usr/share/nagios3/stylesheets, which is a little awkward, but just the sort of thing that the rewrite command is for …

    location / {
        root /usr/share/nagios3/htdocs;
        index index.html;

        rewrite ^/nagios3/stylesheets/(.*)$ /../stylesheets/$1 break;
        rewrite ^/nagios3/(.*)$ /$1 break;
    }

Next, we tell Nginx to send requests for CGI pages down to a FastCGI server :-

    location ~ \.cgi$ {
        root /usr/lib/cgi-bin/nagios3;
        include /etc/nginx/fastcgi_params;

        rewrite ^/cgi-bin/nagios3/(.*)$ /$1;

        auth_basic "Nagios";
        auth_basic_user_file /etc/nagios3/htpasswd.users;

        fastcgi_pass 127.0.1.1:8998;
        fastcgi_param SCRIPT_FILENAME /usr/lib/cgi-bin/nagios3$fastcgi_script_name;
        fastcgi_param AUTH_USER       $remote_user;
        fastcgi_param REMOTE_USER     $remote_user;
    }

We need to make sure that these requests are under authentication, and that we pass the authenticated username to the CGI script properly, hence the auth_basic and fastcgi_param AUTH_USER lines.

That’s Nginx taken care of, but we also need to make sure there’s a generic FastCGI server running on the specified address/port. No configuration is necessary, as we’re passing everything we need, including the script name. fcgiwrap comes recommended on the Nginx wiki.

/usr/bin/spawn-fcgi -a 127.0.1.1 -p 8998 \
-u www-data -g www-data \
-f /usr/local/bin/fcgiwrap -P /var/run/fcgiwrap.pid

And that’s all you need!

Testing a FastCGI service

Wednesday, July 7th, 2010

If you have a FastCGI service running, normally you just talk to it through the front-end web server. However, for testing purposes you should send requests directly to the fastcgi server.

Getting this done isn’t terribly obvious, as the FastCGI protocol is not in plain text you can’t just telnet to the server and enter commands, the way you can with HTTP. There is a useful command, cgi-fcgi that comes from the libfcgi package on Debian/Ubuntu (and probably in similar packages in other distros), but the man page assumes you already know a lot more about FastCGI than you probably needed to set something up in the first place!

From http://gist.github.com/mpasternacki comes a short wrapper script to invoke cgi-fcgi to send a simple request - http://gist.github.com/209446. This script sets a bunch of environment variables that the fcgi script may require up front.

Here’s a simpler example. I have a FastCGI server running on 127.0.1.1 port 8998. I used lighttpd’s spawn-fcgi to start up an instance of fcgiwrap, a minimal fcgi processor recommended on the Nginx wiki.

/usr/bin/spawn-fcgi -a 127.0.1.1 -p 8998 \
-u www-data -g www-data \
-f /usr/local/bin/fcgiwrap -P /var/run/fcgiwrap.pid

Then, I use cgi-fcgi to connect to the port, and see what I get back.

$ cgi-fcgi -bind -connect 127.0.1.1:8998
Cannot get script name, are DOCUMENT_ROOT and SCRIPT_NAME (or SCRIPT_FILENAME) set and is the script executable?
Status: 403 Forbidden
Content-type: text/plain

403

This is enough to confirm that fcgiwrap is running OK, but doesn’t tell us if it can actually carry out any work. I’ll ask it to run a Nagios CGI command :-

$ DOCUMENT_ROOT=/var/www \
SCRIPT_FILENAME=/usr/lib/cgi-bin/nagios3/tac.cgi \
cgi-fcgi -bind -connect 127.0.1.1:8998
getcgivars(): Unsupported REQUEST_METHOD -> ''

I'm guessing you're trying to execute the CGI from a command line.
In order to do that, you need to set the REQUEST_METHOD environment
variable to either "GET", "HEAD", or "POST".  When using the
GET and HEAD methods, arguments can be passed to the CGI
by setting the "QUERY_STRING" environment variable.  If you're
using the POST method, data is read from standard input.  Also of
note: if you've enabled authentication in the CGIs, you must set the
"REMOTE_USER" environment variable to be the name of the user you're
"authenticated" as.

Excellent, this error message looks like it has come from the actual CGI command itself. Let’s set a couple more variables to help it work … let’s add REQUEST_METHOD as the error message suggested.

$ REQUEST_METHOD=GET DOCUMENT_ROOT=/var/www \
SCRIPT_FILENAME=/usr/lib/cgi-bin/nagios3/tac.cgi \
cgi-fcgi -bind -connect 127.0.1.1:8998

Cache-Control: no-store
Pragma: no-cache
Refresh: 90
Last-Modified: Wed, 07 Jul 2010 01:27:16 GMT
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-type: text/html

...

I’ll spare you the rest of the HTML output. This shows us a way of using a simple command-line test to verify that a FastCGI service is running correctly, without involving any front-end webserver. This is useful if you’re getting stuck setting up the front-end webserver, or if you like to have intelligent service dependency monitoring that can diagnose problems more precisely.

Wordpress on Debian 5 with nginx, apache2 and admin under HTTPS

Saturday, January 16th, 2010

This Wordpress instance is being served by a combination of Nginx and Apache2, with the admin pages under HTTPS using the Admin SSL plugin. It’s all done with the Debian 5 packaged versions and some nifty configuration …

Nginx

The public webserver is Nginx, which is the high-performance front-end webserver you should be using. Nginx is listening for both HTTP and HTTPS. It is directly serving all static content it can, based on a regexp to detect the types.

As added complications, the /wp-uploads/ files are in a different location, and need to have the requested file name rewritten. And there’s no point wasting our time sending static content over HTTPS, we’re going to redirect all of it to HTTP. This will trigger browser warnings regarding mixed content, but that’s not a big problem.

location ~ \.(html|css|js|png|gif|jpg|svg|ico|txt)$ {
    if ($scheme = https) {
        # Refuse to serve static content under HTTPS
        rewrite ^/(.*)$ http://$host/$1 break;
    }
    # Intercept and serve static files directly
    if ($request_uri ~ /wp-uploads/.*$) {
        rewrite ^/wp-uploads/(.*)$ /$1 break;
        root /var/www/wp-uploads/inode.co.nz;
    }
    if ($request_uri !~ /wp-uploads/.*$) {
        root /var/www/inode.co.nz;
    }
}

Everything else is proxied down to an Apache running mod_php, on localhost. This is pretty straightforward, except the Apache itself is running HTTP and HTTPS (using the same certificates as Nginx), so I have to choose which proxy to talk to :-

# Choose the proxy based on the current encryption scheme
if ($scheme = http) {
    proxy_pass http://127.0.1.1:80;
}
if ($scheme = https) {
    proxy_pass https://127.0.1.1:443;
}

We are also setting up some new headers so that Apache can use mod_rpaf, enabling it to log real end-user IP addresses in its logfiles.

Apache

The application webserver is Apache, running mod_php (libapache2-mod-php5), mod_rewrite (which is not enabled by default on Debian) and mod_rpaf (libapache2-mod-rpaf). Apache is listening to both HTTP and HTTPS, but will look out for requests to /wp-admin/ and /wp-login.php and make sure that they are redirected to HTTPS. This is basically the same setup as recommended by the Wordpress Codex.

# Bounce sensitive requests to https
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^/wp-admin(.*) https://inode.co.nz/wp-admin$1 [QSA,L]
    RewriteRule ^/wp-login.php(.*) https://inode.co.nz/wp-login.php$1 [QSA,L]
</IfModule>

The HTTPS server will also rewrite non-admin page requests, and direct you back to the HTTP version.

# Bounce non-sensitive requests to http
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule !^/wp-admin(.*) - [C]
    RewriteRule !^/wp-login.php(.*) - [C]
    RewriteRule ^/(.*) http://inode.co.nz/$1 [QSA,L]
</IfModule>

Admin SSL

This is almost enough, as most of the admin URLs presented by Wordpress are relative, and will therefore be HTTPS if the requested page was HTTPS. But for Wordpress versions below 2.6 (i.e. the standard Debian version is 2.5.1) you will need to install the Admin SSL plugin. The default configuration is fine, just install and enable the plugin (specifically, we are not using Shared SSL).

Conclusion

Here is a diagram showing what we have achieved :-

Wordpress under nginx & apache

HTTPS will be automatically invoked when accessing admin pages, and will be automatically switched off at all other times. Only requests that need PHP will be passed on to Apache, everything else will be handled by nginx.