Unable to use rewrite with redir

Hi, I’m trying to move an old website from nginx to caddy. One of the things that needs translating are the dozens of rewrite rules that have accumulated.

The case I’m currently looking at is our URL shortener. This rewrites certain patterns such as https://small.uk/@Simon to https://realwebsite.uk/profile.php?username=Simon. In nginx, this is implemented as single rewrite rules, for example:

rewrite ^/@([\w-]+)$ https://realwebsite.uk/profile.php?username=$1 redirect;

In Caddy, I’ve tried to reproduce this using a combination of path_regexp and rewrite to rewrite the paths, then redir to finish up and redirect to the main domain. However, I’ve not been able to use the rewritten uri inside the redir directive, and it’s confusing me why this is the case.

To test, I’ve produced the following config:

http://short.local {
    @simple_matcher path_regexp /foo$
    rewrite @simple_matcher /bar

    respond http://www.local{uri}
}

Fetching this using curl gives the following response:

> GET /foo HTTP/1.1
> Host: short.local
> User-Agent: curl/8.9.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=utf-8
< Server: Caddy
< Date: Sun, 01 Jun 2025 17:59:24 GMT
< Content-Length: 20
<
http://www.local/bar

As expected, the rewrite directive has replaced foo with bar, and I can then redirect by combining the other domain and rewritten uri.

However, when I then try to redirect to this url, simply by using redir:

http://short.local {
    @simple_matcher path_regexp /foo$
    rewrite @simple_matcher /bar

    redir http://www.local{uri}
}

Instead of the Location header using the expected /bar path, it uses the original /foo path, as seen in curl:

> GET /foo HTTP/1.1
> Host: short.local
> User-Agent: curl/8.9.1
> Accept: */*
>
< HTTP/1.1 302 Found
< Location: http://www.local/foo
< Server: Caddy
< Date: Sun, 01 Jun 2025 18:02:21 GMT
< Content-Length: 0
<

Now, I don’t doubt there are other (albeit less concise) ways of achieving what I want, but before I try those, I’d like to understand why respond does what I expect, but redir doesn’t.

Version and environment info:

Caddy v2.10.0

Dockerfile:

FROM caddy:2-builder AS builder

RUN xcaddy build

FROM caddy:2

COPY --from=builder /usr/bin/caddy /usr/bin/caddy

compose.yml:

services:
  caddy:
    build: ./caddy/build
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./caddy/conf:/etc/caddy
      - caddy_data:/data
      - caddy_config:/config

volumes:
  caddy_data:
  caddy_config:

Running on Windows 10, Podman, WSL2

It’s because of the directive order - see Caddyfile Directives — Caddy Documentation

redir is ordered before rewrite.

Put these directives in a route and it is not automatically ordered.

2 Likes

Thanks. For some reason I had it in my head that redir and respond were both near the bottom.