Configuration: syntax and file structure

This page covers the Ferron configuration file format, some blocks and directives, and the configuration resolution model.

Ferron configuration files⁠#

Ferron uses .conf (or .ferron) files. The config-ferronconf adapter parses them. A configuration file starts top-level statements that define global blocks, host blocks, matchers, and snippets.

# Uncomment to include additional configuration files
#include "/etc/ferron/conf.d/**/*.ferron"

{
    runtime {
        io_uring
    }

    tcp {
        listen "::"
    }
}

match api_request {
    request.uri.path ~ "/api"
    request.method in "GET,POST"
}

snippet common_http {
    http {
        protocols h1 h2
    }
}

example.com {
    use common_http

    tls {
        provider manual
        cert "{{env.TLS_CERT}}"
        key "{{env.TLS_KEY}}"
    }
}

Top-level statements⁠#

A configuration file can contain the contents at the top level:

  • Global blocks: { ... } for server-wide settings
  • Host blocks: <host-pattern> { ... } for virtual host configuration
  • Match blocks: match <name> { ... } for reusable conditional matchers
  • Snippet blocks: snippet <name> { ... } for reusable directive groups
  • Include directives: include "path.conf" loads a file with more configuration.

Value types⁠#

Ferron configuration supports these value types:

  • Strings: plain ([example.com]) or quoted (["example.com"])
  • Integers: 80, 443, 1000
  • Floats: 3.14
  • Booleans: true, false
  • Interpolated strings: {{env.TLS_CERT}} reads from environment variables
  • Duration strings: 30m, 1h, 90s, 1d

Flags (boolean directives)⁠#

Some directives accept boolean values. For convenience, you can write these as flags with no configured arguments, which is equivalent to true:

example.com {
    # This is a bare flag, equivalent to setting the value to true
    directory_listing
}
example.com {
    # To disable, use false explicitly
    directory_listing false
}

This shorthand can be useful for simple on/off toggles where the intent is clear.

Duration strings⁠#

Some directives accept duration values. Ferron supports these formats:

SuffixUnitExampleResult
h or HHours12h, 1H12 hours
m or MMinutes30m, 30M30 minutes
s or SSeconds90s, 90S90 seconds
d or DDays1d, 1D1 day
(none)Seconds (default)1212 seconds

Plain numbers without a suffix count as seconds.

Raw string literals⁠#

Raw string literals (r"...") handle escape processing: use them for values that contain regex patterns or similar content. Raw strings process no escape sequences. Backslashes stay literal:

match api_request {
    request.uri.path ~ r"^/api/v1(?:/|$)"
}

Without raw strings, the same regex would need escaped backslashes:

match api_request {
    request.uri.path ~ "^/api/v1(?:/|$)"
}
Warning

Invalid escape sequences in strings (for example, \z, \$) cause parse errors. Use raw strings (r"...") if you need literal backslashes in values like regexes.

Note

Raw strings do not support interpolation ({{...}}). Use standard strings if you need variable substitution.

Line continuations⁠#

Split long directives across multiple lines with \ at the end of the line. Indent the continuation:

example.com {
    # This is merely a directive example
    example_proxy http://localhost:3000 \
      http://localhost:3001
}

Line continuations can include a trailing comment:

example.com {
    # This is merely a directive example
    example_proxy http://localhost:3000 \ # first backend
      http://localhost:3001
}

Comments⁠#

Comments in ferron.conf files start with #. Multiline comments are not supported (you have to start every line with #).

Host blocks⁠#

Host blocks appear only at the top level. Supported selectors include:

Selectors:

  • example.org: hostname tree
  • *.example.org: wildcard hostname
  • 127.0.0.1: IP-based host
  • [2001:db8::1]: IPv6 address
  • http example.org: explicit protocol
  • http example.org:8080: explicit protocol and port
  • tcp *:5432: TCP listener
  • *:8080: explicit port (entire server at port 8080)
  • *: wildcard (entire server at default ports)

Defaults:

  • If you omit the protocol, it defaults to http.
  • For HTTP host blocks, if you omit the port, Ferron treats it as 80.

If you specify a hostname (for example, a domain name) and give no explicit port, Ferron starts two listeners. One runs on the default HTTP port (80). One runs on the default HTTPS port (443) with automatic ACME TLS.

Includes and snippets⁠#

  • include "path.conf" at the top level loads another config file relative to the exposed file.

include "path.conf" at the top level loads another config file relative to the current file.

  • snippet <name> { ... } defines a reusable block of directives.
  • use <snippet-name> inside a block expands that snippet in place.
Note
  • Top-level file includes and snippet expansion work differently.
  • Parse error rejects include cycles and snippet cycles.
  • A snippets block may span a set of hosts.

Configuration model⁠#

Ferron resolves configuration in layers, once per request, before pipeline stages run:

  1. Global configuration from { ... } provides startup and runtime settings.
  2. Ferron selects matching host blocks by local IP and hostname.
  3. Ferron merges matching location blocks. Matching is prefix only. The longest match wins.
  4. Ferron merges matching if and if_not blocks.
Note
  • location uses prefix matching. /api matches /api and /api/users.
  • A longer, more specific location wins over a less specific one.
  • All expressions in a match block use AND semantics.
  • A rewrite in a later pipeline stage does not trigger a new round of location matching. See Request pipeline order.

Inheritance and behavior⁠#

Directives inherit from outer blocks to inner blocks. A location block starts with the host block values. An if block starts with the enclosing host or location values.

  • When a directive appears in both a parent block and a child block, the child value wins for that block.
  • When a directive appears only in the parent block, the child block inherits it.
  • Some directives accumulate across layers instead of overriding, for example rewrite rules and map entries. Their reference pages state the behavior.
  • Handler and backend selectors are host-isolated: proxy, proxy_concurrent_conns, fcgi, fcgi_php, cgi, scgi, forward_proxy, root, index, basic_auth, rate_limit_backend, static-file tunables (compressed, etag, file_cache_control, precompressed, mime_type, directory_listing, disable_symlinks), client_ip_from_header, trace_id_header, https_redirect, and trailing_slash_redirect do not inherit from the wildcard * host into a named host, but they still inherit from global defaults and a location block still inherits them from its own host. For example, * { proxy ... } does not cause a named host that only sets root to reverse-proxy. Each directive page states its exact behavior.
Note

When validation and runtime behavior differ, the directive pages explain that.

Note

Duration strings accept suffixes like 30m, 1h, 90s, 1d. Numbers without suffix count as seconds. Boolean directives are bare flags (equivalent to true) or explicitly false when disabling.

Hot-reload⁠#

Ferron .conf configuration files support hot reload. It detects a change and reloads the configuration gracefully.

ferron run --config-params 'watch=1;file=ferron.conf' --config-adapter ferronconf

Configuration drift hints⁠#

When hot reload is off (the default), Ferron still detects a changed config source file that has not loaded. This is configuration drift. It signals that the running configuration may not match the file on disk.

Ferron detects drift with periodic mtime checks (no full parse). It emits a WARN log and sets the ferron.admin.config_drift gauge to a value of 1. When the configuration reloads and drift resolves, Ferron emits an INFO log and resets the metric to 0.

Ferron enables drift hints by default. To disable:

ferron run --config-params 'code=1;file=logs.conf' --config-adapter ferronconf

The GET /status endpoint of the admin API also reports this state in the config_drift and config_drift_hints_enabled fields.

See also⁠#