Caddy ignoring handle_errors 404 directive

1. Caddy version (caddy version):

v2.0.0

2. How I run Caddy:

a. System environment:

macOS 10.14.6

b. Command:

caddy run

d. My complete Caddyfile or JSON config:

localhost:2016 {
  reverse_proxy localhost:8000

  handle_errors {
    @404 {
        expression {http.error.status_code} == 404
    }
    respond @404 404 {
      body "404"
      close
    }
  }
}

3. The problem I’m having:

I am trying to use handle_errors to handle 404 responses returned from reverse_proxy but the handle_errors directive is being ignored.

I have a PHP server running at localhost:8000, which behaves as expected:

curl -i localhost:8000

HTTP/1.1 404 Not Found
Host: localhost:8000
Date: Sun, 31 May 2020 08:55:00 +0000
Connection: close
Content-Type: text/html; charset=UTF-8
Content-Length: 533

<!doctype html><html><head><title>404 Not Found</title><style>
body { background-color: #fcfcfc; color: #333333; margin: 0; padding:0; }
h1 { font-size: 1.5em; font-weight: normal; background-color: #9999cc; min-height:2em; line-height:2em; border-bottom: 1px inset black; margin: 0; }
h1, p { padding-left: 10px; }
code.url { background-color: #eeeeee; font-family:monospace; padding:0 2px;}
</style>
</head><body><h1>Not Found</h1><p>The requested resource <code class="url">/</code> was not found on this server.</p></body></html>

When I make a request to Caddy the reverse_proxy is making the request to localhost:8000, but the 404 is not being handled by the handle_errors directive and I see the same response:

curl -i https://localhost:2016

HTTP/2 404
content-type: text/html; charset=UTF-8
date: Sun, 31 May 2020 08:57:07 +0000
host: localhost:2016
server: Caddy
content-length: 533

<!doctype html><html><head><title>404 Not Found</title><style>
body { background-color: #fcfcfc; color: #333333; margin: 0; padding:0; }
h1 { font-size: 1.5em; font-weight: normal; background-color: #9999cc; min-height:2em; line-height:2em; border-bottom: 1px inset black; margin: 0; }
h1, p { padding-left: 10px; }
code.url { background-color: #eeeeee; font-family:monospace; padding:0 2px;}
</style>
</head><body><h1>Not Found</h1><p>The requested resource <code class="url">/</code> was not found on this server.</p></body></html>

4. Error messages and/or full log output:

There is no output in the logs.

5. What I already tried:

I tried using http.response.status instead of http.error.status_code in the Caddyfile but this had no effect.

Do I need to write a middleware to detect that the status code being returned from the reverse_proxy is 404 then return an error from that middleware handler? If so, are there any examples of how the error should be constructed in order to ensure that the handle_errors directive is triggered?

If the backend you’re proxying to returns a 404, that is not (currently) treated as a 404 by Caddy, because the backend successfully handled the request by returning the proper status code.

If you use the latest on the master branch, though, you’ll get this commit which enables it, slated for 2.1: reverseproxy: Enable response interception (#1447, #2920) · caddyserver/caddy@538ddb8 · GitHub

Hi @matt,

Thanks for the swift feedback.

I’ve just cloned the latest master and can see the commit you refer to in the git log.
However, I’m seeing exactly the same response as I described above (i.e., the handle_errors directive is ignored and the 404 response from reverse_proxy is being passed straight through and returned by Caddy).

Any suggestions?
Thanks

Yeah, sorry, that commit doesn’t change existing behavior, but it makes it possible to change it (in the JSON) as there’s a new property you can set in the JSON to intercept the response from the upstream and change how you handle the response to the client based on that. Does that make sense?

@matt thanks for the clarification.
JSON currently looks as follows:

{
  "apps": {
    "http": {
      "servers": {
        "srv0": {
          "errors": {
            "routes": [
              {
                "handle": [
                  {
                    "handler": "subroute",
                    "routes": [
                      {
                        "handle": [
                          {
                            "body": "404",
                            "close": true,
                            "handler": "static_response",
                            "status_code": 404
                          }
                        ],
                        "match": [
                          {
                            "expression": "{http.response.status} == 404"
                          }
                        ]
                      }
                    ]
                  }
                ],
                "match": [
                  {
                    "host": [
                      "localhost"
                    ]
                  }
                ],
                "terminal": true
              }
            ]
          },
          "listen": [
            ":2016"
          ],
          "routes": [
            {
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "localhost:8000"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "match": [
                {
                  "host": [
                    "localhost"
                  ]
                }
              ],
              "terminal": true
            }
          ]
        }
      }
    }
  }
}

Is there an example/docs of the property that needs to be set to intercept the response from the upstream and change the response that’s returned? I had a look through the reverse_proxy JSON docs but perhaps I missed it?

Yep, so, the feature isn’t released yet, so the source code is your documentation for now. :slight_smile:

In your reverse_proxy handler, add this property:

"handle_response": [
    {
        "match": {"status": 404},
        "routes": [ ... ]
    }
]

In the "routes" property you would put your handler routes to handle a 404 returned from the upstream, for example. Note that this is subject to change since it hasn’t been released yet.

Many thanks @matt following did the trick

                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "localhost:8000"
                            }
                          ],
                          "handle_response": [
                            {
                              "match": {
                                "status_code": [
                                  404
                                ]
                              },
                              "routes": [
                                {
                                  "handle": [
                                    {
                                      "body": "404",
                                      "close": true,
                                      "handler": "static_response",
                                      "status_code": 404
                                    }
                                  ]
                                }
                              ]
                            }
                          ]
                        }
                      ]
1 Like

Great! I think you’re the first person to use this feature, glad it is working for you!

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