Configuration: HTTP response body replacement

This page documents the replace, replace_last_modified, and replace_filter_types directives for modifying HTTP response bodies on the fly. Ferron applies string replacement after all content generation (static files, proxy responses, and so on) and before caching. Clients receive the modified content, and the cache stores it.

Directives⁠#

String replacement⁠#

  • replace <search: string> <replacement: string>
    • This directive specifies a string to search for in the response body and its replacement. You can define multiple replace directives. Ferron applies them in order. Default: none

Block options⁠#

OptionArgumentsDescriptionDefault
once<bool>When true, Ferron replaces only the first time the searched string appears.false

Configuration example:

example.com {
    # Replace all occurrences
    replace "old-company-name" "new-company-name"

    # Replace only the first occurrence
    replace "http://old-domain.com" "https://new-domain.com" {
        once
    }
}
Note
  • Ferron applies multiple replace directives in order. Later replacements operate on the output of earlier ones.
  • The once option defaults to false (replace all occurrences). Use once true to replace only the first.

Simple replacement⁠#

example.com {
    replace "foo" "bar"
}

Ferron replaces every time foo appears in the response body with bar.

Tip

If Ferron does not apply replacements, verify that you disabled HTTP compression for the affected responses. For JSON replacement, add application/json to replace_filter_types.

Replace only first occurrence⁠#

example.com {
    replace "old" "new" {
        once
    }
}

Ferron replaces only the first time old appears in the response body. Later appearances remain unchanged.

Chained replacements⁠#

example.com {
    replace "foo" "bar"
    replace "bar" "baz"
}

Ferron applies the replacements in order. A response body containing foo and foo becomes bar and bar after the first replacement. It becomes baz and baz after the second. Note that the second replacement also affects the output of the first.

MIME type filtering⁠#

  • replace_filter_types <mime-type: string>...
    • This directive specifies which response MIME types Ferron processes for string replacement. The filter can be a specific MIME type (like text/html) or a wildcard (*) to process all responses. Default: replace_filter_types "text/html"

Configuration example:

example.com {
    replace_filter_types "text/html" "text/css" "application/javascript"

    replace "old" "new"
}

Wildcard filter⁠#

example.com {
    # Process all response types
    replace_filter_types "*"

    replace "footer-old" "footer-new"
}

Default behavior⁠#

When replace_filter_types is not configured, Ferron processes only text/html responses:

example.com {
    # Only text/html responses are modified
    replace "old" "new"
}

Last-Modified header handling⁠#

  • replace_last_modified <preserve: bool>
    • This directive specifies whether Ferron preserves the Last-Modified response header when it modifies the body. When false, Ferron removes the Last-Modified header from responses that undergo replacement. Default: replace_last_modified false

Configuration example:

example.com {
    replace_last_modified

    replace "old" "new"
}

HTTP compression interaction⁠#

String replacement requires you to disable HTTP compression for the affected responses. When a response has a Content-Encoding header, the data is already compressed with gzip, brotli, or another algorithm. Ferron skips the replacement to avoid corrupting the compressed data.

If you need to replace strings in responses that gzip, brotli, or another algorithm would compress, you must disable compression:

example.com {
    # Disable static file compression
    compressed false

    # Disable dynamic content compression
    dynamic_compressed false

    # Now replacement can work safely
    replace "old" "new"
}
Note

If you enable compression and the algorithm compresses a response, Ferron silently skips the replacement and emits a ferron.replace.skipped_compressed metric.

Pipeline position⁠#

The replace stage runs:

  • After the dynamic compression stage (to make sure the data is not compressed)
  • Before the HTTP cache stage (so cached content is already replaced)

This ordering makes sure that string replacement operates on raw, uncompressed response bodies. It also makes sure that the modified content is what gets stored in the cache.

Observability⁠#

Metrics⁠#

MetricTypeAttributesDescription
ferron.replace.replacements_appliedCounterNoneResponses successfully modified by replacement rules
ferron.replace.skipped_compressedCounterNoneResponses skipped due to Content-Encoding header (compressed data)
ferron.replace.skipped_mimeCounterNoneResponses skipped due to MIME type mismatch

Trace spans⁠#

The response replacement stage sets the following attributes on its ferron.stage.http_replace span:

AttributeTypeDescription
ferron.replace.appliedboolWhether Ferron applied the replacement.
ferron.replace.skip_reasonstringReason Ferron skipped the replacement, when applicable (for example, compressed_body, unsupported_mime).