I am trying to get cgi-bin scripts running on an nginx system using uWSGI and I am having issues with this, and with debugging this to figure out what is going wrong. The system is a Raspberry Pi running Rasbian bookworm, and I have added an nginx location block for cgi-bin to the default site in /etc/nginx/sites-available/default
as follows...
location ^~ /cgi-bin/ {
root /var/www/cgi-bin;
include uwsgi_params;
uwsgi_pass unix:/tmp/uwsgi.sock;
}
As far as I understand, that location block defines an expression so that anything beginning with cgi-bin is then handled within. The barebones configuration for uWSGI includes a file provided with my system that sets some defaults, /etc/nginx/uwsgi_params
which is here...
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;
The above is simple enough, and I have experimented with a few things there when doing that. I have created a systemd unit which runs uWSGI with a specific configuration, which you can see here...
[uwsgi]
uid = uwsgi
gid = uwsgi
plugins = cgi,logfile
socket = /tmp/uwsgi.sock
cgi = /cgi-bin=/var/www/cgi-bin
chmod-socket = 666
chown-socket = www-data:www-data
req-logger = file:/var/log/uwsgi/errors.log
logger = file:/var/log/uwsgi/errors.log
chdir = /var/www/cgi-bin
I have tried some other variations with the above configuration regarding paths. It was after I had a look at things with the methods below, that I decided to give up on my concern with these paths and these sort of details.
Things start up, permissions on the cgi-bin
directory are set properly, and things are running normal for me here. When I try to run a script with the URL http://this.system/cgi-bin/nph-proxy.cgi
I get a 502 error in nginx, and then this error appears in the log I set for uWSGI...
-- unavailable modifier requested: 0 --
The log does not show much detail on what has gone wrong here, and that is my main problem. I could not find any way to get more detailed logs or more context about this error, and so I went on to do some further investigations. There were some threads where some Django users were having issues due to missing plugins. I have verified that the plugins for CGI and these error logs exist and are being loaded.
This corresponds with this error for the request in the nginx error log...
2023/07/24 18:02:18 [error] 32579#32579: *5 upstream prematurely closed connection while reading response header from upstream, client: 192.168.1.4, server: _, request: "GET /cgi-bin/test.cgi HTTP/1.1", upstream: "uwsgi://unix:/tmp/uwsgi.sock:", host: "redacted.home"
Because there were no options to get more detailed logs, I decided to have a look some other ways. The first was to get a GPT-4 instance a friend lends to me to create a simple CGI Perl script that simply creates a file when it is ran and writes it's environment variables into it...
#!/usr/bin/perl
use CGI;
my $cgi = CGI->new;
# Open the debug file
open(my $fh, '>', '/var/www/cgi-bin/test.log') or die "Could not open file 'debug_file.txt' $!";
# Print the environment variables to the file
foreach my $key (sort keys %ENV) {
print $fh "$key=$ENV{$key}\n";
}
# Close the file
close $fh;
# Generate a simple HTTP response
print $cgi->header('text/plain');
print "Debug information written to '/path/to/debug_file.txt'.\n";
exit;
No file was ever created by this simple Perl script here, and so I thought I would try a couple of other things to see what was happening.
I decided to use the tool socat with command socat UNIX-LISTEN:/tmp/uwsgi.sock,user=www-data STDOUT
to listen on the socket that uWSGI uses, to see requests myself. The output was slightly garbled and would not paste well here, but I was able to see some of the details nginx was passing on in this request...
SERVER_PROTOCO HTTP/1.1
REQUEST_SCHEME http
REQUEST_URI /cgi-bin/test.cgi
PATH_INFO /cgi-bin/test.cgi
There were other things in the output from socat here, but overall nothing really stood out here either that was obvious or that I could change. However, I thought the paths that nginx was providing here might be a culprit. Tweaking with paths and such changed nothing, as my next investigations into what is going on reveal.
I also decided to use strace
to monitor the uWSGI worker process, strace
allows one to look at system calls made by a process, which although is a bit limiting can give some insights. I obtained the process ID from the log file when starting up the service. I used this strace command strace -fvv -s 9999 -p 32699
which follows any forks, is very verbose, and does not truncate strings that are less than 9999 bytes. Once running, it waits like this...
strace: Process 32699 attached
epoll_wait(10,
When I make the HTTP request to nginx for my testing script in the cgi-bin
directory, we can see the worker process do some work. We can see the uWSGI worker ask the operating system for the request here, and some information about it. The next thing is for the script to make a system call related to writing the error into the log.
epoll_wait(10, [{EPOLLIN, {u32=9, u64=9}}], 1, -1) = 1
accept4(9, {sa_family=AF_UNIX}, [110->2], SOCK_NONBLOCK) = 12
read(12, "\0v\1\0\f\0QUERY_STRING\0\0\16\0REQUEST_METHOD\3\0GET\f\0CONTENT_TYPE\0\0\16\0CONTENT_LENGTH\0\0\v\0REQUEST_URI\26\0/cgi-bin/nph-proxy.cgi\t\0PATH_INFO\26\0/cgi-bin/nph-proxy.cgi\r\0DOCUMENT_ROOT\20\0/var/www/cgi-bin\17\0SERVER_PROTOCOL\10\0HTTP/1.1\16\0REQUEST_SCHEME\4\0http\v\0REMOTE_ADDR\v\000192.168.1.4\v\0REMOTE_PORT\5\00040434\v\0SERVER_PORT\2\080\v\0SERVER_NAME\1\0_\t\0HTTP_HOST\n\0pidns.home\17\0HTTP_USER_AGENT\n\0curl/8.0.1\v\0HTTP_ACCEPT\3\0*/*", 4100) = 378
write(2, "-- unavailable modifier requested: 0 --\n", 40) = 40
close(12) = 0
Here I break down the above to something easier to read...
REQUEST_URI /cgi-bin/nph-proxy.cgi
PATH_INFO /cgi-bin/nph-proxy.cgi
DOCUMENT_ROOT /var/www/cgi-bin
SERVER_PROTOCOL HTTP/1.1
REQUEST_SCHEME http
SERVER_PORT 80
SERVER_NAME (possibly empty)
It comes to my attention that some some paths don't look quite right in the request as seen by strace. The proc filesystem also indicated some issues with the working directory being used, which was set to /
- which I fixed and also updated in the configuration files I have shown above for both nginx and uWSGI.
I think it says a lot here about what strace isn't showing us here which is file operations, or any system calls to run the script. Despite various tweaks to nginx and the configuration for the uWSGI service, there are never any attempts to open any files or to execute anything.
Documentation shows that users having this error had to install and enable certain plugins for the error to go away. These people were running Python/Django applications however, and I am just using CGI and the logfiles extension. The documentation for running CGI applications mentions nothing beyond enabling the CGI plugin and having a cgi
variable in the configuration file.
Given that the error happens without the process doing anything more than getting a request, the process never attempts anything with details in the request, and that some users get this error when plugins and such are missing - I think one can assume that the unsupported modifiers
are basically variables provided by nginx. The defaults here would have everything needed by modern web applications, for things like clean-urls and other things which don't exist in CGI.
I suspect what might fix this is some reduction in what is being passed by nginx here, but I cannot find anything about this in the documentation. If someone is more familiar with the workings of CGI, maybe something in my configuration or debugging above stands out.