Wildcard virtual domains with wildcard roots

I am trying to use a wildcard domain, *.archive.dev, with a wildcard root directive, root C:\caddy\Archive\*. Essentially, any folder in Archive/ would be accessible as a subdomain and the root would use the same domain - for example: test.archive.dev uses the root Archive/test/.

Is this achievable? The setup in the Caddyfile shares every setting except root and virtual host.

This is my Caddyfile, where settings are specific to the Grav CMS:

# Archive
*.archive.dev:80 {
	root C:\caddy\Archive\*
	log logs/access.log
	errors logs/error.log
	gzip
	startup php-cgi -b 127.0.0.1:9000 &
	fastcgi / 127.0.0.1:9000 {
		ext .php
		split .php
		index index.php
	}
	rewrite {
		regexp .*
		ext /
		to /index.php?_url={uri}
	}
	
	# Begin - Security
	# deny all direct access for these folders
	rewrite {
		r       /(.git|cache|bin|logs|backups|tests)/.*$
		status  403
	}
	# deny running scripts inside core system folders
	rewrite {
		r       /(system|vendor)/.*\.(txt|xml|md|html|yaml|php|pl|py|cgi|twig|sh|bat)$
		status  403
	}
	# deny running scripts inside user folder
	rewrite {
		r       /user/.*\.(txt|md|yaml|php|pl|py|cgi|twig|sh|bat)$
		status  403
	}
	# deny access to specific files in the root folder
	rewrite {
		r       /(LICENSE.txt|composer.lock|composer.json|nginx.conf|web.config|htaccess.txt|\.htaccess)
		status  403
	}
	## End - Security

	# global rewrite should come last.
	rewrite {
		to  {path} {path}/ /index.php?_url={uri}
	}
}
1 Like

Hello Ole, welcome to the Caddy forum! :slight_smile:

There is no way to do what you’re trying to do currently (I wanted to do something similar.)

If folders in Archive are static, my solution might work for you.

Put your primary config in a file Caddyfile-base.
Then use the following script to generate the Caddyfile:

#!/bin/bash
CF=/etc/caddy/Caddyfile # The output file
cat Caddyfile-base > $CF
printf "\n# THE FOLLOWING WAS GENERATED ON $(date)\n\n" >> $CF
for dir in `ls "/var/caddy/data/www/sites"`; do
    printf "$dir {\n    root /var/www/sites/$dir\n}\n" >> $CF
done

You can convert that to PowerShell pretty easily.

1 Like

Are you using Caddy 0.9 (beta)? Using wildcards in site addresses is not supported before that.

