caddy: Caddyfile: no way to set a custom 404/error page

It’s time for yet another issue. I can’t seem to find a way to set a custom 404 page. On the old Caddyfile, I would have errors { 404 404.html }, but that doesn’t seem to work here.

I have looked at handle_errors, but that feels more for errors from directives rather than normal errors such as a 404.

I have also found out that there’s basically no way to get a status code, which is pretty understandable. It’s not something from the request, so it makes sense.

That’s all that I was able to find on the current Caddy v2 documentation, so I decided to open this issue.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 23 (14 by maintainers)

Commits related to this issue

Most upvoted comments

It’s not pretty, but I was able to have a custom 404 message with the following:

file_server {
  # If we visit /404.html directly we receive a 404 response, and not a 200.
  hide 404.html
}

handle_errors {
        @404 {
            expression {http.error.status_code} == 404
        }
        rewrite @404 /404.html
        file_server
}

We’re going to work on this in/after 2.1 – essentially, it’s the same fundamental feature as “wrap the response and then decide what the actual response will be” – so for example, solving your feature request should also solve #2920.

It’ll probably look like a subroute handler that has some extra options for wrapping the response and then executing custom routes based on properties of the response (status code, headers, maybe even body, etc).

Edit: Thanks for putting in some effort and doing some research about it 😃 Refreshing to see! Anyway, you’re right that handle_errors is for server errors, not HTTP errors. (Edit: the handle_errors directive also handles 4xx errors.)

If you want to strictly limit which errors you handle, perhaps this is better:

handle_errors {
	@404 {
		expression {http.error.status_code} == 404
	}
	handle @404 {
		rewrite * /error.html
		file_server
	}
}

Don’t expose a file server when you shouldn’t. 😃 It all depends on your use case.

@diamondburned That’s the best idea. I’m going to update our docs to use that example. 🐱

Edit: Yep, this works:

handle_errors {
	rewrite * /{http.error.status_code}
	reverse_proxy https://http.cat
}

I think using handle_errors is actually great, since I can probably make it proxy over https://http.cat.

@QSchulz That will only work for 404 errors when using the static file server; not error pages in general (this issue is about 404s but also both, I guess). It will also serve the 404 page with a 200 status code. That is not usually expected. But again, it depends on your needs.

I believe the way I have proposed above is the most generally useful and succinct, and it already works today with v2.0:

handle_errors {
	rewrite * /{http.error.status_code}.html
	file_server
}

You can build on that or tweak it according to your needs.

You don’t need to build a separate page for every status code:

handle_errors {
	rewrite * /error.html
	templates
	file_server
}

(This uses templates so the page’s content is customized, presumably for each status code.)

@millette Great! That is a little simpler – assuming you have error pages for all the common error statuses (404, 502, maybe 500, etc.)

As for the hide, I’m quite convinced that a 200 response in that situation is actually correct: user explicitly requested a page called 404.html – it should probably be served up, no? (Tangential. You do you. I’m just saying… as a client, that’s what I would expect.)