Configuration: CGI support
This page documents the cgi directive for configuring CGI (Common Gateway Interface) support in Ferron. 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"
}
}You can write the cgi block as a boolean flag to enable CGI with all defaults. You can also write it 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 Ferron treats as a CGI script. Unlike cgi-bin directory matching, the file does not need to be executable. You can specify this directive multiple times. | none |
Configuration example:
example.com {
cgi {
extension ".php"
extension ".rb"
}
}- Ferron matches extensions case-insensitively.
- Ferron treats files with these extensions as CGI scripts wherever they appear in the file tree.
- This complements
cgi-bindirectory matching. Ferron always treats files insidecgi-binas 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, for example .php). Later arguments form the interpreter command line. Pass false as the second argument to disable the interpreter for that extension. You can specify this directive 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 you pass
falseas the second argument, Ferron clears the interpreter list. The file must then run directly, for example 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, Ferron parses scripts with a shebang line (for example,
#!/usr/bin/env python3). It derives the interpreter from the shebang. - On Windows, Ferron executes
.exefiles directly. It usescmd /cfor.batand.cmdfiles. Ferron parses shebang scripts in the same way as on Unix.
environment#
| Nested directive | Arguments | Description | Default |
|---|---|---|---|
environment | <name: string> <value: string> | This directive sets a CGI environment variable passed to the interpreter process. Ferron resolves values with the same interpolation syntax as other directives. You can specify this directive multiple times. | none |
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.
- Ferron automatically removes the
Proxyheader from the request to prevent the httpoxy vulnerability. - Ferron always sets
SERVER_SOFTWARE,SERVER_NAME, andSERVER_PORTautomatically. - Ferron also sets
REQUEST_URI,QUERY_STRING,PATH_INFO,SCRIPT_NAME,AUTH_TYPE,REMOTE_USER, andSERVER_ADMIN.
- Ferron always removes the
Proxyheader to prevent the httpoxy vulnerability. - If a CGI script exits with a non-zero status, Ferron returns a
500 Internal Server Errorresponse. Ferron trims the stderr output and logs aWARNmessage. - Ferron sets the working directory to the directory that contains 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 you enable CGI and do not set an explicit index directive, Ferron adds default index file names. By default, Ferron checks index.html, index.htm, and index.xhtml in order.
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 applies only when you do not set an explicit index directive. With your own index directive, Ferron uses it 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 work for .bat, .cmd, and other script files. Ferron executes native .exe files directly.
CGI script locations#
Ferron treats a request 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 Ferron finds a matching file, it looks for an interpreter in this priority:
- Custom
interpreterdirective matching the file extension. - Built-in default interpreter for the extension.
- If the file is directly executable (executable bit on Unix, or native
.exeon Windows), Ferron runs it directly. - If the file has a shebang line, Ferron parses the interpreter 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 (for example, Basic, Bearer). |
REMOTE_USER | Authenticated username, if available. |
SERVER_ADMIN | Server administrator email (from admin_email configuration). |
HTTPS | on when the server encrypts the connection. |
Additional variables set by environment directives override any automatically set variables with the same name.
Trace context injection#
When the request has a trace context, Ferron injects W3C Trace Context headers into the CGI request. These headers (traceparent, tracestate, and baggage) map 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. It then creates child spans automatically.
You need no per-module configuration. Trace context injection depends globally on 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 | None | 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 | None | How long a CGI process runs |
ferron.cgi.stderr_errors | Counter | None | Number of CGI requests that produced non-empty stderr output |
Access log fields#
The CGI module contributes the following fields to the HTTP access log line:
| Field | Type | Description |
|---|---|---|
ferron.cgi.script_path | string | Path to CGI script executed. |
ferron.cgi.exit_code | int | CGI process exit code. |
Trace spans#
The CGI stage sets the following attributes on its ferron.stage.cgi span:
| Attribute | Type | Description |
|---|---|---|
http.response.status_code | int | HTTP status code returned by the CGI script. |
ferron.cgi.script_path | string | Path to the CGI script. |
ferron.cgi.exit_code | int | Exit code of the CGI process. |
error.type | string | Error type on failure, enabling trace UI highlighting. |
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 Ferron to handle PHP files 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)
}