Configuration: HTTP headers and CORS

This page documents the header and cors directives for configuring response header manipulation and Cross-Origin Resource Sharing (CORS) handling.

Directives⁠#

The header directive manipulates response headers before sending to the client. The directive supports three forms:

SyntaxEffect
header +Name "value"Add header (appends, allows duplicates)
header -NameRemove all instances of the header
header Name "value"Replace header (removes existing, sets new value)

Header values support interpolation with {{...}} syntax.

Configuration example:

example.com {
    header +X-Client-IP "{{remote.ip}}"
    header X-Powered-By "Ferron"
    header -Server
}

Interpolation variables⁠#

VariableDescription
{{remote.ip}}IP address of the client
{{remote.port}}Port of the client
{{server.ip}}Listening IP address of the server
{{server.port}}Listening port of the server
{{request.host}}The matched hostname
{{request.scheme}}http or https
{{env.NAME}}Environment variable NAME
Note

For header interpolation, remote.ip and server.ip automatically canonicalize IPv4-mapped IPv6 addresses to IPv4. See Conditionals and variables and HTTP host directives for details.

Info

For the complete variable reference, see Conditionals and variables.

Ferron leaves unresolved variables as {{name}} in the output.

cors⁠#

The cors directive configures Cross-Origin Resource Sharing behavior.

example.com {
    cors {
        origins "https://example.com" "https://app.example.com"
        methods GET POST PUT DELETE
        headers "Content-Type" "Authorization"
        credentials
        max_age 86400
        expose_headers "X-Custom-Header"
    }
}
Nested directiveArgumentsDescriptionDefault
origins<string>...Allowed origins. Use "*" to allow all. Accepts variable interpolations (for example {{request.header.origin}} for Origin header reflection).none (CORS disabled)
methods<string>...Allowed HTTP methods for preflight.none
headers<string>...Allowed request headers for preflight.none
credentials<bool>Allow credentials (cookies, auth headers).false
max_age<number>Preflight cache duration in seconds.none
expose_headers<string>...Headers exposed to the browser in responses.none

Behavior⁠#

  1. Preflight handling: When an OPTIONS request includes Origin and Access-Control-Request-Method headers, the module returns 204 No Content with the appropriate CORS response headers.

  2. Response headers: When enabled, the module adds CORS headers to all responses (including error responses). The headers include Access-Control-Allow-Origin, Access-Control-Allow-Credentials, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Max-Age, Access-Control-Expose-Headers, and Vary: Origin.

Origin matching⁠#

  • If origins contains "*", the module allows any origin and sets Access-Control-Allow-Origin to *.
  • Otherwise, the module compares the incoming Origin header against the list. If it matches, the module echoes the header back. If it does not match, the module adds no CORS headers.

Configuration example: allow all origins

api.example.com {
    cors {
        origins "*"
        methods GET POST
        headers "Content-Type" "Authorization"
        credentials false
        max_age 3600
    }
}

Configuration example: specific origins with credentials

api.example.com {
    cors {
        origins "https://app.example.com" "https://admin.example.com"
        methods GET POST PUT DELETE OPTIONS
        headers "Content-Type" "Authorization" "X-Request-ID"
        credentials
        max_age 86400
        expose_headers "X-Total-Count" "X-Page"
    }
}
Note

If CORS headers do not appear in responses, verify that you set origins. Ferron disables CORS by default if origins is empty.

Best practices⁠#

ferron doctor reports the following best-practice check for directives on this page.

  • cors { credentials true } with origins "*". Allowing credentials with wildcard origins defeats browser same-origin protection. Use explicit trusted origins when you allow credentials.

Observability⁠#

Trace spans⁠#

The headers stage sets the following attributes on its ferron.stage.headers span:

AttributeTypeDescription
ferron.headers.setintNumber of response headers set.
ferron.headers.unsetintNumber of response headers removed.