Home / Alt manpages / fcgiwrap(8)

  • fcgiwrap(8)
  • Admin command
  • linux

Serve CGI Scripts through fcgiwrap and Nginx on Debian

You will connect Nginx to Debian's systemd-managed fcgiwrap socket, point it at a CGI directory, and verify the request with a harmless script. Allow about 20 minutes if Nginx is already installed and you can reload its configuration. You need root access for package and service changes; the diagnostic commands can usually run as an ordinary user.

1. Check the installed fcgiwrap and socket

This guide is based on Debian's fcgiwrap package 1.1.0-14build1, whose executable reports fcgiwrap 1.1.0. The package supplies a systemd socket unit listening on /run/fcgiwrap.socket. The socket is owned by www-data:www-data and has mode 0660, so the Nginx worker must be able to access it.

$ fcgiwrap -h
Usage: fcgiwrap [OPTION]
Invokes CGI scripts as FCGI.

$ systemctl status fcgiwrap.socket --no-pager
$ stat -c '%A %U:%G %n' /run/fcgiwrap.socket

Expect the socket unit to be active after it has been started. The stat command should show srw-rw---- and www-data:www-data on this installation. If the socket does not exist, start the socket before touching Nginx:

# systemctl enable --now fcgiwrap.socket
# systemctl is-active fcgiwrap.socket
active

This enables socket activation for future boots and starts it now. To undo only that enablement, use systemctl disable --now fcgiwrap.socket during a maintenance window. Stopping it will break requests that depend on this socket.

2. Put one known CGI script in place

Use a directory that is deliberately separate from ordinary static files. Debian's example configuration maps the URL prefix /cgi-bin/ to /usr/lib/cgi-bin. Check whether that directory already contains the script you intend to run:

$ sudo find /usr/lib/cgi-bin -maxdepth 1 -type f -printf '%M %u:%g %p\n'
$ sudo test -x /usr/lib/cgi-bin/example.cgi && echo executable

Replace example.cgi with the real filename. A CGI program must be executable and should emit a valid CGI response, starting with headers such as Content-Type and followed by a blank line. Do not make an unreviewed file executable in a web-facing directory: fcgiwrap runs CGI programs on behalf of incoming requests.

For a controlled smoke test, a minimal shell CGI could be installed by an administrator after reviewing it:

# install -o root -g www-data -m 0750 /path/to/reviewed-example.cgi /usr/lib/cgi-bin/example.cgi
$ head -n 8 /usr/lib/cgi-bin/example.cgi
#!/bin/sh
printf 'Content-Type: text/plain\n\n'
printf 'fcgiwrap test OK\n'

The install command changes system state and overwrites the destination if it already exists. Preserve an existing file first, or choose a new name. Remove the test script after verification with rm -- /usr/lib/cgi-bin/example.cgi only when you are certain it is not used by another site.

Checkpoint: confirm the script before configuring Nginx

At this point you should have an executable CGI path and an active socket. If either is missing, stop here. Nginx errors cannot repair a missing script or an inaccessible socket.

3. Add the Nginx CGI location

Inside the relevant server block, add a location like this. It follows Debian's installed example and uses SCRIPT_FILENAME, which gives fcgiwrap the complete executable path and avoids ambiguity around PATH_INFO.

location /cgi-bin/ {
    gzip off;
    root /usr/lib;

    include /etc/nginx/fastcgi_params;
    fastcgi_param SCRIPT_FILENAME /usr/lib$fastcgi_script_name;
    fastcgi_pass unix:/run/fcgiwrap.socket;
}

With this mapping, a request for /cgi-bin/example.cgi produces the path /usr/lib/cgi-bin/example.cgi. The manual says that SCRIPT_FILENAME overrides the DOCUMENT_ROOT plus SCRIPT_NAME calculation. That is useful here because the filesystem path is explicit. Do not copy this location into a server that exposes a different CGI directory without changing both the URL and filesystem mapping.

Test the complete Nginx configuration before reloading it:

$ sudo nginx -t
nginx: configuration file /etc/nginx/nginx.conf test is successful
# systemctl reload nginx

A reload normally keeps existing connections alive, but it still changes live service behaviour. If the test fails, do not reload. Restore the previous configuration or remove only the new location, then rerun nginx -t.

4. Make a request and check the result

Use the public hostname and URL that select the server block you edited. A local request is less surprising when the site is bound locally:

$ curl --include http://127.0.0.1/cgi-bin/example.cgi
HTTP/1.1 200 OK
Content-Type: text/plain

fcgiwrap test OK

The exact HTTP headers vary with Nginx. The useful checks are a successful response, the CGI content type, and the body produced by the script. If the script accepts a path suffix, a URL such as /cgi-bin/example.cgi/extra supplies the remainder as PATH_INFO only when the web server passes the parameters in a compatible way. Supplying SCRIPT_FILENAME prevents fcgiwrap from rewriting that value while finding the executable.

5. Diagnose the common failures

A 502 Bad Gateway usually means Nginx cannot connect to the FastCGI endpoint or the upstream closed the connection. Check the socket, unit logs and Nginx error log:

$ systemctl is-active fcgiwrap.socket
$ journalctl -u fcgiwrap.service -n 50 --no-pager
$ sudo tail -n 50 /var/log/nginx/error.log

If the socket is present but access is denied, compare its owner and mode with the Nginx worker identity. Do not solve that by making the socket world-writable. The package changed its socket permissions from a world-writable setting to 0660 after a security issue, so widening access can turn an access problem into a privilege-escalation risk.

A 404 from Nginx often means the location or URL does not match what you configured. A Permission denied message from fcgiwrap usually points to the script, one of its parent directories, or a missing execute bit. Check without changing anything:

$ namei -l /usr/lib/cgi-bin/example.cgi
$ test -x /usr/lib/cgi-bin/example.cgi; echo "script status: $?"
$ sudo journalctl -u fcgiwrap.service -n 50 --no-pager

With -f, fcgiwrap sends CGI standard error through FastCGI so it appears in the web server's error log. Debian's systemd service already sets DAEMON_OPTS=-f. Avoid adding another conflicting service or manually binding the same socket with fcgiwrap -s; the packaged socket unit already opens the endpoint and passes it on file descriptor 0.

Done means

  • fcgiwrap.socket is active and /run/fcgiwrap.socket is not world-writable.
  • The CGI file is reviewed, executable, and mapped to the requested URL deliberately.
  • nginx -t passed before the live reload.
  • A test request returned the expected CGI headers and body.
  • You know where to check Nginx and fcgiwrap logs without weakening socket permissions.