Why does caddy return an empty 200 response for a non-configured host?

1. My Caddy version (caddy version):

~ # ./caddy version                                                                                                                                                            
v2.0.0-rc.3 h1:z2H/QnaRscip6aZJxwTbghu3zhC88Vo8l/K57WUce4Q=

2. How I run Caddy:

a. System environment:

Ubuntu 18.04

b. Command:

~ # ./caddy run --config caddyconfig.json 

c. Service/unit/compose file:

N/A

d. My complete Caddyfile or JSON config:

Retrieved via the /config/ API on a running configuration

{
  "admin":{
    "enforce_origin":false,
    "listen":":2020",
    "origins":[
      "192.168.10.2:2020"
    ]
  },
  "apps":{
    "http":{
      "servers":{
        "srv":{
          "automatic_https":{
            "skip":[
              "jackett.example.eu",
              "registry.example.eu"
            ]
          },
          "listen":[
            ":19500"
          ],
          "routes":[
            {
              "handle":[
                {
                  "handler":"subroute",
                  "routes":[
                    {
                      "handle":[
                        {
                          "handler":"reverse_proxy",
                          "upstreams":[
                            {
                              "dial":"http://172.18.0.15:9117"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "match":[
                {
                  "host":[
                    "jackett.example.eu"
                  ]
                }
              ],
              "terminal":true
            },
            {
              "handle":[
                {
                  "handler":"subroute",
                  "routes":[
                    {
                      "handle":[
                        {
                          "handler":"reverse_proxy",
                          "upstreams":[
                            {
                              "dial":"http://172.18.0.23:5000"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "match":[
                {
                  "host":[
                    "registry.example.eu"
                  ]
                }
              ],
              "terminal":true
            }
          ]
        }
      }
    }
  }
}

3. The problem I’m having:

When connecting to one of the defined hosts everything is OK - I receive the expected reply.

When connection as a host which is not defined in caddy, caddy replies with an empty 200 answer:

curl --header "Host: doesnotexist.hello.world" http://192.168.10.2:19500/ -v
*   Trying 192.168.10.2...
* TCP_NODELAY set
* Connected to 192.168.10.2 (192.168.10.2) port 19500 (#0)
> GET / HTTP/1.1
> Host: doesnotexist.hello.world
> User-Agent: curl/7.55.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: Caddy
< Date: Fri, 24 Apr 2020 11:32:46 GMT
< Content-Length: 0
<
* Connection #0 to host 192.168.10.2 left intact

4. Error messages and/or full log output:

When setting up the log level to DEBUG, I see correct replies from Caddy when I hoit a defined site, for instance

2020/04/24 11:25:57.816 DEBUG   http.handlers.reverse_proxy     upstream roundtrip      {"upstream": "172.18.0.15:9117", "request": {"method": "GET", "uri": "/", "proto": "HTTP/1.1", "remote_addr": "192.168.10.72:62555", "host": "jackett.example.eu", "headers": {"X-Forwarded-For": ["192.168.10.72"], "User-Agent": ["curl/7.55.1"], "Accept": ["*/*"]}}, "headers": {"Location": ["/UI/Dashboard"], "Date": ["Fri, 24 Apr 2020 11:25:57 GMT"], "Server": ["Kestrel"], "Content-Length": ["0"]}, "duration": 0.001942645, "status": 301}

The status is 301 and it is correct

When I curl the non-existing site (for instance doesnotexist.hello.world above) there is no output in the logs (nothing).

Is this a default behaviour? If so, can I change it to something else? (I am actually not sure what the proper return code for “the host is not available here” should be (because it will exist in DNS via a wildcard). Not 200 in any case (I think - because it does not seem right))

This is intentional; it’s described here: v2: Any requested document returns always 0 bytes · Issue #3226 · caddyserver/caddy · GitHub

Of course. Just define a route in your JSON that doesn’t have any matchers. Or, a site block in your Caddyfile that doesn’t have any hostnames in the address or other matchers.

I tried this, but I belive I did not understand your suggestion (caddy still returns a 200):

"apps": {
                "http": {
                    "servers": {
                        "srv": {
                            "listen": [
                                ":19500"
                            ],
                            "routes": [
                                {
→ there is no matcher for that route (this line is not part of the JSON of course)
                                    "handle": [{
                                        "handler": "static_response",
                                        "body": "no container defined"
                                    }]
                                }
                            ],  # the hosts are configured here
                            "automatic_https": {
                                "skip": []
                            }
                        }
                    }
                }
            }

(I programatically add to that, specifically in routes, more routes and matchers - these work fine per my initial question)

What does your full config look like after the change? It’s hard to just answer a question about a subset of the config. We need to see the big picture

Well, there’s nothing in that config snippet that would return anything other than a 200… what are you trying to do?

You are right, I am sorry for the lack of clarity.

What I am trying to do is to differentiate between the cases where

  • there is no container behind my reverse proxy. Caddy responds with a 200 (because the connexion was successful) and an empty content (because this choice was made during the design)
  • there is a container behind the proxy but for some reason it replies to the proxy with an empty content (on purpose or because something went havok) - caddy replies with a 200 and this empty content from the upstream server.

This is why I wanted to respond with something which would indicate that there is no upstream server under that address (the no container defined). I missed the point about 200 being (correctly) returned because I was focused on the text reply. So let’s stay with no container defined and a 200. (I am really sorry for the confusion)

Now that I have restarted, reloaded and rebooted and my configuration finally caught up, the effect I see is that this route without any matchers takes over the other routes with the matchers:

  • if the snippet above is present in the configuration, all responses to any host are no container defined
    if I remove it, the right containers respond when called, or I get the empty 200 when hitting a non-existing container

When Caddy proxies a request, it just writes the response to downstream – so if the backend replies with a 200, Caddy will respond with a 200. If it replies with a 404, Caddy will respond with a 404. If it replies with a 500, Caddy responds with a 500.

(This behavior will be more configurable in the future.)

The rest of the container/Docker stuff is a bit beyond me though, so I will need to defer to someone more knowledgeable about that to help.

You would get a 502 if Caddy can’t connect to the proxied service.

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