Configuration: URL rewriting

This page documents the rewrite directive for transforming request URLs using regular expression patterns. Ferron applies rewrites in the request pipeline, before proxying or static file serving, so later stages use the rewritten URL.

Important

Rewrites do not trigger a new round of location matching. Ferron selects the location block once, on the original URL, and strips the matched prefix before rewrite rules run. Rules therefore see the location-stripped path. Use match blocks with if or if_not for regex routing. See Request pipeline order.

Info

For url_sanitize interaction, see Routing and URL processing. For static file serving, see Static file serving.

Directives⁠#

rewrite⁠#

  • rewrite <regex: string> <replacement: string>
    • This directive specifies a regular expression pattern and replacement string for URL rewriting. You can reference regex capture groups in the replacement string ($1, $2, and so on). Default: none

Block options⁠#

OptionArgumentsDescriptionDefault
last<bool>When true, stop processing further rewrite rules after this one matches.false
directory<bool>When true, apply this rule when the URL corresponds to a directory.true
file<bool>When true, apply this rule when the URL corresponds to a file.true
allow_double_slashes<bool>When true, preserve double slashes (//) in the URL instead of collapsing them.false

Configuration example:

example.com {
    rewrite "^/old-path/(.*)" "/new-path/$1"
}

Simple rewrite⁠#

example.com {
    rewrite "^/old-path/(.*)" "/new-path/$1"
}

Ferron internally rewrites all /old-path/anything requests to /new-path/anything. The client sees no redirect. The rewrite is transparent.

Tip

If you get unexpected routing behavior, check the order of the rewrite rules. Rules with last stop further processing.

Stop processing with last⁠#

example.com {
    rewrite "^/api/v1/(.*)" "/api/v2/$1" {
        last
    }
    rewrite "^/api/v2/(.*)" "/api/v3/$1"
}

Ferron rewrites /api/v1/users requests to /api/v2/users and then stops. The second rule never sees the /api/v2/ prefix.

Chained rules without last⁠#

example.com {
    rewrite "^/legacy/(.*)" "/modern/$1"
    rewrite "^/modern/(.*)" "/current/$1"
}

Ferron first rewrites a /legacy/foo request to /modern/foo. Then the second rule rewrites it to /current/foo.

File/directory-specific rules⁠#

file false and directory false skip the rule when the URL maps to a real file or directory under root. Use both guards for fallback patterns such as single-page apps and front controllers. Without the guards, a broad pattern rewrites static asset requests too, and scripts, styles, and images break.

example.com {
    root /var/www

    rewrite "^/static/(.*)" "/assets/$1" {
        file
        directory false
    }
}
Tip

Start every catch-all fallback rule with file false and directory false. Add last to stop rule processing after the fallback matches. Enable rewrite_log true while you test the guards.

rewrite_log⁠#

  • rewrite_log <bool>
    • This directive specifies whether Ferron logs each URL rewrite operation to the error log. Default: rewrite_log false

Configuration example:

example.com {
    rewrite_log true
}

Example log output:

URL rewritten from "/old-path/users" to "/new-path/users"

Regex syntax⁠#

The regular expression engine used is regex, which executes regular expressions in linear time (no support for backtracking). This means that regular expressions are executed efficiently without backtracking, mitigating potential ReDoS and catastrophic backtracking attacks.

However, this also means you cannot use syntax listed below:

  • Lookahead statements ((?= ...), (?! ...))
  • Lookbehind statements ((?<= ...), (?<! ...))
  • Backreferences to capture groups (\1)

If you have to negate a condition, you can use negated variants of some directives, such as if_not blocks instead of if, or !~ in matchers instead of ~.

The matching is case-insensitive on Windows and case-sensitive on other platforms.

Observability⁠#

Metrics⁠#

MetricTypeAttributesDescription
ferron.rewrite.rewrites_appliedCounterNoneURLs successfully rewritten
ferron.rewrite.invalidCounterNoneRewrite rules that produced an invalid path (resulting in a 400 response)

Logs⁠#

When rewrite_log is on, Ferron logs each rewrite operation to the error log at INFO level.

Structured logs⁠#

Description (summary)LevelAttributes
URL rewrittenINFOferron.rewrite.from (string) shows the original path + query string. ferron.rewrite.to (string) shows the rewritten path + query string

Access log fields⁠#

The rewrite module contributes the following field to the HTTP access log line:

FieldTypeDescription
ferron.rewrite.appliedboolWhether Ferron applied a URL rewrite to the request.

Trace spans⁠#

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

AttributeTypeDescription
ferron.rewrite.appliedboolWhether Ferron applied a rewrite rule to the request.
ferron.rewrite.pattern_countintNumber of rewrite rules that matched.