Multiple redirects from subpath to subdomain + new path to old subpath

1. The problem I’m having:

I have very little experience with Caddy, and I have trouble setting up a couple of redirects. I hope someone can give me some help so I can better understand how to do such redirects in the future on my own.

Problem: I have an old PHP forum that runs on www.realitymod.com/forum/
It uses various php files such as index.php, viewtopic.php etc. as well as query params to provide content to frontend users.
We are now replacing that forum with an entirely different one, and I want to use that opportunity to get rid of that annoying subpath we had so far.
As the new forum is also PHP based and uses the same or at least similar files to serve content, I want a “catch all” redirect, but I also need to handle specific paths differently.

Once that is all sorted, I would like to - at least for the transition period- be able to access the old /forum path under a different path, e.g. realitymod.com/oldforum or something, serving its files via the proper fastcgi.

I would also appreciate some pointers on how you would approach writing these redirects. I have a hard time conceptualizing the directives with all the possible placeholders and directive order, probably due to lack of experience, I find it difficult to follow “step by step” how Caddy solves my attempts at giving it directives.

3. Caddy version:

v2.8.4 h1:q3pe0wpBj1OcHFZ3n/1nl4V4bxBrYoSoab7rL9BMYNk=

4. How I installed and ran Caddy:

Installed the ubuntu caddy package, then set up the alternative for a custom binary that includes the dns-cloudflare module.

a. System environment:

Ubuntu 20.04 adm64, systemd, “bare metal”

c. Service/unit/compose file:

