Caddy 2 handle_error examples with custom error pages?

I’ve been searching around for examples of the handle_error directive and so far haven’t come up with anything good. The end goal is to be able to create custom error pages that we can roll out to our various caddy servers which are acting as proxies using reverse_proxy directive.

The page for handle_errors directive is pretty sparse on details:

Anyone got examples? I’m using v2.0.0-beta.18 currently.

Thanks as always!


I’ve changed my search terms a bit and finding some stuff. Going over this now. If anyone else has examples I’ll still take them!

Yeah… we need examples here; I’m deferring most work on error handling to ~v2.1 when I can dedicate a little more time into good designs for it. But everything you need for basic error handling should be available with the handle_errors directive, if a little verbose.

Also, please upgrade to beta 20. :slight_smile:

Are there any examples I can run off of? I’m trying to figure out how to take what I’m reading and place it into the normal Caddy formatted file vs JSON.

So currently, the only way to do it is using the still experimental CEL expression matcher feature. Just gave it a quick go, it looks something like this:

handle_errors {
	@404 {
		expression int({http.error.status_code}) == 404
	respond @404 "Four Oh Four"

Edit: Did a bit more experimentation, this also works:

expression {http.error.status_code} == '404'

There’s some finickiness with types (Caddy placeholders only output strings) and double quotes (" is parsed by Caddy and gets stripped before it hits CEL), but single quotes ' get passed through just fine and CEL handles it as a string as well.

Looks like @matt just wanted to scratch an itch and he added a fix so that you can compare it as an int :joy:

So after that commit you should do expression {http.error.status_code} == 404

I’m still trying to get this to work and not getting the response back for some reason. I’m thinking that maybe I’m placing the directive in the wrong place. So far I’ve tried inside the defined site and outside above the site. When inside the site (below) you’ll see that I’m placing it above the reverse_proxy; which appears to be the correct order based on the directive order.

   default_sni my.webserver.local
   http_port 80
   https_port 443

my.webserver.local {
   tls C:\Caddy\Certificate.pem C:\Caddy\Certificate.key
} {
   header {
      Strict-Transport-Security "max-age=31536000;"
      Server "C15-83"
      X-XSS-Protection "0"
      X-Server-Test "10.6"
   handle_errors {
      expression {http.error.status_code} == '404' {
         respond @404 "Oops 404 Try again"
   redir /{uri}
   tls C:\Caddy\Certificate.pem C:\Caddy\Certificate.key
   reverse_proxy http://localhost:8001 {
      health_path /ping.jsp
	  health_interval 15s
	  health_timeout 30s
	  health_status 2xx
	  health_body "Database query successful"
   log {
      output file C:\Caddy\Logs\mydomain_com_access.log {
         roll_size 100
         roll_keep 7

What’s interesting is that when I generate a 404 error the header “Server” is dropped and replaced with the value of “Caddy” am I able to define headers outside of the defined site?

That’s not right. expression is a matcher type, not a directive.

   handle_errors {
      @404 {
         expression {http.error.status_code} == '404'
      respond @404 "Oops 404 Try again"

If you want to display some 404.html page, you can use rewrite instead of respond. That part was just for explanatory purposes.

If you’re confused about matchers, read this page in the docs: Request matchers (Caddyfile) — Caddy Documentation

1 Like

Sorry copy/pasted the wrong config. I’ve just been tossing around a few things to see if it would respond with the basic respond so I can see it working before I expand out into custom pages. Even with the correction I’m still not getting the expected response of “Oops 404 Try again”

You’ll need to set your header block inside of handle_errors as well for this. If an error happens, it skips the rest of the directives and jumps straight to handle_errors effectively.

Can you give more info? What did you try exactly? Can you replicate with curl -v to see the output? What do you see instead?


Okay, learning more every day. I’ve just found out that 404’s are handled by the backend tomcat service so the system will actually never respond with a 404. So I redid my test with a 502; which is the error we get when Tomcat goes unresponsive. That worked so thank you so far for all the help.


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