V2: Struggling with reverse_proxy in a Caddyfile

I’m trying to set up a simple reverse proxy, using a Caddyfile, but the browser receives a blank 200 response. It works when using command line arguments, but not a Caddyfile. Can someone spot my typo?

1. Caddy version (caddy version):

v2.0.0 h1:pQSaIJGFluFvu8KDGDODV8u4/QRED/OPyIR+MWYYse8=

2. How I run Caddy:

a. System environment:

Windows 2016 Server 64-bit

b. Command:

caddy_2.0.0_windows_amd64\caddy.exe reverse-proxy --from :80 --to 127.0.0.1:8888
and
caddy_2.0.0_windows_amd64\caddy.exe run

c. Service/unit/compose file:

n/a

d. My complete Caddyfile or JSON config:

The command line generates this autosave.json:

{"admin":{"disabled":true},"apps":{"http":{"servers":{"proxy":{"listen":[":80"],"routes":[{"handle":[{"handler":"reverse_proxy","transport":{"protocol":"http"},"upstreams":[{"dial":"127.0.0.1:8888"}]}]}]}}}}}

The following Caddyfile

http://localhost/ {

    reverse_proxy http://127.0.0.1:8888

}

generates the following autosave.json:

{"apps":{"http":{"servers":{"srv0":{"automatic_https":{"skip":["localhost"]},"listen":[":80"],"routes":[{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"127.0.0.1:8888"}]}]}]}],"match":[{"host":["localhost"],"path":["/"]}],"terminal":true}]}}}}}

3. The problem I’m having:

Using the Caddyfile, the browser receives a status 200, xero byte length response

4. Error messages and/or full log output:

5. What I already tried:

I started with a bigger Caddyfile, but it’s been stripped down now to the minimum. And no HTTPS.

6. Links to relevant resources:

The issue is the trailing slash at the end of your site label. This gets parsed as a path matcher which only matches /.

Path matchers are exact match in Caddy v2. If you used http://localhost/* it would work as you expect, but also if you remove the / entirely and just use http://localhost.

If you adapt your config to JSON (caddy adapt --config path/to/Caddyfile --pretty) you’ll see the difference:

With /:

{
  "apps": {
    "http": {
      "servers": {
        "srv0": {
          "listen": [
            ":80"
          ],
          "routes": [
            {
              "match": [
                {
                  "host": [
                    "localhost"
                  ],
                  "path": [
                    "/"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "127.0.0.1:8888"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "terminal": true
            }
          ],
          "automatic_https": {
            "skip": [
              "localhost"
            ]
          }
        }
      }
    }
  }
}

Without /:

{
  "apps": {
    "http": {
      "servers": {
        "srv0": {
          "listen": [
            ":80"
          ],
          "routes": [
            {
              "match": [
                {
                  "host": [
                    "localhost"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "127.0.0.1:8888"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "terminal": true
            }
          ],
          "automatic_https": {
            "skip": [
              "localhost"
            ]
          }
        }
      }
    }
  }
}
2 Likes

I thought that exactly but it’s working fine here… (I only changed the port for reverse proxy).
(I’m confused, what would make this work if it is not supposed to ?)

But if it works for @vbakke, good enough I guess. :slight_smile:

It would work fine for any request to the root, i.e. http://localhost, but any request like http://localhost/foo would not match, and would return an empty status 200 response.

Yes :sweat_smile: I just realized that. I should have tested it before commenting… meh.
Thanks!

Thank you @francislavoie! This explains a lot.
(I tried swapping reverse_proxy with file_server, and that worked, but only for the root folder. Nothing below it! :sweat_smile: )

But does http://localhost/ make any sense as a first line?
It seems like a very easy typo to make. And I got very little hints from the logs.

Could we make this a bit more error message friendly for the next people doing this mistake? :thinking:

It’s a totally valid config if you want to specifically only serve something at the root one way and make another site block that serves everything else another way. I don’t think it’s really something we can make an error.

If it makes sense, this could be added in the structure example : Caddyfile Concepts — Caddy Documentation.

We have the ‘Site address’, but as you mentioned here a Site address can be any domain + any URI and this is a valid config.

It is mentioned in the addresses section that the path is parsed but it doesn’t warn about path matching being exact – it’s already mentioned in many other places in the docs though.

1 Like

What about mentioning it in the logs too? At least when { debug } is turned on?
Someting like: “No matcher fits incoming request ‘GET /images’”. At least then it would be possible to get a hint.

When Caddy is mute, it difficult to know where to start looking. :wink:

Yeah, that’s not a bad idea re debug.

Thanks for the suggestion!

1 Like

Talking about logging. When Caddy is a reverse proxy, it’s often hard to know if a given problem is in the Caddy config, if it is upstream, or downstream.

Any help here to sort of divide and conquer the debugging problem will be a huge benefit for locating what area to research.

E.g. logging upstream headers are more useful than logging downstream headers. Because downstream you can track with you browser. What happens between the reverse proxy and the backend server is a lot more hidden.

So if Caddy offers good debugging, we’ll get a benefit over the alternatives. :slight_smile:

Yeah we had a mistake in the spot that was logging reverse_proxy debug logs so it wasn’t logging errors. These two commits fixed it and v2.1 will be better in that regard.

If you want, you can compile from source or use the latest build from CI for now if you need it right away.

1 Like

FYI I opened an issue on GitHub for this:

I took a quick look at the request handling logic and unfortunately, I don’t see an immediately obvious way to log when a request isn’t handled.

Caddy uses middleware style request handler chaining. I don’t think we have any state we can check at the end of the chain to differentiate whether the empty 200 response was intentional or not. I think the response served is just the default empty response that Golang writes. We’ll see.

1 Like

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