Reverse Proxy with Dynamic Upstream URL Path

1. The problem I’m having:

I am desperately trying to setup custom user domains to map to full path url of my product. (and I’m very new to Caddy)
For example: User types in the browser a url with subdomain that they control, and sees a page that is hosted elsewhere with a full path url.

https://blog.userdomain.comhttps://app.myproduct.com/user/blog-123/hello-world

I understand that reverse_proxy upstream addresses cannot contain paths or query strings and I should use rewrite and this is something I am unable to figure out.

There is one nuance, the path/segment blog-123 will need to be dynamically retrieved from a backend/storage based on the host. To make that happen I put a NodeJS/Express server that Caddy calls to retrieve that dynamic piece of information, but from then on I am unable to route the user the page using NodeJS/Express. I’m getting series of issues when I try that as I need to handle each request browser makes, like js, css, fonts, assets etc. I think I’m approaching this incorrectly.

One other thing I tried was just to retrieve the dynamic id/path in NodeJS/Express side and return that back to Caddy as a header to then use it to rewrite the full url, which I’m failing to do so as well.

Overall I’m a bit lost, I’m not even sure if I’m trying the impossible or what am I missing to make this happen.

Another thought I am having is to put the NodeJS/Express server first, retrieve the dynamic value and then pass it onto Caddy, but I am afraid I’ll have issues eventually with on_demand_ssl as Caddy will be the second layer in line.

2. Error messages and/or full log output:

I don’t necessarily have error messages, I’m just unable to figure out the approach.

When I try NodeJS to proxy all the requests, I get mime types errors, though irrelevant I assume.

Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.

3. Caddy version:

v2.9.1 h1:OEYiZ7DbCzAWVb6TNEkjRcSCRGHVoZsJinoDR/n9oaY=

4. How I installed and ran Caddy:

brew install caddy

a. System environment:

macOS 15.3.2
Eventually I’ll use Docker but I’m trying to make this work end to end locally first before I move on.

b. Command:

caddy run

c. Service/unit/compose file:

Not using Docker yet.

d. My complete Caddy config:

I’m sorry I don’t have a config that adds any value, I’m trying to figure out what I am supposed to do.

5. Links to relevant resources:

Take a look at rewrite to see if it might help with what you’re trying to do - or at least part of it.

For example:

https://blog.userdomain.com {
    reverse_proxy https://app.myproduct.com {
        rewrite /user/blog-123/hello-world{uri}
    }
}

You’d need to adjust the rewrite directive to fit your situation.

1 Like

Hello, thank you for your reply!

I’m trying this to test it out using my localhost, accessing via http://localhost in my browser and I all see is a white page.

:80 {
	reverse_proxy https://caddyserver.com {
		rewrite /support{uri}
	}
}

What curl returns

➜  ~ curl http://localhost -v

* Host localhost:80 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:80...
* Connected to localhost (::1) port 80
> GET / HTTP/1.1
> Host: localhost
> User-Agent: curl/8.7.1
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 200 OK
< Content-Length: 0
< Date: Fri, 21 Mar 2025 17:19:38 GMT
< Server: Caddy
< Server: Caddy
< 
* Connection #0 to host localhost left intact

Removing rewrite and adding header_up, loads the page as expected.

:80 {
	reverse_proxy https://caddyserver.com {
		header_up Host {upstream_hostport}
	}
}

Rewrite works when used inside handle, not sure why it didn’t work for me when nested in reverse_proxy

:80 {
	handle / {
		rewrite * /support
	}

	reverse_proxy https://caddyserver.com {
		header_up Host {upstream_hostport}
	}
}

And eventually even if this were to work, coming back to my original issue, I need to dynamically retrieve an id from a server based on the host. I think the problem starts when I proxy the requests to a backend/nodejs server to and try to return to Caddy so that it continues its flow.

This:

:80 {
	reverse_proxy https://caddyserver.com {
		rewrite /support{uri}
	}
}

is not the same as this:

:80 {
	handle / {
		rewrite * /support
	}

	reverse_proxy https://caddyserver.com {
		header_up Host {upstream_hostport}
	}
}

That would actually be this:

:80 {
	reverse_proxy https://caddyserver.com {
		rewrite /support
		header_up Host {upstream_hostport}
	}
}

Notice the missing {uri}. When you had {uri} in there and did this:

curl http://localhost -v

your curl request sent http://localhost/ to the server. That trailing / is what got assigned to {uri}. So in the end, you were requesting https://caddyserver.com/support/, but that page doesn’t exist - it’s just https://caddyserver.com/support.

That’s why I said you’d need to adjust the rewrite directive to fit your situation. In your case, that means removing {uri}.

And you’re absolutely right - adding

header_up Host {upstream_hostport}

is always a good idea because if the remote server hosts multiple sites on the same IP:PORT, you might end up on the wrong one.

You might need to write your own Caddy plugin for that.

1 Like

Please don’t test against our server

1 Like