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

> [!info]
> For `url_sanitize` interaction, see [Routing and URL processing](https://ferron.sh/docs/configuration/routing/url-processing.md#url-sanitation-and-redirects). For static file serving, see [Static file serving](https://ferron.sh/docs/configuration/content/static-files.md).

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

| Option                 | Arguments | Description                                                                        | Default |
| ---------------------- | --------- | ---------------------------------------------------------------------------------- | ------- |
| `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:**

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

#### Simple rewrite

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

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

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

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

```ferron
example.com {
    rewrite_log true
}
```

Example log output:

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

## Regex syntax

The regular expression engine used is [`regex`](https://crates.io/crates/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

| Metric                            | Type    | Attributes | Description                                                               |
| --------------------------------- | ------- | ---------- | ------------------------------------------------------------------------- |
| `ferron.rewrite.rewrites_applied` | Counter | None       | URLs successfully rewritten                                               |
| `ferron.rewrite.invalid`          | Counter | None       | Rewrite 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) | Level | Attributes                                                                                                                                  |
| --------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| URL rewritten         | INFO  | `ferron.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:

| Field                    | Type | Description                                          |
| ------------------------ | ---- | ---------------------------------------------------- |
| `ferron.rewrite.applied` | bool | Whether Ferron applied a URL rewrite to the request. |

### Trace spans

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

| Attribute                      | Type | Description                                           |
| ------------------------------ | ---- | ----------------------------------------------------- |
| `ferron.rewrite.applied`       | bool | Whether Ferron applied a rewrite rule to the request. |
| `ferron.rewrite.pattern_count` | int  | Number of rewrite rules that matched.                 |