Talk to a FastCGI Application with cgi-fcgi
Your web server says 502 and you need to know whether the FastCGI app is even listening: cgi-fcgi lets you send it a request by hand. You will connect a CGI request to an already-running application, or start a local one behind a UNIX socket.
The route
Jump straight to the step you need, or tick off Done means at the end.
This guide uses the cgi-fcgi shipped by Ubuntu's libfcgi-bin package, version 2.4.2-2.1ubuntu0.24.04.1 on the reference machine. Allow about 15 minutes for a socket-based smoke test if the application is already installed.
Warning
The ordinary request path is -bind -connect. The -start form creates a listener and forks application processes, so do not use it against a production socket until you have confirmed ownership, permissions and a stop plan.
1. Check the installed command
Confirm which executable will run and record its package version. This is an unprivileged check and does not contact a FastCGI service.
$ command -v cgi-fcgi
/usr/bin/cgi-fcgi
$ dpkg-query -W -f='${Package} ${Version}\n' libfcgi-bin
libfcgi-bin 2.4.2-2.1ubuntu0.24.04.1
$ cgi-fcgi
Missing application pathname
Missing -connect <connName>
The usage error is useful: it confirms the binary is present and shows the four supported forms. The manual describes -f, -bind -connect, -start -connect, and combined -connect operation. There is no separate version flag.
2. Choose the connection form
-bind -connect CONN_NAMEis for a listener another process has already created.CONN_NAMEcan be a UNIX socket path such as/run/myapp/app.sock, or ahost:portpair. The command passes the CGI environment and request body to the application, then copies the application's standard output and error to its own output.-start -connect CONN_NAME APP_PATH [N_SERVERS]makescgi-fcgicreate a local listener and fork the application. For a UNIX socket, give the socket path. For TCP, the documented form islocalhost:PORT, and it cannot start a process on a remote host. IfN_SERVERSis omitted, one application process is started.-connect CONN_NAME APP_PATH [N_SERVERS]first tries to bind. If that fails, it tries to start the local application and then binds again. That fallback is only for UNIX socket connections and does not support TCP/IP in this form, so use the explicit form when the endpoint is TCP.
3. Test an existing UNIX socket
Replace /run/myapp/app.sock with the socket path configured by your web server and FastCGI application. Run this as the account that handles CGI requests.
Tip
Do not add sudo just to make a failed application work. Root can hide a socket ownership mistake and may create files the service account cannot use.
$ cgi-fcgi -bind -connect /run/myapp/app.sock
A successful invocation normally emits the application's response, often including CGI headers such as Content-Type. The exact response belongs to the application, not to cgi-fcgi. A missing or inaccessible socket fails before the request is handled:
$ cgi-fcgi -bind -connect /tmp/cgi-fcgi-guide-no-such.sock
Could not connect to /tmp/cgi-fcgi-guide-no-such.sock
$ printf 'exit status: %s\n' "$?"
exit status: 2
Check the path, the service account's search permission on each parent directory, and the listener process. If another account deliberately owns the socket, change the service configuration or group membership through your normal administration process. Do not delete a socket owned by a live service as a first diagnostic step.
4. Start a local application deliberately
Warning
This form changes state. It creates a listening socket and forks application processes. Use a private runtime directory and a harmless test application first. The application path must be an executable file, and the socket directory must already exist and be writable by the account running cgi-fcgi.
$ install -d -m 0750 "$HOME/run/myapp"
$ cgi-fcgi -start -connect "$HOME/run/myapp/app.sock" /opt/myapp/bin/app 2
$ test -S "$HOME/run/myapp/app.sock" && echo 'FastCGI socket exists'
FastCGI socket exists
The command starts two copies of /opt/myapp/bin/app sharing one listening socket. It does not install a service manager unit, supervise crashes or generate a useful HTTP error response if startup fails. Run it under the same account and environment the eventual service will use.
- No shell commands as APP_PATH. If the application needs arguments, use a small, reviewed wrapper executable with the required permissions.
- Mind the socket location. Keep it outside a world-writable directory, where another user could replace it or interfere with the listener.
Recovery
To undo this test, stop the processes using the service's documented method, then remove the test socket and directory only after confirming no listener still uses them. The following check is read-only.
$ ss -xl | grep -- "$HOME/run/myapp/app.sock"
$ ps -ef | grep -- '[m]yapp/bin/app'
An empty result is expected after the processes have stopped. If a real service owns the path, stop using this example and follow that service's shutdown and restart procedure instead.
5. Use a configuration file for a CGI wrapper
The -f CMD_PATH form reads its arguments from a file. Lines beginning with # are ignored, and the first remaining line must be one of the other command forms. This suits a shebang wrapper, where the kernel needs one fixed interpreter argument.
$ cat > /tmp/myapp-cgi-fcgi.conf <<'EOF'
# The application server owns this socket.
-bind -connect /run/myapp/app.sock
EOF
$ cgi-fcgi -f /tmp/myapp-cgi-fcgi.conf
In a script with execute permission, the equivalent pattern is:
#!/usr/bin/cgi-fcgi -f
-bind -connect /run/myapp/app.sock
The file is an argument source, not a general shell script. Do not put shell variables, pipes, redirections or multiple command lines in it. Treat it as configuration holding a single cgi-fcgi invocation. Remove the temporary file after testing if it contains a private socket path, and keep a deployed wrapper readable only by the accounts that need it.
6. Diagnose the common boundaries
Use -bind -connect for a remote or already-running TCP endpoint, for example:
$ cgi-fcgi -bind -connect 127.0.0.1:9000
Do not use the combined auto-start form with that address, because it is explicitly limited to UNIX sockets. The -start form can create a local TCP listener, but use the documented local spelling and confirm the selected port is free before a service depends on it.
Exit status and diagnostics depend on which stage failed:
- Bind failure. The connection was not established.
- Start failure. The socket directory, executable, permissions or application itself is wrong.
- Successful connection. That still does not prove the CGI response is valid, so inspect the returned headers and application logs.
The manpage notes that error handling is intentionally limited: cgi-fcgi does not generate useful HTTP responses for errors, and the start-only mode generates no response. Let the web server provide the public error handling, and send application diagnostics to a controlled log rather than exposing them to clients.
Done means
- Binary confirmed. You checked the installed
cgi-fcgibinary andlibfcgi-binversion. - Form chosen. You used
-bind -connectfor an existing listener, or explicitly accepted the process and socket changes made by-start. - Paths verified. Your socket path, account permissions and application path are checked, not guessed.
- Auto-start understood. Combined
-connectauto-start is for UNIX sockets, not TCP/IP. - Request answered. A test request returned the application's response, and failures are checked in the listener and application logs.