Custom 500 Errors Caddy v2

1. Caddy version (caddy version):

v2.3.0

2. How I run Caddy:

sudo systemctl start caddy

a. System environment:

Ubuntu 20.04
PHP 7.4.3
php7.4-fpm

b. Command:

sudo systemctl reload caddy
sudo systemctl restart caddy

c. Service/unit/compose file:

n/a

d. My complete Caddyfile or JSON config:

      root * /home/my/project/

      log {
          output file /var/log/caddy/access.log {
             roll_size 3MiB
             roll_keep 10
             roll_keep_for 2160h
          }
          format json
      }

      @cachedFiles {          
          path *.jpg, *.jpeg, *.png, *.gif, *.ico, *.woff, *.woff2, *.ttf
      }
      header @cachedFiles Cache-Control "public, max-age=604800, must-revalidate"
      encode gzip zstd

      handle_errors {
          respond "{http.error.status_code} {http.error.status_text}"
          # rewrite * /{http.error.status_code}.php
          # php_fastcgi unix//run/php/php7.4-fpm.sock
          # file_server         
      }

      php_fastcgi unix//run/php/php7.4-fpm.sock
      file_server
}

3. The problem I’m having:

I am attempting to catch any 500 errors so that I can:

  1. Notify our team via email/text
  2. Provide a nice message to our clients

The issue seems to be that handle_errors does not capture 500 errors. I can catch 404 errors and create a nice page for our clients to digest. 500 errors always return a blank page. The only way not to receive a blank page is to set the display_errors = On in the php.ini file, and though that is great in development, I don’t want to air our dirty laundry to the world. In addition, the error sent back via display_errors will not allow me to customize the message. I just receive a PHP error:
( ! ) Parse error: syntax error, unexpected end of file in /home/my/project/admin/all.php on line 53

4. Error messages and/or full log output:

n/a (blank page)

5. What I already tried:

I have tried specifically catching 500 errors

          @404 {
              expression {http.error.status_code} == 404
          }
          respond @404 "four oh Four"
          @500 {
              expression {http.error.status_code} == 500
          }
          respond @500 "busted"

One thing to note, when I do set the display_errors = On in the php.ini file, the log file returned by caddy results in a 200, not a 500 error. If display_errors = Off, then caddy returns a 500 error, but it will not redirect, respond, or rewrite as anticipated.

6. Links to relevant resources:

handle_errors w/ caddy
php.ini / display_errors

By “log file” do you mean the respond message? In that case, yeah, you just told Caddy to write that message but you didn’t tell it which status code to use, so it defaults to 200. Instead, do respond <matcher> "message" <status>

@500 expression `{http.error.status_code} == 500`
respond @500 "busted" 500

Ah, sorry, didn’t realize what was going on – yeah, Caddy doesn’t capture errors from upstreams in handle_errors, it only handles errors coming from Caddy itself (like problems in file_server such as 404s, or problems in connecting to the upstream in proxies, etc).

There is a feature in the reverse_proxy handler called handle_response which allows you to configure Caddy to handle proxy responses based on certain conditions like the response status code or headers, but this is only available in JSON config currently. Caddyfile support for this should land in v2.4.0 though. See this PR to track the progress:

Thank you for your lighting quick response! I tried your suggestion:

@500 expression `{http.error.status_code} == 500`
respond @500 "busted" 500

It didn’t work, but I think you realized that in your follow-up response.

I have spent hours researching Caddy and how it works with PHP and maybe I don’t understand how to use Caddy. I will admit, I was just looking for a way to replace php -S localhost:8080 as this simple server (I suspect) was crashing my Ubuntu desktop. I just wanted to use Caddy locally, so it definitely can handle that task. Then I realized how easy it is to configure and how the CaddyFile reads more clearly than Apache and I thought I should do some more research.

I thought maybe it makes sense to look at Caddy to replace Apache. Should I not use Caddy as I would Apache? What is the preferred Caddy way to handle 500 errors in PHP?

I guess in a perfect world I would write flawless code that wouldn’t throw 500 errors, but I have yet to figure out how to set bugs = off.

Really, you should be handling your errors in PHP. You should use set_error_handler and set_exception_handler as early as possible in the lifecycle of your PHP app such that it’ll catch any errors or exception that bubble up. Frameworks typically do this for you, but if you’re doing your own thing without a framework, then you’ll need to do that yourself.

https://www.php.net/manual/en/function.set-error-handler.php

https://www.php.net/manual/en/function.set-exception-handler.php

Gotcha; thank you for the links. I will take a look set_error_handler and set_exception_handler. I am not using a framework for this project.