Reverse proxy not working as expected

1. Caddy version (caddy version):

2.5.1

2. How I run Caddy:

I run caddy from a docker-compose file together with 2 other containers on my local machine, no custom docker network settings.

a. System environment:

Docker on macOS (latest)

b. Command:

Paste command here.

c. Service/unit/compose file:

version: "3.9"

services:

  pt:
    build:
      context: .
      dockerfile: "Dockerfile.pt"
    ports:
      - 8081:8081
    volumes:
      - pt_data:/var/opt/pt
    environment:
        - SERVER_URL=http://pt:8081/

  ptm:
    depends_on:
      - pt
    build:
      context: .
      dockerfile: "Dockerfile.ptm"
    ports:
      - 8080:8080
    volumes:
      - ptm_data:/opt/pt-magic
    environment:
        - SERVER_URL=http://ptm:8080/

  caddy:
    image: caddy:latest
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - $PWD/Caddyfile:/etc/caddy/Caddyfile
      - $PWD/site:/srv
      - caddy_data:/data
      - caddy_config:/config

volumes:
  caddy_data:
    external: true
  caddy_config:
  pt_data:
    external: true
  ptm_data:
    external: true

d. My complete Caddyfile or JSON config:

:80 {
	reverse_proxy /pt* pt:8081
	reverse_proxy /ptm* ptm:8080
	file_server
}

3. The problem I’m having:

I have those 2 containers (named pt and ptm) together with caddy in a docker-compose file (see above).
What I want to do is reach those containers via localhost/pt and localhost/ptm in my browser (they provide web interfaces)
The issue being: unless I edit my /etc/hosts-file to enable subdomains for localhost (e.g. pt.localhost pointing to 127.0.0.1) I only get blank pages. For the /pt* reverse proxy it even forwards to /login but as this is no longer /pt* it doesn’t show the content of the page.
If I remove the matcher, everything works as expected but without matching it only works for one of the containers of course. How can I tell caddy to keep the domain when forwarding so that the matcher does not break, leaving me with a blank page?

4. Error messages and/or full log output:

curl -v localhost/pt:

➜  ~ curl -v localhost/pt
*   Trying 127.0.0.1:80...
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /pt HTTP/1.1
> Host: localhost
> User-Agent: curl/7.79.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 Found
< Content-Length: 0
< Date: Fri, 27 May 2022 13:17:31 GMT
< Expires: Thu, 01 Jan 1970 00:00:00 GMT
< Location: http://localhost/login
< Server: Caddy
< Set-Cookie: JSESSIONID-PT_8081=node0l0ihijudy6dtqpoi11tik53a15.node0; Path=/; Expires=Fri, 03-Jun-2022 13:17:31 GMT; Max-Age=604800; HttpOnly
< Vary: Origin
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
<
* Connection #0 to host localhost left intact

caddy log for curl:

{"level":"debug","ts":1653657451.420064,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":"pt:8081","total_upstreams":1}

{"level":"debug","ts":1653657451.4341471,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"pt:8081","duration":0.013557167,"request":{"remote_ip":"172.21.0.1","remote_port":"61860","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/pt","headers":{"X-Forwarded-Host":["localhost"],"Accept":["*/*"],"User-Agent":["curl/7.79.1"],"X-Forwarded-For":["172.21.0.1"],"X-Forwarded-Proto":["http"]}},"headers":{"Date":["Fri, 27 May 2022 13:17:31 GMT"],"Vary":["Origin","Access-Control-Request-Method","Access-Control-Request-Headers"],"Set-Cookie":[],"Location":["http://localhost/login"],"Content-Length":["0"],"Expires":["Thu, 01 Jan 1970 00:00:00 GMT"]},"status":302}

caddy log for browser request:

{"level":"debug","ts":1653657365.338175,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":"pt:8081","total_upstreams":1}

{"level":"debug","ts":1653657365.352468,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"pt:8081","duration":0.014207792,"request":{"remote_ip":"172.21.0.1","remote_port":"61850","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/pt","headers":{"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15"],"X-Forwarded-For":["172.21.0.1"],"Accept-Encoding":["gzip, deflate"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],"Cookie":[],"X-Forwarded-Host":["localhost"],"Upgrade-Insecure-Requests":["1"],"Accept-Language":["de-DE,de;q=0.9"],"X-Forwarded-Proto":["http"]}},"headers":{"Date":["Fri, 27 May 2022 13:16:05 GMT"],"Vary":["Origin","Access-Control-Request-Method","Access-Control-Request-Headers"],"Location":["http://localhost/login"],"Content-Length":["0"]},"status":302}

{"level":"debug","ts":1653657365.3651812,"logger":"http.handlers.file_server","msg":"sanitized path join","site_root":".","request_path":"/login","result":"login"}

{"level":"debug","ts":1653657365.368171,"logger":"http.log.error","msg":"{id=tu7v9seqi} fileserver.(*FileServer).notFound (staticfiles.go:511): HTTP 404","request":{"remote_ip":"172.21.0.1","remote_port":"61850","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/login","headers":{"Cookie":[],"Accept-Language":["de-DE,de;q=0.9"],"Connection":["keep-alive"],"Upgrade-Insecure-Requests":["1"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15"],"Accept-Encoding":["gzip, deflate"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"]}},"duration":0.002992667,"status":404,"err_id":"tu7v9seqi","err_trace":"fileserver.(*FileServer).notFound (staticfiles.go:511)"}

5. What I already tried:

I verified that it works as expected when removing the matcher which at the same time takes away my use case.

6. Links to relevant resources:

Your upstream app would need to be aware of the subpath. See this article which explains the problem:

2 Likes

Thank you for the response! Appreciate it. I’ll read up. :slight_smile: