Struggling with redirection expressions

1. The problem I’m having:

Hi folks,
I want to redirect /tag/<foo> to /tags/<foo> and /post/<bar> to /posts/<bar>
in lighttpd i was able to do this like so:

    url.redirect = (
      "^/post/(.*)" => "/posts/$1",
      "^/tag/(.*)" => "/tags/$1",

i have tried various options, such as:

redir /tag/* /tags/

this doesn’t include suffix string, just always goes to /tags/, so i guess i need to use regular expressions?

redir path_regexp old ^/tag/(.*)$ /tags/{re.old.1}

this results in this error: Feb 18 18:53:28 arch1 caddy[135705]: Error: adapting config using caddyfile: parsing caddyfile tokens for 'redir': Not a supported redir code type or not valid integer: 'old', at /etc/caddy/Caddyfile:57

i have tried

	# redir path_regex ^/tag/(.*) /tags/

which results in Feb 18 18:55:17 arch1 caddy[135724]: Error: adapting config using caddyfile: parsing caddyfile tokens for 'redir': Not a supported redir code type or not valid integer: '^/tag/(.*)', at /etc/caddy/Caddyfile:56

finally i came up with this config:

    	@old path_regexp old ^/tag/(.*)$
	redir @old /tags/{re.old.1}

    	@oldposts path_regexp postp ^/post/(.*)$
	redir @oldposts /posts/{re.oldp.1}

this one works fine for tags, however for posts, it always redirects to /posts ignoring the regex match. eg curl output:

> GET /post/practical-fault-detection-redux-next-generation-alerting-now-as-presentation/ HTTP/2
> Host:
> User-Agent: curl/8.6.0
> Accept: */*
< HTTP/2 302 
< alt-svc: h3=":443"; ma=2592000
< location: /posts/
< server: Caddy
< content-length: 0
< date: Sun, 18 Feb 2024 18:45:06 GMT
* Connection #0 to host left intact

2. Error messages and/or full log output:

had some trouble copypasting logs properly. but hopefully the issue is obvious enough

3. Caddy version:


4. How I installed and ran Caddy:

pacman -S caddy # i use arch, btw

a. System environment:

I use arch linux, btw.

b. Command:

it auto starts

c. Service/unit/compose file:

[root@arch1 http]# caddy version
[root@arch1 http]# locate caddy.service
[root@arch1 http]# cat /usr/lib/systemd/system/caddy.service
# caddy.service
# For using Caddy with a config file.
# Make sure the ExecStart and ExecReload commands are correct
# for your installation.
# See for instructions.
# WARNING: This service does not use the --resume flag, so if you
# use the API to make changes, they will be overwritten by the
# Caddyfile next time the service is restarted. If you intend to
# use Caddy's API to configure it, add the --resume flag to the
# `caddy run` command or use the caddy-api.service file instead.

Description=Caddy web server
Documentation= systemd-networkd-wait-online.service

ExecStartPre=/usr/bin/caddy validate --config /etc/caddy/Caddyfile
ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile --force
ExecStopPost=/usr/bin/rm -f /run/caddy/admin.socket

# Do not allow the process to be restarted in a tight loop. If the
# process fails to start, something critical needs to be fixed.

# Use graceful shutdown with a reasonable timeout


# Hardening options
ReadWritePaths=/var/lib/caddy /var/log/caddy /run/caddy


d. My complete Caddy config:

# The Caddyfile is an easy way to configure your Caddy web server.
# The configuration below serves a welcome page over HTTP on port 80.
# To use your own domain name (with automatic HTTPS), first make
# sure your domain's A/AAAA DNS records are properly pointed to
# this machine's public IP, then replace the line below with your
# domain name.
	# Restrict the admin interface to a local unix file socket whose directory
	# is restricted to caddy:caddy. By default the TCP socket allows arbitrary
	# modification for any process and user that has access to the local
	# interface. If admin over TCP is turned on one should make sure
	# implications are well understood.
	admin "unix//run/caddy/admin.socket"

(cors) {
	@origin header Origin {args[0]}
	header @origin Access-Control-Allow-Origin "{args[0]}"
	header @origin Access-Control-Allow-Methods "OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE"
} {
	import cors
	root * /srv/http/tapas
	reverse_proxy /waitlist 127.0.01:6000
} {
	root * /srv/http/tapas
} {
	reverse_proxy {
		header_up Host
		header_down -Access-Control-Allow-Origin
	import cors
} {
	root * /srv/http/dieter-dev
	# failures:
	# rewrite /tag/* /tags/ # doesn't include suffix str, just goes to /tags/
	redir path_regex ^/tag/(.*) /tags/ # ??
	#redir path_regexp old ^/tag/(.*)$ /tags/{re.old.1} # startup error
	# this works, but is a bit clunky
	#    	@old path_regexp old ^/tag/(.*)$
	#	redir @old /tags/{re.old.1}

	#    	@oldposts path_regexp postp ^/post/(.*)$
	#	redir @oldposts /posts/{re.oldp.1}

	root /files/ /srv/http/files

import /etc/caddy/conf.d/*

5. Links to relevant resources:

That’s because your placeholder isn’t using the right name. You named the regexp postp but you used {re.oldp.1}

This is invalid syntax. Handler directives (like redir) take an optional matcher as the first argument. That’s how conditions are applied to them.

You can’t put named matchers inline, only simple path matchers.

d’oh! thanks…

oh… i tried to follow Request matchers (Caddyfile) — Caddy Documentation,
where this matcher is listed under " Standard matchers"
the limitation that i can’t use them was not clear, since they’re mentioned with the other matchers (that the redir docs link to)

is there a more simple, elegant way of expressing what i’m trying to do?

Yeah, the assumption is that you’ve read the Syntax part at the top before any of the matchers. The docs for each matcher only shows how to use that matcher in isolation.

I’m working on updates to the docs which should clarify this soon.

No, that is the simplest way.

Though there is a change I’m going to make shortly which will make it shorter in the next release, by letting you omit the regexp name:

@oldposts path_regexp ^/post/(.*)$
redir @oldposts /posts/{re.1}

Matchers are designed this way, because otherwise it would be impossible to know where the matcher ends and where the handler’s arguments begin, if we allowed full matchers to be inline. It would be impossible to parse.

I will miss lighttpd’s elegant single line syntax, but ok, not a big price to pay. thanks :slight_smile:

Wouldn’t it actually be simpler to do redir /tag/* /tags/{uri} or am I missing something here?

Matching does not strip the matched portion. So that would result in /tags/tag/whatever

I see. And a rewrite?

It’s the same. Matchers don’t change the request, they’re just fancy “if statements”.

You need to use the uri or rewrite directives to transform the current URL.

The regexp approach above uses (.*) to extract the part of the URL after the prefix, then reuses that using a placeholder in the redir.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.