But the root folders are static still in 0.9; this is required because some directives need to know the root of the site when the server starts. (We’re looking at ways to make dynamic roots possible in the future. In fact, see #901 which I opened up because of your question.)

1 Like

It turns out I am using 0.8.3, will upgrade shortly. I replied to #901.

I upgraded to 0.9b2. Following the clarifications on #901, rewrite can seemingly achieve this. Any thoughts on how?

Using root C:\caddy\Archive\{host} with *.archive.dev:80 does not work, it returns No such site at 127.0.0.1:80. I assume it has something to do with a rewrite-directive for root, but I’m not quite sure how that would look.

What are you trying to access that says no such host?

I tried older.archive.dev which has an old version (in Archive/older/) which works with the config in the original post, ie. set up for the CMS. With a more plain setup, say archive.dev and root at Archive/, it works as expected.

{host} matches the whole hostname. So older.archive.dev would try files in Archive/older.archive.dev/

I renamed the folder to Archives/older.archive.dev/, thus the config now starts with:

*.archive.dev:80 {
	root C:\Caddy-0.9b2\Archive\{host}

But it still returns No such site at 127.0.0.1:80 (I also made sure the hosts-file has 127.0.0.1 older.archive.dev set).

You can’t use placeholders in root, but you can do something like this:

*.archive.dev:80

root C:\Caddy-0.9b2\Archive
rewrite {
    to {host}{uri}
}

I think?

Hmm, I tried a more minimal test case:

Caddyfile

*.archive.dev:80 {
  root C:\Caddy-0.9b2\Archive\
  rewrite {
    to {host}/{uri}
  }
}

Folder
Archive/
Archive/older.archive.dev/

Hosts
127.0.0.1 older.archive.dev

Index.html (in Archive/older.archive.dev/)
<h1>Older</h1>

But the error persists.

Oops, the rewrite should probably be {host}{uri} (without the slash in the middle) to be most correct since {uri} starts with a / (but Caddy cleans it up and removes the extra slash).

But that won’t fix your problem.

Strange, though, your Caddyfile works for me just fine with the same setup. Keep in mind that {host} will contain the port if the request’s hostname has a port in it too. So if :80 appears in the Host of the request, it will try loading Archive/older.archive.dev:80/... which is probably not what you want.

Maybe it works for me because Chrome is not sending the request with :80 in the Host of the request?

Something really odd is happening with Beta2. Given this config (on Windows 10 64-bit):

# PHPInfo
localhost:80 {
	log logs/access.log
	errors logs/error.log
	gzip
	startup php-cgi -b 127.0.0.1:9000 &
	fastcgi / 127.0.0.1:9000 php {
		index index.php
	}
}

# Archive
archive.dev:80 {
	log logs/access.log
	errors logs/error.log
	root C:\Caddy-0.9b2\Archive
}

I do get the No such site errors. However, if only one of the configs are present - ie., either PHPInfo or Archive - then the respective domains render as expected. I had not considered this before, as the Caddyfile seems very standard.

I see it now. Caddy 0.9 has a whole new virtualhosting mechanism so it’s probably a bug. Will work on it!

Thanks for your patience and experimentation.

1 Like

Okay, turns out this is a mix of how Caddy serves localhost and how your hosts file maps archive.dev.

As a special case, Caddy binds to 127.0.0.1 when it serves localhost, rather than empty host which accepts connections from the outside. But the hosts file is saying to map archive.dev to 127.0.0.1 so the listener that doesn’t serve archive.dev of course can’t find a site with that address.

Anyway, I could remove this special case so that localhost also binds to empty host (in this case, :80) but this would open a socket for the whole world, potentially, to access (even if all they can see is just “No such site”).

I’m not immediately sure of another solution to this… what do you think?

1 Like

Well the open socket to the world is also an open socket to the network, no? That is always handy for testing locally, but yes, it is sort of a risk. However, like any other server-software, is it not expected of the server administrator to protect the server from unwanted access by layers around the server itself?

For years this was the risk of XAMPP, that by letting it run as a service it would be exposed beyond the local computer, and so you had to make sure it was either intended to be open or you locked it down through your router or local system.

I think for Windows users the generic binding, to empty host, is acceptable because one of the most comfortable things about Caddy is that it is not forced as a service and then only runs when you spin it up through CLI - hence the risk of external access is already low, and should for any other case be much lower by protecting the connection through firewall or the like.

1 Like

I’ll probably eliminate this special-case handling; I can see now why it might be a problem.

Same problem would be encountered if you specified bind 127.0.0.1 in your localhost site. That’d be expected though, right? Since you explicitly specify it, and you also set your hosts file to route archive.dev to 127.0.0.1…

Yes, as localhost is by default bound to 127.0.0.1 on Windows. Or perhaps it is interpreted the other way around? Regardless, the function of binding archive.dev or any other domain to 127.0.0.1 is just to tell the system that traffic should be handled locally - and so any local server which is set to handle the domain will accept the traffic.

1 Like

And the way the routing works here, I believe, is that the most specific socket takes the connection. So if you have a listener on :80 and 127.0.0.1:80 and a request is made for archive.dev, the OS will send the request to 127.0.0.1:80 with Host: archive.dev in the header. Since there is a listener listening on 127.0.0.1:80, it gets all the loopback requests, including those for localhost or otherwise that may not have specifically been bound to 127.0.0.1.

So I can’t tell if yours is the edge case or the other way around. :smile: I’m wondering if messing with the hosts file is more the edge case (although I’m with ya, I do it all the time).

Can you try something for me? In the config for archive.dev can you put bind "" and see if that fixes anything for ya? I’ve got a few things going on, you’ll probably have a chance to try before me.

Tested now with this Caddyfile:

# PHPInfo
localhost {
	tls off
	log logs/access.log
	errors logs/error.log
	gzip
	startup php-cgi -b 127.0.0.1:9000 &
	fastcgi / 127.0.0.1:9000 php {
		index index.php
	}
}

# Archive
archive.dev {
	bind ""
	tls off
	log logs/access.log
	errors logs/error.log
	root C:\Caddy-0.9b2\Archive
}

But that still gives a No such site at 127.0.0.1:2015 (also tried with bind "" under localhost). Remove either the localhost-config or the archive.dev-config and each runs as expected.

Besides editing the hosts-file, is there a native way of getting Windows to detect custom domains such as archive.dev? I’ve only ever gone that route as it keeps development enviroments that much neater and it’s never let me down with Apache (which lets me down in so many other ways).