[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target

d. My complete Caddy config:

Caddyfile

{
	log default {
		output file /var/log/caddy/caddy.log
		format json {
			time_format iso8601
		}
	}

	servers {
		# Limit to Cloudflare
		trusted_proxies static private_ranges 173.245.48.0/20 103.21.244.0/22 103.22.200.0/22 103.31.4.0/22 141.101.64.0/18 108.162.192.0/18 190.93.240.0/20 188.114.96.0/20 197.234.240.0/22 198.41.128.0/17 162.158.0.0/15 104.16.0.0/13 104.24.0.0/14 172.64.0.0/13 131.0.72.0/22 2400:cb00::/32 2606:4700::/32 2803:f800::/32 2405:b500::/32 2405:8100::/32 2a06:98c0::/29 2c0f:f248::/32
		trusted_proxies_strict
		client_ip_headers CF-Connecting-IP
	}

	acme_dns cloudflare <api_token>
}


import sites/*.conf

homepage.conf:

realitymod.com {
	redir https://www.{host}{uri}
}

www.realitymod.com {
	root * /var/www/vhosts/realitymod.com/html

	basic_auth /forum/* {
		<creds>
	}

	handle_path /forum/* {
		php_fastcgi unix//run/php/php7.1-fpm-vb_forum.sock {
			# This ensures subfolders/files that are standalone are not run through the homepage index.php
			try_files /forum/{path} /forum/index.php
		}

		# file_server
		# rewrite {path} /forum/index.php
	}

	@redirects {
		not file
		# Exclude any paths that should NOT be treated by index.php. This includes anything that is located in subfolders.
		# Please don't add any more subfolders. Use subdomains and separate users/focus instead in the future to avoid monoliths.
		not path /prspy* /mapgallery* /manual* /forum*
	}

	php_fastcgi unix//run/php/php8.3-fpm-homepage.sock {
		# This ensures subfolders/files that are standalone are not run through the homepage index.php
		try_files @redirects /index.php?page2={file}
	}
	file_server {
		hide lcp .git .gitignore *.log
	}

	log {
		output file /var/log/caddy/homepage.log
		format json {
			time_format iso8601
		}
	}

	@blocked {
		path /config.php /common.php *.log *.htaccess
	}
	respond @blocked 403 {
		close
	}

	# Mapgallery is supposed to allow CORS so other, community-based services can use it for their own purposes.
	handle /mapgallery/* {
		header {
			Access-Control-Allow-Origin "*"
			Access-Control-Allow-Headers "Authorization"
			Access-Control-Allow-Methods "GET"
			Access-Control-Expose-Headers "Content-Security-Policy, Location"
			Access-Control-Max-Age "600"
		}
	}
	# redir: Deny request and send a new URI string that the client should visit (== shows new URI in address bar).
	# rewrite: Accept the request and 'redirect' it internally. Will not cause a change in the adress bar.
	# redir always occurs before rewrite and is final.
	# Rewrites are applied in order they appear here. If a rewrite directive matches, following rewrites will NOT apply anymore.
	# https://caddyserver.com/docs/caddyfile/directives#directive-order
	redir /latest-changelog /forum/showthread.php?t=152797 301

	# www.realitymod.com/forum/member.php?u=123  -->
	# forum.realitymod.com/memberlist.php?mode=viewprofile&u=123
	# Need a "catchall" that redirects everything like this example, unless specifics exist such as directly above
	# www.realitymod.com/forum/viewtopic.php?t=123 -->
	# forum.realitymod.com/viewtopic.php?t=123
	redir /forum/member.php* https://forum.realitymod.com/memberlist.php?mode=viewprofile&{query} 301
	redir /forum* https://forum.realitymod.com{uri} permanent
	redir /forum/donate /forum/misc.php?do=donate 301

	rewrite /rss* https://www.realitymod.com/forum/external.php?forumids=380
	@media-screenshots path_regexp /media/screenshots-([0-9]+)$
	rewrite @media-screenshots /index.php?page2=screenshots&p={re.1}
	rewrite @redirects /index.php?page2={file}
}

phpbb.conf

# The phpbb.realitymod.com subdomain will be decomissioned very soon without redirects. It was only set up for internal testing.
# We only want to use forum.realitymod.com using forward.
phpbb.realitymod.com forum.realitymod.com {
	root * /opt/phpbb/
	php_fastcgi unix//run/php/php8.3-fpm-phpbb.sock

	@serve path *.js *.css *.jpg *.png *.gif *.ico *.svg *woff2 *.ttf *.eot
	file_server @serve

	log {
		output file /var/log/caddy/caddy-phpbb.log
		format json {
			time_format iso8601
		}
	}

	@blocked {
		# These are critical core files. Never expose them directly.
		path /config.php /common.php
	}
	respond @blocked 403

	basic_auth {
		<while in dev, won't be later in prod>
	}
}

You can use the GitHub - WeidiDeng/caddy-cloudflare-ip plugin instead of listing out all of Cloudflare IPs. Better long term cause it will keep the list updated automatically.

If you also need private_ranges, you can use GitHub - fvbommel/caddy-combine-ip-ranges: IP prefix module for Caddy that combines the output of other IP prefix modules. to combine both static and cloudflare IP sources together.

This doesn’t work. Matchers like @redirects cannot be applied to subdirectives unless they specifically document that they can. You would need to do the rewrite by hand before php_fastcgi.

The directive order is listed here Caddyfile Directives — Caddy Documentation, and there’s an explanation below that of how the sorting algorithm works.

If you want to see the sorting result, you can run caddy adapt -p to see the JSON config which is after sorting. They run in the order they appear in the JSON config, top-down. Keep in mind some directives are terminal (write a response and end the middleware chain) and some are not and allow fallthrough.

I’m not sure which redirect you’re having trouble with, you’ll need to elaborate if you’re having an issue with something specific.

Thanks for the reply!

The directive order itself is not the issue, I found that link myself (which is why I added it my homepage.conf comments as a quick lookup). The confusion is how multiple redirects that would match interact with each other. The adapt -p is very helpful for that, thanks.

I think the problem I have is that I don’t know exactly what the placeholders stand for, an example for each of them would be great.
It would be even better if I could somehow get an easy “preview” of how various sample URLs would get redirected.

The concrete problems I’m struggling with right now are the redirects in homepage.conf:

# Need specific redirects for individual requests such as this one
	# www.realitymod.com/forum/member.php?u=123  -->
	# forum.realitymod.com/memberlist.php?mode=viewprofile&u=123
	# Need a "catchall" that redirects everything else like this example, unless specifics exist such as directly above
	# www.realitymod.com/forum/viewtopic.php?t=123 -->
	# forum.realitymod.com/viewtopic.php?t=123
	redir /forum/member.php* https://forum.realitymod.com/memberlist.php?mode=viewprofile&{query} 301
	redir /forum* https://forum.realitymod.com{uri} permanent
	redir /forum/donate /forum/misc.php?do=donate 301

Currently, above config leads to a redirect of www.realitymod.com/forum/member.php?u=123 to https://forum.realitymod.com/member.php , dropping the query params entirely.

That looks like it should work to me. How are you testing it? Use curl -v to make the request and show what you get.

Only comment is the * on the matcher is probably not useful. It’s a path matcher, so unless you have URLs like /forum/member.php/foo/bar you could just match /forum/member.php exactly. Path matchers don’t match the query part of the URL.

You can set yourself up with a quick .sh script that runs curl -v then grep the Location: header value to compare it to your expected result.

Which ones are you confused about? The docs kinda assume you understand the parts of an HTTP request, you can learn how that works on MDN etc.

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