Configuration: CGI support
This page documents the cgi directive for configuring Ferron’s CGI (Common Gateway Interface) support. CGI enables dynamic content by spawning external interpreters for scripts matched by file extension or placed under a cgi-bin directory.
cgi
example.com {
cgi {
extension ".php"
interpreter ".php" php-cgi -c /etc/php/cgi.ini
environment "APP_ENV" "production"
}
}The cgi block can be written as a boolean flag to enable CGI with all defaults, or as a block with nested directives to customize behavior.
| Form | Description |
|---|---|
cgi | Enables CGI with all defaults. |
cgi true | Explicitly enables CGI. |
cgi false | Disables CGI for the current scope. |
cgi { ... } | Enables CGI and configures nested directives. |
extension
| Nested directive | Arguments | Description | Default |
|---|---|---|---|
extension | <string> | This directive registers an additional file extension that should be treated as a CGI script. Unlike cgi-bin directory matching, the file does not need to be executable. This directive can be specified multiple times. | — |
Configuration example:
example.com {
cgi {
extension ".php"
extension ".rb"
}
}- Extensions are matched case-insensitively.
- Files with these extensions are treated as CGI scripts regardless of their location in the file tree.
- This is complementary to
cgi-bindirectory matching — files insidecgi-binare always treated as CGI scripts.
interpreter
| Nested directive | Arguments | Description | Default |
|---|---|---|---|
interpreter | <extension: string> <arg: string>... | This directive maps a file extension to a custom interpreter command. The first argument is the extension (with a leading dot, e.g. .php). Subsequent arguments form the interpreter command line. Pass false as the second argument to disable the interpreter for that extension. This directive can be specified multiple times. | built-in defaults |
Configuration example:
example.com {
cgi {
interpreter ".php" php-cgi -c /etc/php/cgi.ini
interpreter ".pl" perl
interpreter ".py" python3
interpreter ".php" false
}
}- The file path is automatically appended as the final argument.
- When
falseis used as the second argument, the interpreter list is cleared, meaning the file must be directly executable (e.g., via a shebang line or native executable). - For Unix systems, files without a matching interpreter must have the executable permission bit set.
- On Unix systems, scripts with a shebang line (e.g.,
#!/usr/bin/env python3) are parsed and the interpreter is derived from the shebang. - On Windows,
.exefiles are executed directly,.bat/.cmdfiles usecmd /c, and scripts with shebangs are parsed similarly to Unix.
environment
| Nested directive | Arguments | Description | Default |
|---|---|---|---|
environment | <name: string> <value: string> | This directive sets a CGI environment variable passed to the interpreter process. Values are resolved with the same interpolation syntax as other directives. This directive can be specified multiple times. | — |
Configuration example:
example.com {
cgi {
environment "APP_ENV" "production"
environment "APP_SECRET" "{{env.APP_SECRET}}"
environment "RUBY_VERSION" "3.3"
}
}- Environment variables take precedence over any existing variables with the same name.
- The
Proxyheader is automatically removed from the request to prevent the httpoxy vulnerability. - Ferron always sets
SERVER_SOFTWARE,SERVER_NAME,SERVER_PORT,REQUEST_URI,QUERY_STRING,PATH_INFO,SCRIPT_NAME,AUTH_TYPE,REMOTE_USER, andSERVER_ADMINautomatically.
- The
Proxyheader is always removed to prevent the httpoxy vulnerability. - If a CGI script exits with a non-zero status, Ferron logs a
WARNmessage and returns a500 Internal Server Errorresponse — stderr output is logged as a warning (trimmed before logging). - The working directory is set to the directory containing the script file.
Default interpreters
The following built-in interpreters are available when no custom interpreter directive matches:
| Extension | Default interpreter |
|---|---|
.pl | perl |
.py | python |
.sh | bash |
.ksh | ksh |
.csh | csh |
.rb | ruby |
.php | php-cgi |
.exe (Windows) | (direct execution) |
.bat (Windows) | cmd /c |
.vbs (Windows) | cscript |
Default index files
When CGI is enabled and no explicit index directive is configured, Ferron automatically injects default index file names. By default, the following files are checked in order: index.html, index.htm, index.xhtml.
If you register additional extensions via the extension directive, Ferron also prepends corresponding index files to the front of the list:
| Registered extension | Prepend to index list |
|---|---|
.cgi | index.cgi |
.php | index.php |
For example, with extension ".php" configured, the injection order becomes: index.php, index.html, index.htm, index.xhtml.
This injection only applies when no explicit index directive is set. If you configure your own index directive, Ferron will use that instead.
CGI scripts must be inside a cgi-bin directory or have an extension registered via the extension directive. On Unix, scripts without a matching interpreter directive must have the executable permission bit set (chmod +x). On Windows, shebang lines are supported for .bat, .cmd, and other script files — native .exe files are executed directly.
CGI script locations
A request is handled as a CGI script when:
- The resolved path is inside a
cgi-bindirectory (case-insensitive match on the first path component after the document root), or - The file extension matches one registered via the
extensiondirective.
When a matching file is found, Ferron checks for an interpreter using the following priority:
- Custom
interpreterdirective matching the file extension. - Built-in default interpreter for the extension.
- If the file is directly executable (has the executable bit on Unix, or is a native
.exeon Windows), it is run directly. - If the file has a shebang line, the interpreter is parsed from the shebang.
CGI scripts receive REMOTE_USER and AUTH_TYPE only when used alongside http-basicauth. For related configuration, see Static file serving, URL rewriting, and HTTP headers and CORS.
Environment variables
Ferron automatically sets the following CGI environment variables:
| Variable | Description |
|---|---|
SERVER_SOFTWARE | Always Ferron. |
SERVER_NAME | Server hostname. |
SERVER_ADDR | Local server address. |
SERVER_PORT | Server port. |
REQUEST_METHOD | HTTP method. |
REQUEST_URI | Original request URI. |
QUERY_STRING | Query string (empty string if none). |
PATH_INFO | Path info extracted from the request. |
SCRIPT_NAME | The script path relative to the document root. |
AUTH_TYPE | Authentication type from the Authorization header (e.g., Basic, Bearer). |
REMOTE_USER | Authenticated username, if available. |
SERVER_ADMIN | Server administrator email (from admin_email configuration). |
HTTPS | Set to on when the connection is encrypted. |
Additional variables set by environment directives override any automatically set variables with the same name.
Trace context injection
When a trace context exists for the request, Ferron automatically injects W3C Trace Context headers (traceparent, tracestate, and baggage) into the CGI request. These headers are mapped to standard CGI environment variables:
| Header | CGI environment variable |
|---|---|
traceparent | HTTP_TRACEPARENT |
tracestate | HTTP_TRACESTATE |
baggage | HTTP_BAGGAGE |
This enables end-to-end distributed tracing with CGI scripts. For example, a PHP script running with the official OpenTelemetry SDK for PHP can read these headers to create child spans automatically.
No per-module configuration is needed. Trace context injection is controlled globally by whether a trace context exists — see Tracing configuration for details on enabling trace generation and sampling.
Observability
Logs
WARN: logged when a CGI script exits with a non-zero status and produces output on stderr. The message includes the trimmed stderr content.
Structured logs
| Description (summary) | Level | Attributes |
|---|---|---|
| CGI errors on stderr | WARN | error.message (string) — trimmed stderr output from the CGI process |
Metrics
| Metric | Type | Attributes | Description |
|---|---|---|---|
ferron.cgi.requests | Counter | — | Number of CGI requests processed |
ferron.cgi.failures | Counter | error.type ("non_zero_exit_code"), ferron.cgi.exit_code | Number of CGI requests that failed with a non-zero exit code |
ferron.cgi.process.duration | Histogram | — | Duration of CGI process execution |
ferron.cgi.stderr_errors | Counter | — | Number of CGI requests that produced non-empty stderr output |
Examples
PHP with a custom PHP-CGI binary
example.com {
root /srv/www/example
cgi {
extension ".php"
interpreter ".php" php-cgi -c /etc/php/8.2/cgi/php.ini
}
}Multiple interpreters with environment variables
example.com {
root /srv/www/app
cgi {
extension ".rb"
interpreter ".rb" ruby
interpreter ".py" python3
environment "RUBY_VERSION" "3.3"
environment "PYTHONUNBUFFERED" "1"
}
}Disabling the default PHP interpreter
example.com {
root /srv/www/example
cgi {
interpreter ".php" false
}
}This allows PHP files to be handled via shebang lines or direct execution instead.
Using cgi-bin with additional extensions
example.com {
root /srv/www/example
cgi {
extension ".php"
environment "APP_ENV" "production"
}
# /srv/www/example/cgi-bin/handler.py is treated as CGI
# /srv/www/example/scripts/script.php is also treated as CGI
# (because of the ".php" extension directive)
}