Configuration: core directives
This page documents directives that belong in top-level global blocks:
{
# global directives here, for example:
console_log
}- These directives affect startup and listener construction, not per-request routing.
- The
config-ferronconfmodule (for.conffiles) or theconfig-jsonmodule (for.jsonfiles) handles configuration file parsing.
For observability-specific configuration, see Observability and logging. For per-host HTTP settings, see HTTP host directives. For admin API security hardening, see Security considerations.
Directives#
Default ports#
-
default_http_port <port: integer | false>- This directive sets the default HTTP port when a host block does not specify a port. The value must be a positive integer ≤ 65535, or
falseto disable the default HTTP listener. Default:default_http_port 80
- This directive sets the default HTTP port when a host block does not specify a port. The value must be a positive integer ≤ 65535, or
-
default_https_port <port: integer | false>- This directive sets the default HTTPS port used for HTTP-to-HTTPS redirects and URL generation. The value must be a positive integer ≤ 65535, or
falseto disable the default HTTPS listener. Default:default_https_port 443
- This directive sets the default HTTPS port used for HTTP-to-HTTPS redirects and URL generation. The value must be a positive integer ≤ 65535, or
Configuration example:
{
default_http_port 8080
default_https_port 8443
}- When a host does not specify an explicit port, Ferron starts an HTTP listener on
default_http_portand an HTTPS listener ondefault_https_port. - The redirect stage constructs
https://URLs using this port (omitting it when the value is443). - Setting
default_http_port falsedisables the automatic HTTP listener, anddefault_https_port falsedisables the automatic HTTPS listener and HTTP-to-HTTPS redirects. - If you set both directives to
false, host blocks without explicit ports create no listeners, and Ferron logs a warning.
Disable default HTTP listener (HTTPS only):
{
default_http_port false
}Disable both default listeners (only explicit ports work):
{
default_http_port false
default_https_port false
}Runtime#
io_uring <bool>- This directive turns on
io_uringfor the server when it is available. Enablingio_uringcould improve performance for static file serving involving large files or reverse proxying involving very high concurrency. If initialization fails, Ferron falls back toepolland logs a warning. Default: disabled
- This directive turns on
Configuration example:
{
runtime {
io_uring
}
}Network and listeners#
-
listen <address: string>- This directive sets the bind addresses for TCP listeners. It accepts either IP addresses or full socket addresses. If you use a socket address, its port must match the HTTP port that Ferron starts. If you just need to change default HTTP ports, use
default_http_portanddefault_https_portinstead. Default:[::]:<http-port>
- This directive sets the bind addresses for TCP listeners. It accepts either IP addresses or full socket addresses. If you use a socket address, its port must match the HTTP port that Ferron starts. If you just need to change default HTTP ports, use
-
send_buf <size: integer>- This directive sets the TCP send buffer size. It must resolve to a non-negative integer at runtime. Default: OS default
-
recv_buf <size: integer>- This directive sets the TCP receive buffer size. It must resolve to a non-negative integer at runtime. Default: OS default
-
backlog <size: integer>- This directive sets the maximum number of pending connections allowed on the listener socket. Default:
-1(Windows, *BSD, macOS),4096(others)
- This directive sets the maximum number of pending connections allowed on the listener socket. Default:
-
multipath <bool>- This directive turns on Multipath TCP (MPTCP) for the listener. MPTCP allows a single TCP connection to use multiple network interfaces at the same time, improving throughput and resilience. When enabled, Ferron attempts to create an MPTCP socket. If the kernel lacks MPTCP support or MPTCP is off, Ferron logs a warning and falls back to standard TCP. Default: disabled
Configuration example:
{
tcp {
listen "127.0.0.1"
send_buf 65536
recv_buf 131072
multipath
}
}Unix domain sockets (Unix only)#
unix <path: string> { ... }- This directive creates a Unix domain socket listener. You can repeat it to listen on multiple sockets. Each directive accepts a filesystem path and an optional block with
backlog,mode,owner, andgroupsubdirectives. The path can be absolute (/run/ferron.sock) or relative (ferron.sock,tmp/ferron.sock). Relative paths are resolved against the server’s current working directory and canonicalized when the target exists, otherwise kept as-is. Default: none
- This directive creates a Unix domain socket listener. You can repeat it to listen on multiple sockets. Each directive accepts a filesystem path and an optional block with
The unix directive also supports the subdirectives below:
| Directive | Arguments | Description | Default |
|---|---|---|---|
backlog | <size: integer> | Maximum pending connections | -1 (unlimited) |
mode | <mode: string|integer> | File permissions for the socket | OS default respecting umask |
owner | <user: string|integer> | Owner of the socket | Process user |
group | <group: string|integer> | Group of the socket | Process group |
When at least one unix directive is present, Ferron disables all TCP and QUIC (for HTTP/3) listeners and serves HTTP only over Unix sockets. Host blocks are then served via the Unix sockets no matter what are their port assignments, meaning Host header and TLS SNI still select the virtual host. To serve both TCP and Unix at once, do not use unix (run a separate TCP terminator or reverse proxy in front of the Unix socket instead).
unixis available only on Unix-like systems. The validator rejects it on Windows.- Socket paths must be shorter than 108 bytes (
sun_pathlimit), must not containNUL, and must not already exist as a non-socket file. - Parent directories are created automatically. Stale socket files from a previous run are removed if they are sockets; otherwise startup fails.
Configuration example:
{
unix "/run/ferron.sock" {
backlog 128
mode "0660"
owner ferron
group ferron
}
# Relative path (resolved against the server's working directory)
# (for example, /var/log/ferron when using the default Docker image).
unix "ferron.sock" {
mode "0660"
}
# Multiple sockets (see above)
unix "/run/ferron-api.sock" {
mode "0660"
}
}PROXY protocol#
protocol_proxy [bool]- This directive turns on PROXY protocol v1/v2 parsing for incoming TCP connections. When enabled, Ferron reads the PROXY protocol header from HAProxy or similar load balancers before processing the HTTP request. The client and server addresses from the PROXY header replace the actual socket addresses while the connection is open. Default:
protocol_proxy false
- This directive turns on PROXY protocol v1/v2 parsing for incoming TCP connections. When enabled, Ferron reads the PROXY protocol header from HAProxy or similar load balancers before processing the HTTP request. The client and server addresses from the PROXY header replace the actual socket addresses while the connection is open. Default:
Ferron supports both PROXY protocol v1 (text-based) and v2 (binary). If parsing fails, Ferron rejects the connection and logs an error.
Reverse proxy connection limits#
concurrent_conns <limit: integer>- This directive sets the global maximum number of concurrent TCP connections maintained in the reverse proxy keep-alive connection pool. All hosts that use the
proxydirective share the limit. Unix socket connections have no limit. Default:concurrent_conns 16384
- This directive sets the global maximum number of concurrent TCP connections maintained in the reverse proxy keep-alive connection pool. All hosts that use the
Configuration example:
{
concurrent_conns 10000
}Admin API#
The admin block configures the built-in administration endpoints. If the admin block is absent, Ferron disables the admin API entirely.
-
listen <address: string>(admin-api)- This directive sets the socket address for the admin HTTP listener. Default:
listen 127.0.0.1:8081
- This directive sets the socket address for the admin HTTP listener. Default:
-
auth_token <token: string>(admin-api)- This directive sets a bearer token for authenticating admin API requests. When set, clients must send
Authorization: Bearer <token>header. The/healthendpoint is always exempt from authentication (required by load balancers and orchestrators). Default: none (authentication disabled)
- This directive sets a bearer token for authenticating admin API requests. When set, clients must send
-
health [bool](admin-api)- This directive enables the
GET /healthendpoint. It returns200 OKor503 Service Unavailableduring shutdown. Default:health true
- This directive enables the
-
status [bool](admin-api)- This directive enables the
GET /statusendpoint. It returns JSON with uptime, active connections, request count, and reload count. Default:status true
- This directive enables the
-
config [bool](admin-api)- This directive enables the
GET /configendpoint. It returns the current effective configuration as sanitized JSON (sensitive fields redacted). Default:config true
- This directive enables the
-
reload [bool](admin-api)- This directive enables the
POST /reloadendpoint. It triggers a configuration reload equivalent to SIGHUP. Default:reload true
- This directive enables the
-
reload_get [bool](admin-api)- This directive enables the
GET /reloadendpoint. It returns the current reload status. Default:reload_get true
- This directive enables the
-
runtime [bool](admin-api)- This directive enables the
GET /runtimeendpoint. It returns runtime information such as thread count and io_uring status. Default:runtime true
- This directive enables the
Configuration example:
{
admin {
listen "127.0.0.1:8081"
auth_token "my-secret-token"
health true
status true
config true
reload true
reload_get true
runtime true
}
}The /config endpoint redacts sensitive directive names, such as: key, cert, private_key, password, secret, token, ticket_keys, bearer, passwd, htpasswd.
Observability#
The observability block configures per-host event sinks for logging and metrics. Multiple observability directives for the same host accumulate event sinks.
provider <name: string>(observability-consolelog,observability-logfile)- This directive sets the observability provider name. Required when observability is enabled through the block form. Supported providers:
console(observability-consolelog),file(observability-logfile). Default: none
- This directive sets the observability provider name. Required when observability is enabled through the block form. Supported providers:
Configuration example:
example.com {
observability {
provider console
}
}provider console#
The bundled console provider (observability-consolelog) takes no additional subdirectives and writes supported observability events to Ferron logs.
provider file#
The bundled file provider (observability-logfile) writes observability events to specified log files.
| Additional subdirective | Arguments | Description | Default |
|---|---|---|---|
access_log | <string> | File path for access log output. | none |
error_log | <string> | File path for error log output. | none |
format | <string> | Access log formatter name (text or json). | text |
error_format | <string> | Application log formatter name (text or json). | text |
access_log_rotate_size | <number> | Maximum access log file size in bytes before rotation. | disabled |
access_log_rotate_keep | <number> | Number of rotated access log files to keep. | none (no limit) |
error_log_rotate_size | <number> | Maximum error log file size in bytes before rotation. | disabled |
error_log_rotate_keep | <number> | Number of rotated error log files to keep. | none (no limit) |
Configuration example:
example.com {
observability {
provider file
access_log /var/log/ferron/access.log
error_log /var/log/ferron/error.log
format text
error_format json
}
}- Ferron creates log files in append mode if they do not exist.
- Ferron buffers writes and flushes them every 1 second and on shutdown.
- If you omit
access_log, Ferron ignores access events, and the same applies toerror_log. - With rotation on, Ferron renames the current log file to
<filename>.1, shifts rotated files up, and creates a new log file. - If you set
access_log_rotate_keep(orerror_log_rotate_keep) to0, Ferron deletes the log file on rotation instead of renaming it.
Observability aliases#
Ferron has shorthand directives for common observability configurations. Ferron transforms these automatically into equivalent observability blocks.
log#
The log directive is shorthand for configuring access logging with the file provider.
example.com {
# These are equivalent:
log /var/log/access.log {
format text
}
observability {
provider file
access_log /var/log/access.log
format text
}
}Examples:
example.com {
# Enable access logging with default format
log /var/log/access.log
# Enable with custom format
log /var/log/access.log {
format json
}
# Enable with log rotation (100MB max, keep 5 rotated files)
log /var/log/access.log {
access_log_rotate_size 104857600
access_log_rotate_keep 5
}
# Disable access logging
log false
}error_log#
The error_log directive is shorthand for configuring error logging with the file provider.
example.com {
# These are equivalent:
error_log /var/log/error.log
observability {
provider file
error_log /var/log/error.log
}
}Examples:
example.com {
# Enable error logging
error_log /var/log/error.log
# Enable with log rotation (50MB max, keep 3 rotated files)
error_log /var/log/error.log {
error_log_rotate_size 52428800
error_log_rotate_keep 3
}
# Enable with JSON application log formatting
error_log /var/log/error.log {
error_format json
}
# Disable error logging
error_log false
}console_log#
The console_log directive is shorthand for configuring console-based observability.
example.com {
# These are equivalent:
console_log {
format json
}
observability {
provider console
format json
}
}Admin API#
The admin API is a built-in HTTP interface for server health checks, status monitoring, configuration inspection, and reload control. It is meant for local access and debugging purposes.
Security considerations#
The admin API is a privileged control plane with full server configuration access and reload capability. It has no encryption and no authentication by default. You can enable bearer token authentication with the auth_token directive. Treat it with the same security posture as a root shell on your server.
Risks of binding to 0.0.0.0#
Setting listen "0.0.0.0:<port>" makes the admin API completely open to any client that can reach the host. Omitting the bind address defaults to all interfaces and has the same effect. This can happen accidentally in containerized environments (for example, Docker with bridge networking) or misconfigured networks.
Consequences of an open admin API:
- Denial of service: Anyone can send
POST /reloadcontinuously, causing reload loops that degrade performance. - Configuration leak:
GET /configreveals the full server configuration, including hostnames, upstream addresses, and routing rules, with sensitive values redacted. - Service disruption: Anyone can use reload with modified configuration to disable any endpoint or inject misconfigured directives.
Hardening recommendations#
-
Always bind to localhost unless you have a specific, secure reason not to:
{ admin { listen "127.0.0.1:8081" health true status true config true reload true } } -
Disable unnecessary endpoints. Only enable the endpoints you need:
{ admin { listen "127.0.0.1:8081" health true status false config false reload true } } -
Use a reverse proxy for remote access. If you need to access the admin API from a remote machine, front it with an authenticating reverse proxy. Do not bind to
0.0.0.0:Remote user → reverse proxy (auth required) → 127.0.0.1:8081 (admin API) -
Restrict network access at the infrastructure level. Use firewall rules, security groups, or VPC networking to make sure only trusted hosts can reach the admin port.
-
Monitor admin API access. Use your observability sinks to track requests to admin endpoints for anomaly detection.
-
Never expose the admin API to the public internet. If you need remote administration, use SSH tunneling:
ssh -L 8081:127.0.0.1:8081 admin@your-server # Then access http://127.0.0.1:8081 locally
API reference#
The admin API is a RESTful interface for server configuration and control. Below are the available endpoints:
GET /health#
It returns 200 OK while the server runs, or 503 Service Unavailable when shutdown starts. Suitable for load balancer and orchestration health checks.
GET /status#
Returns JSON with server metrics:
{
"uptime_sec": 12345,
"connections_active": 42,
"requests_total": 100000,
"reloads": 3,
"observability_events_dropped": 0,
"observability_event_queue_len": 0
}| Field | Description |
|---|---|
uptime_sec | Seconds since the server started. |
connections_active | Currently open TCP connections across all HTTP listeners. |
requests_total | Total HTTP requests served across all listeners. |
reloads | Number of configuration reloads. |
observability_events_dropped | Total number of observability events dropped due to backpressure. |
observability_event_queue_len | Approximate current length of the observability event queue. |
GET /config#
It returns the full effective server configuration as sanitized JSON. Ferron replaces sensitive directives (TLS keys, passwords, tokens) with "[redacted]". Useful for debugging and auditing.
GET /reload#
Returns the current reload status as JSON:
{
"last_reload_time": "2026-05-29T12:00:00Z",
"last_reload_error": null,
"active_generation": 42
}| Field | Description |
|---|---|
last_reload_time | ISO 8601 timestamp of the last reload attempt. |
last_reload_error | Error message from the last reload, or null if successful. |
active_generation | The configuration generation number currently in effect. |
POST /reload#
It triggers a configuration reload, equivalent to sending SIGHUP to the daemon process.
Returns the reload status as JSON:
{
"status": "reload_initiated",
"error": null
}| Field | Description |
|---|---|
status | "reload_initiated" if the reload is in progress, or "reload_failed" if it failed. |
error | Error message from the last reload attempt, or null if successful. |
GET /runtime#
Returns the runtime status as JSON:
{
"primary_threads": 8,
"io_uring_supported": true,
"io_uring_runtime_enabled": true
}| Field | Description |
|---|---|
primary_threads | Number of primary threads (typically equal to CPU count). |
io_uring_supported | Whether the current system supports io_uring. |
io_uring_runtime_enabled | Whether io_uring was successfully enabled at runtime. |
Best practices#
ferron doctor reports the following best-practice checks for directives on this page.
Log rotation#
logwithout rotation: File-based access logging should includeaccess_log_rotate_size(or an external log rotation policy) to prevent unbounded disk growth.error_logwithout rotation: File-based error logging should includeerror_log_rotate_size(or an external log rotation policy).
Default ports#
- Both default ports disabled: Setting
default_http_port falseanddefault_https_port falsemeans host blocks without explicit ports create no listeners. Make sure all host blocks specify explicit ports, or keep at least one default listener enabled.
PROXY protocol#
protocol_proxyenabled: PROXY protocol trusts addresses from clients. Enable it only on listeners reachable exclusively by trusted load balancers.
Admin API#
admin.listenon non-loopback address: The admin API has no encryption and no authentication by default. Useauth_tokento enable bearer token auth. Bind to a loopback address or restrict access via network controls.adminwithoutauth_token: The admin API has no authentication by default. Useauth_tokento require a bearer token on all endpoints except/healthwhen the listener is reachable from untrusted networks.
Location blocks#
- No duplicate
locationblock pathnames: Duplicate pathnames in location blocks cause the server to return an ambiguous response, so avoid them.