It would be nice if Caddy could use a different proxy upstream dependent on the host that is requested.
eg.
proxy / http://{host}.internal.somedomain
in Nginx we do this by using server_name ~^(?<subdomain>.+)\.domain\.com$; and proxy_pass http://$subdoain.int.domain.com;
Not sure how hard it is to add this.
Thanks for your question, Maartje. Try this:
host1.com {
proxy / http://host1.com.internal.somedomain
}
host2.com {
proxy / http://host2.com.internal.somedomain
}
Much simpler I think, no?
Not in our usecase. We use it to proxy dynamic subdomains (client's username) to their specific internal ip for a container where a server runs.
It could be possible to enable placeholders for upstream addresses, we'll have to look into that! Anyone is welcome to look into this, not sure I'll have time right now.
I can try, if nobody minds.
Was just thinking the same, never worked with the Caddy codebase but i imagine the upstream values just have to be passed through some parser. Will take a look myself later
@vchimishuk didn't see your reply, sorry. Feel free to try!
I've been able to add outreq.Host = replacer.Replace(outreq.Host) which should do what I think it has to. But it seems on the setup net/url parses the URL where it returns the error that it doesn't like the {}. Not sure how to solve that issue
Seems you will have to create a new func like NewDynamicHostReverseProxy similar to NewSingleHostReverseProxy and call it instead at https://github.com/mholt/caddy/blob/master/caddyhttp/proxy/upstream.go#L152 if uh.Name contain {host}.
Ok I added that and parse the URL in there (with a dirty strings.Replace 😛 to get it parsed). The code runs but still parse http://{host}:8080: invalid character "{" in host name. Seems like it still parses it before passing it on. Will keep on looking.
Pushed my work (https://github.com/meyskens/caddy) if anybody spots my mistake you'll get many love.
You have to update https://github.com/mholt/caddy/blob/master/caddyhttp/proxy/upstream.go#L152 to call NewDynamicHostReverseProxy too.
Something like:
if strings.Contains(uh.Name, "{host}") {
uh.ReverseProxy = NewDynamicHostReverseProxy(uh.Name, uh.WithoutPathPrefix)
} else {
baseURL, err := url.Parse(uh.Name)
if err != nil {
return nil, err
}
uh.ReverseProxy = NewSingleHostReverseProxy(baseURL, uh.WithoutPathPrefix)
}
if u.insecureSkipVerify {
uh.ReverseProxy.Transport = InsecureTransport
}
@tpng sorry, wrote exactly that but forgot to add that to Git. My bad. Will push it.
Pushed, even with only uh.ReverseProxy = NewDynamicHostReverseProxy(uh.Name, uh.WithoutPathPrefix) I had the same issue.
Nevermind, I didn't notice ./build.bash didn't replace the binary in /bin. It works fine now!
What will probably happen is we will release 0.9.1 without this change, since it's quite invasive, and will need some time to test it before the first release including it.
hi,
it would be great to have other placeholders too. not only {host} but also {port}, {scheme} and some more.
@ulrichSchreiner Good news is that if one request placeholder is supported, all of them are.
For those following along at home, we've decided that the proxy middleware would probably best be redesigned to support dynamic upstreams rather than hacking it into the staticUpstream type.
A lot of thoughts and efforts went into building proxy capabilities of Caddy and especially in the recent releases. It proved how important and useful simplicity and universality for many use cases it has. Thank you to Caddy creator and contributors for that.
I want to bring to attention that providing a way for dynamic hostnames (or other means for automatic configuration of up stream servers) combined with Caddy's "wildcard" tls allows the most effortless and convenient way to set up an arbitrary number of web services each in it's own VM or container and served using single public ipv4! That would be truly exciting news for self-hosters.
@yura8 can you provide a little more detail of what specifically you would like to see.
@tobya
Dedicated server with 1 public IP. Install Proxmox on it. Then install Caddy on the host node or inside a container inside the hypervisor. Use Caddy as a transparent proxy for containers/KVMs with NextCloud, RSS, Plex, Seafile, Koel, Calibre, Wger, et cetera. One container for each application with it's own webserver and database.
Each time new VM with a new application would be spin up in Proxmox Caddy will acquire certificate for new subdomain-hostnode (tv.example.com) and transparently proxy all ports needed for this application without virtualized application and/or it's webserver being aware of it. Automagically :)
✅ Obtaining the certificate dynamically can already be done
🔳 But proxying to a dynamic backend is what requires a redesign of the Proxy middleware; the type is called "StaticUpstream" because it is static, not dynamic.
DynamicUpstream would be neat. I read the corresponding PR where you stated that redesign of middleware would be required and I posted here to inquire if such changed happened since last year's discussion or not.
Just throwing it out there, this feature would be very popular and useful. Kudos to all Go developers, hope this will be implemented someday.
Can someone post a full nginx config that expresses what they're trying to accomplish?
This is one use case proxing something like http://example.com/console/hostname1/* to http://hostname1:1588/*
https://github.com/instantbox/instantbox-frontend/blob/master/nginx.conf#L19-L31
Interesting, how would health checks and load balancing work since the backends are dynamic and varied?
Heathcheck may be tough. But load balance can be done through multiple "to" keys if needed.
(hostname1).server1.example.com
(hostname1).server2.example.com
I have this partially implemented at home. It sort of works. Will need to test it more / polish it up.
Okay, I just went ahead and finished it in 1e31be8.
Caddy 2 can proxy to different remotes by using any valid placeholders (variables) in the dial property of an upstream:
"dial": "{http.request.host}:80"
is one example. Or, to use a specific part of the domain name:
"dial": "{http.request.host.labels.2}.service.internal:80"
In other words, you'll define a single upstream and its address is evaluated dynamically at request-time. This has implications for health checks, of course, so be wise when configuring health checks, because in this case a single upstream can actually be many arbitrary number of hosts/ports.
Go try it out!
does this work in the caddyfile? or do you have to do this specifically in the json file?
Yes this does work in Caddyfile.
For next time, please ask your usage questions on the Caddy community forums. By replying to old issues on GitHub, you're sending notifications to everyone who was watching this issue.
Most helpful comment
For those following along at home, we've decided that the proxy middleware would probably best be redesigned to support dynamic upstreams rather than hacking it into the staticUpstream type.