NGINX CGI 0.15: How to Hook It Into a Running Server
The new CGI module arrives in version 0.15 and is ready for instant deployment on recent Debian or Ubuntu releases. Within ten minutes an ordinary PC user will learn how to install the binary, enable it for a location, and fire up a shell script that prints a greeting.
Getting the pre‑built package
Adding the GetPageSpeed repository keyring to /etc/apt/keyrings is not a cosmetic nicety; if this directory does not exist, the tee command silently fails, and apt will later complain about an “untrusted signature” when trying to install nginx‑cgi.
sudo install -d -m 0755 /etc/apt/keyrings curl -fsSL https://extras.getpagespeed.com/deb-archive-keyring.gpg \ | sudo tee /etc/apt/keyrings/getpagespeed.gpg >/dev/null # Add the repository (Ubuntu example - replace 'ubuntu' and 'jammy' for your distro) echo "deb [signed-by=/etc/apt/keyrings/getpagespeed.gpg] https://extras.getpagespeed.com/ubuntu jammy main" \ | sudo tee /etc/apt/sources.list.d/getpagespeed-extras.list
Once the keyring exists, the next step—appending the repository line for your distribution—to the sources list tells apt where to locate both nginx itself and the new module. After a quick apt‑get update, a single command pulls everything in:
sudo apt-get install nginx nginx-module-cgi
The installation process automatically loads the module, so no manual load_module line is required on vanilla Debian 12 or Ubuntu 24.
Loading the module from a custom location
If nginx was compiled manually or if your system stores modules in /usr/lib/nginx/modules-available, the CGI binary will not be found unless an explicit load statement appears at the top of the configuration.
load_module /some/path/ngx_http_cgi_module.so;
Without that line, any later cgi on directives are ignored and requests to scripts will return “404 Not Found” instead of the intended dynamic content.
Turning CGI on for a specific URI prefix
Once nginx knows about the module, adding cgi on; inside a server or location block tells it to hand every matching request off to an external interpreter. The setting is inherited by nested locations unless a child explicitly turns it off with cgi off.
The cascading behaviour means that if you enable CGI for /cgi-bin, all requests under /cgi-bin/* will spawn the script; only a child location that sets cgi off; will prevent that.
A minimal shell CGI script
For a quick sanity check, place a simple bash file in the document root’s cgi-bin directory. The script must emit a header section followed by an empty line, then the body.
A missing separator is the most common cause of a 500 error; this happened to a user after a bad driver update when an accidental carriage‑return slipped into the output.
The example below shows the bare minimum:
#!/bin/bash echo "Status: 200 OK" echo "Content-Type: text/plain" echo echo "Welcome to CGI world!"
If the shebang line is omitted, nginx‑cgi will reject the file with a “No interpreter” error unless a global cgi_interpreter setting is in place. The safest bet is to keep the shebang and give the file execute permission.
Ensuring execution rights
Nginx‑cgi insists on the executable bit for any script it runs. A 403 Forbidden is easier to diagnose than an opaque “permission denied” error that a missing execute flag can trigger, even on systems where scripts are sometimes run without chmod +x.
The quick workaround—setting cgi_interpreter—is rarely needed; simply applying
sudo chmod +x /var/www/html/cgi-bin/hello.sh
solves the problem in 99 % of setups.
Testing the configuration
After editing the config, a reload guarantees that all changes take effect:
sudo systemctl restart nginx curl http://127.0.0.1/cgi-bin/hello.sh
The response should read “Welcome to CGI world!”. If it does not, consult /var/log/nginx/error.log; a 500 here usually points to an illegal header or a missing separator, while a 403 indicates that the execute bit was never set.
Things that can go wrong
- Invalid HTTP headers – Only Status: <code> <text> and well‑formed key/value pairs are allowed; anything else triggers a 500 unless strict mode is disabled.
- High‑concurrency workloads – Because CGI spawns a new process per request, it does not scale to high QPS or heavy traffic; for those scenarios a lightweight embedded language like Lua or Nginx’s FastCGI integration is preferable.
- Missing shebang – Without cgi_interpreter, the absence of a shebang line produces a 500 error, even if the file otherwise looks correct.
With these straightforward steps anyone running a recent Debian, Ubuntu, or Fedora distribution can have CGI up and running fast enough to prototype admin scripts or lightweight dynamic pages.
Release Nginx CGI v0.15
Allow users to control CGI stderr log level Deprecate REMOTE_USER and AUTH_TYPE vars
