# Configuration: syntax and file structure

> Source: https://ferron.sh/docs/configuration/fundamentals/syntax

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.

```ferron
# 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`:

```ferron
example.com {
    # This is a bare flag, equivalent to setting the value to true
    directory_listing
}
```

```ferron
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:

| Suffix     | Unit              | Example      | Result     |
| ---------- | ----------------- | ------------ | ---------- |
| `h` or `H` | Hours             | `12h`, `1H`  | 12 hours   |
| `m` or `M` | Minutes           | `30m`, `30M` | 30 minutes |
| `s` or `S` | Seconds           | `90s`, `90S` | 90 seconds |
| `d` or `D` | Days              | `1d`, `1D`   | 1 day      |
| (none)     | Seconds (default) | `12`         | 12 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:

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

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

```ferron
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:

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

Line continuations can include a trailing comment:

```ferron
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](https://ferron.sh/docs/configuration/fundamentals/request-pipeline.md).

## 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.

```bash
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:

```bash
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

- [Conditional and variables](https://ferron.sh/docs/configuration/fundamentals/conditionals.md)
- [Formatting a configuration](https://ferron.sh/docs/configuration/fundamentals/formatting.md): `ferron-fmt` for formatting
- [Routing and URL processing](https://ferron.sh/docs/configuration/routing/url-processing.md) (`location`, `if`, `if_not`)
- [Directives](https://ferron.sh/docs/configuration/server/core-directives.md)