<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0">
<channel>
<title>Fun with ones and zeros - nginx</title>
<description><![CDATA[Barry's notes on computer software and hardware]]></description>
<link>/blog/tags/nginx</link>
<lastBuildDate>Wed, 29 Apr 2026 03:15:51 -0700</lastBuildDate>
<item>
<title>IPv6 World Launch Day</title>
<link>/blog/entries/ipv6-world-launch-day</link>
<pubDate>Sun, 08 Apr 2012 15:36:00 -0700</pubDate>
<author>bp@barryp.org (Barry Pederson)</author>
<description><![CDATA[<img src="/assets/7/18/World_IPv6_launch_banner_256.png" alt="IPv6 Logo" style="float:left; margin-right: 2em;" width="106" height="226"/>
<p>I've been working on getting this website up and running under IPv6, and it turned out to be somewhat involved.
Firstly, I signed up with Hurricane Electric's <a href="http://tunnelbroker.net">tunnelbroker.net</a>, to get IPv6 connectivity, because my ISP
doesn't offer it yet.  Setup my own DNS servers running <a href="http://www.nlnetlabs.nl/projects/nsd/">nsd</a>, which was a bit of a learning curve, but in the long
run I think it'll be better than working with goofy DNS managers like you'd find on registrar or hosting websites.<br />
NameCheap is now letting you setup IPv6 glue records right on their website (previously you had to file a support
ticket), so that made things easier.</p>
<p>The only big glitch I ran into is that on FreeBSD, using simply</p>
<pre><code>listen [::]:80;</code></pre>
<p>to listen to both IPv4 and IPv6 didn't work.  When trying that, I found that any request coming in as IPv4 would give
weird 403 or 404 (I don't remember which) errors, where it seemed nginx just didn't know what virtual host to go to.<br />
Linux doesn't seem to have that problem. Ended up using separate listen statements, as in:</p>
<pre><code>listen 80 default_server;
listen [::]:80 default_server ipv6only=on;</code></pre>
<p>for the main site, but VERY IMPORTANTLY, the remaining sites could not have the <code>ipv6only=on</code> directive, they just
simply say </p>
<pre><code>listen  80;
listen [::]:80;</code></pre>
<p>(found that trick in this <a href="http://serverfault.com/questions/277653/nginx-name-based-virtual-hosts-on-ipv6">ServerFault page</a>).  This also has the advantage of showing proper IPv4 IP addresses in
the logs, instead of IPv4-mapped IPv6 addresses such as <code>::ffff:11.22.33.44</code>, so I ended up doing the same thing on a
Linux box even though it handled dual-stack by default just fine.</p>
<p>I also for testing purposes, made aliases</p>
<ul>
<li><a href="http://ipv4.barryp.org/blog/">http://ipv4.barryp.org/blog/</a></li>
<li><a href="http://ipv6.barryp.org/blog/">http://ipv6.barryp.org/blog/</a></li>
</ul>
<p>To force one protocol or the other.  When you use <a href="http://barryp.org/blog/">http://barryp.org/blog/</a>, it's not obvious which you're
using.</p>]]></description>
</item>
<item>
<title>AWStats under Nginx and SCGI</title>
<link>/blog/entries/awstats-under-nginx-and-scgi</link>
<pubDate>Wed, 14 Sep 2011 06:00:00 -0700</pubDate>
<author>bp@barryp.org (Barry Pederson)</author>
<description><![CDATA[<body><p>Earlier, I wrote about running <a href="/blog/entries/cgi-scripts-nginx-using-scgi">CGI Scripts with Nginx using SCGI</a>
with the help of a small C shim.  One particular CGI app I've had to
alter slightly to work under this setup is <a href="https://awstats.sourceforge.io/">AWStats</a>, which
is a decent-sized Perl app, but only requires one line added to satisfy
SCGI's requirement of a <code>Status</code> line at the beginning of a response.</p>
<p>Here's a patch to AWStats 7.0</p>
<div class="source"><pre><span></span><span class="gd">--- awstats.pl.original 2011-09-11 21:20:40.954555528 -0500</span>
<span class="gi">+++ awstats.pl  2011-03-31 00:19:35.867343845 -0500</span>
<span class="gu">@@ -750,6 +750,7 @@</span>
 #------------------------------------------------------------------------------
 sub http_head {
        if ( !$HeaderHTTPSent ) {
<span class="gi">+                print "Status: 200 OK\n";</span>
                my $newpagecode = $PageCode ? $PageCode : "utf-8";
                if ( $BuildReportFormat eq 'xhtml' || $BuildReportFormat eq 'xml' ) {
                        print( $ENV{'HTTP_USER_AGENT'} =~ /MSIE|Googlebot/i
</pre></div></body>]]></description>
</item>
<item>
<title>CGI Scripts with Nginx using SCGI</title>
<link>/blog/entries/cgi-scripts-nginx-using-scgi</link>
<pubDate>Sun, 11 Sep 2011 15:37:00 -0700</pubDate>
<author>bp@barryp.org (Barry Pederson)</author>
<description><![CDATA[<body><p><em>Using <a href="https://github.com/barryp/scgi-inetd-cgi">scgi_run</a> with Nginx</em></p>
<p><a href="https://www.nginx.com/resources/wiki/">Nginx</a> is a great web server, but one thing it doesn't support
is <a href="https://en.wikipedia.org/wiki/Common_Gateway_Interface">CGI scripts</a>.  Not all webapps need to be high-performance
setups capable of hundreds or thousands of requests per second.  Sometimes
you just want something capable of handling a few requests now and then,
and don't want to keep a long-running process going all the time just
for that one webapp.  How do you handle something like that under Nginx?</p>
<p>Well, it turns out you're going to have to have <em>something</em> running as a
long-running external process to help Nginx out (because Nginx can't
spawn processes itself).  It just doesn't have to be dedicated to any
one particular webapp.  One way to go would be to setup <em>another</em>
webserver that <em>can</em> do CGI scripts, and have Nginx proxy to that when
need be.  </p>
<p>Apache is one possibility, something like this:</p>
<p><img src="/assets/9/21/nginx_apache.png" alt="Nginx &lt;-&gt; Apache"></p>
<p>But Apache's a fairly big program, has lots of features, a potentially
complicated configuration.  Kind of defeats the purpose of going to a
lighter-weight program like Nginx.  What else can we do?</p>
<h2>Super-servers</h2>
<p>Many Unix-type systems will have a <em><a href="https://en.wikipedia.org/wiki/Super-server">super-server</a></em>
available to launch daemons as need be when some network connection is
made.  On BSD boxes it's typically <code>inetd</code>, MacOSX has <code>launchd</code>, Linux
distros often have <code>xinetd</code> or other choices available.  </p>
<p>If we already have a super-server running on our box, why not setup
Nginx to connect to that, and let the super-server take care of launching
our CGI script?  We just need one extra piece of the puzzle, something to
read a web request over the socket Nginx opened up, setup the CGI
environment, and execute the script.  </p>
<p>Wait, that sounds like a web server - aren't we back to something like
Apache again?  No, it doesn't have to be anything nearly that
complicated if we were to use the <a href="https://en.wikipedia.org/wiki/Simple_Common_Gateway_Interface">SCGI</a> protocol, instead of HTTP.</p>
<h2>SCGI</h2>
<p>SCGI is a very simple protocol that's <a href="http://nginx.org/en/docs/http/ngx_http_scgi_module.html">supported by Nginx</a>
and many other webservers.  It's much much simpler than FastCGI, and
maps pretty closely to the CGI specfication, with one minor difference
to note...</p>
<p>In the <a href="https://www.ietf.org/rfc/rfc3875.txt">CGI RFC</a>, the response may contain an optional Status line,
as in:</p>
<pre><code>Status: 200 OK</code></pre>
<p>In the SCGI protocol, the <code>Status</code> line is required, not optional.  </p>
<p><em>Nginx will function with the <code>Status</code> line missing, but there'll be
warnings in your error log.</em></p>
<p>If you can alter your CGI scripts to include a <code>Status</code> line, or live with
warnings in logs, we have a way forward now.</p>
<h2>scgi_run</h2>
<p>I've got a C <a href="https://github.com/barryp/scgi-inetd-cgi">project on GitHub</a> that implements this small piece of
glue to turn a SCGI request into a CGI enviroment.  The binary weighs in at
around 8 to 12 Kilobytes after being stripped.</p>
<p>Basically, we're looking at a flow like this:</p>
<p><img src="/assets/9/22/nginx_scgi.png" alt="Nginx &lt;-&gt; SCGI"></p>
<ol>
<li>Nginx connects to a socket listened to by inetd</li>
<li>inetd spawns <code>scgi_run</code>, with stdin and stdout wired to the accepted
connection</li>
<li><code>scgi_run</code> reads SCGI request headers from stdin and sets up a CGI environment</li>
<li><code>scgi_run</code> execs CGI script (stdin and stdout are still connected to the
socket to Nginx)</li>
<li>CGI script reads request body if necessary from stdin and writes
response out through stdout.</li>
</ol>
<p>A couple things to note here </p>
<ul>
<li>when we get to the final step, the CGI script is talking directly to
Nginx - there's no buffering by any other applications like there would be
in an Apache setup.  </li>
<li>scgi_run is no longer executing, it execed the CGI script so there's
not another process hanging around waiting on anything.</li>
<li>A super-server like inetd can typically be configured to run the handler
under any userid you want, so you basically get SUEXEC-type functionality for
free here.</li>
</ul>
<p>The <code>scgi_run</code> code on GitHub operates in two modes:</p>
<ol>
<li>If argv[1] ends with a slash <code>/</code>, then argv[1] is taken to be a directory
name, and the program will look for the <code>SCRIPT_FILENAME</code> passed by Nginx
in that directory.</li>
<li>Otherwise, argv[1] is taken as the path to a specific CGI script
(so <code>SCRIPT_FILENAME</code> is ignored), and any additional arguments are passed
on to the CGI script.</li>
</ol>
<h2>Configuration</h2>
<p>A simple setup looks something like this, assuming you've compiled <code>scgi_run</code> and have
the binary stored as <code>/local/scgi_run</code> </p>
<p>For FreeBSD <code>inetd</code> for example, you might add a line to <code>/etc/inetd.conf</code> like this:</p>
<pre><code>:www:www:600:/var/run/scgi_localcgi.sock stream  unix    nowait/16   www /local/scgi_run /local/scgi_run /local/cgi-bin/</code></pre>
<p>Which causes <code>inetd</code> to listen to a Unix socket named
<code>/var/run/scgi_localcgi.sock</code>, and when a connection is made, it spawns
<code>/local/scgi_run</code> with argv[0] set to <code>/local/scgi_run</code> and argv[1] set
to <code>/local/cgi-bin/</code>.  As a bonus, the socket ownership is set to www:www
and chmoded to 0600, which limits who can connect to it.</p>
<p>In Nginx, you might have something like:</p>
<pre><code>location /local-cgi/ {
    alias /local/cgi-bin/;

    scgi_pass unix:/var/run/scgi_localcgi.sock;
    include /usr/local/etc/nginx/scgi_params;
    scgi_param  SCRIPT_NAME $fastcgi_script_name;
    scgi_param  PATH_INFO $fastcgi_path_info;
    scgi_param  SCRIPT_FILENAME $request_filename;
}</code></pre>
<p>And then for a simple script, you might have <code>/local/cgi-bin/hello.sh</code> as</p>
<div class="source"><pre><span></span><span class="ch">#!/bin/sh</span>
<span class="nb">echo</span> <span class="s2">"Status: 200 OK"</span>
<span class="nb">echo</span> <span class="s2">"Content-Type: text/plain"</span>
<span class="nb">echo</span> <span class="s2">""</span>
<span class="nb">echo</span> <span class="s2">"Hello World"</span>
</pre></div>
<p>That you would run by hitting <code>http://localhost/local-cgi/hello.sh</code></p>
<h2>Conclusion</h2>
<p>So, with the help of a tiny 8KB binary, Nginx (or any other SCGI client)
with the help of a super-server like <code>inetd</code> can execute CGI scripts
(keeping in mind though the requirement for the <code>Status</code> line).  It's a
fairly lightweight solution that may also be useful in embedded situations.</p>
<p>Enjoy, and <a href="/">go buy some harddrives</a> to store your CGI scripts on, I hear
SSDs are very nice. :)</p></body>]]></description>
</item>
</channel>
</rss>