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.
On second thought after reading https://github.com/caddyserver/caddy/issues/3312, I've found out about {http.error.status_code}
.
Seems like that's for JSON, and I can imagine how that would work in a Caddyfile. I'm still going to keep this open, however, because I believe that an official clean solution should be in the examples.
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 (Edit: the handle_errors
is for server errors, not HTTP errors.handle_errors
directive also handles 4xx errors.)
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
}
TIL there's http.error.status_code
. I thought of something similar, but I had no idea you could access the status code.
(I lied in my above post -- was confused about something. The handle_errors
directive and the error routes in the JSON also works for all HTTP errors, even if it's not in the 5xx range.)
So, wait, can't you just do:
handle_errors {
rewrite * /{http.error.status_code}.html
file_server
}
(Also, the hide
in the above example is not strictly necessary.)
@mholt About hide
, it's only to prevent a direct visit to 404.html and receiving a 200 response.
I also just tried your shorter suggestion and it works perfectly. Thanks :-)
@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.)
Can this be added into the Caddy documentation so that people migrating from v1 can refer to it?
@diamondburned There's a new commit here: https://github.com/caddyserver/website/commit/5f028df5dc4a68ab1d97cff8b566ddf91d279846#diff-7c899be2034fb5b3a97a47eb5d5bdcf3
Maybe https://github.com/caddyserver/website/blob/master/src/docs/markdown/v2-upgrade.md could be improved as well.
UPDATE: submitted https://github.com/caddyserver/website/pull/53
Since people are sharing their way of doing it, I managed with:
example.com {
try_files {path} /404.html
file_server
}
The only thing is that it does not change the URL, so it stays the same (e.g. example.com/foobar) but the content is the one from example.com/404.html.
That way I didn't need to create a page for each error code.
Now that I'm thinking about it... since my website is static.. your solution would be okay since I don't think any other code other than 404 could happen. Anyway. Thanks for asking and the solutions, that was one thing I was missing from caddy v1.
@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.)
I think using handle_errors
is actually great, since I can probably make it proxy over https://http.cat.
@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
}
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 }
dangerous for the casual reader - https://github.com/caddyserver/caddy/issues/3476 for an example
handle_errors {
rewrite * /error.html
templates
file_server
}
Example of conditional behaviour in a template based on status code ?
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.
Hi @mholt 馃憢
In my case, I'm trying to handle 502 errors from a reverse_proxy
directive. However, the handle_errors
directive also converts the 404 errors from a file_handler
directive into an empty response with a 200 status code.
Is there a way to allow 404s (and other errors which are not 502) to pass through as usual?
handle_errors {
@502 {
expression {http.error.status_code} == 502
}
handle @502 {
rewrite * /config/caddy/502.html
file_server
}
}
@csytan
However, the handle_errors directive also converts the 404 errors from a file_handler directive into an empty response with a 200 status code.
I assume you mean a file_server
directive?
That's because your handle_errors
block only handles 502 errors, not 404 errors also...
Is there a way to allow 404s (and other errors which are not 502) to pass through as usual?
What do you mean by "pass through as usual"? Errors get returned to the HTTP server which either runs error routes (if defined) or responds with an empty reply for the given status code. They don't "pass through" anything.
This issue is resolved and getting off-topic, please open topics on the forum for future usage questions: https://caddy.community
Thanks for the reply Matt. It makes more sense to me now.
In case anyone has the same problem of getting a 200 status for unhandled errors, here's how you can pass through the status code:
handle_errors {
@502 {
expression {http.error.status_code} == 502
}
handle @502 {
rewrite * /config/caddy/502.html
file_server
}
handle {
respond "{http.error.status_code} {http.error.status_text}" {http.error.status_code}
}
}
I think I've been having a similar issue to @csytan. I have a server block file a file_server
directive and it's returning an empty 200 for non-existent files.
jackreid.xyz {
encode gzip
file_server * {
root /usr/share/caddy/site
}
handle_errors {
rewrite * /{http.error.status_code}.html
file_server
}
}
You can see this live by visiting https://jackreid.xyz/noexist for example. I've been struggling to understand @csytan's proposed solution to see if it's relevant for me too. Can anybody explain if returning an empty 200 for non-existent files is expected behaviour even?
@JackWReid The root is only set for your primary file_server, not the errors one.
This thread is quickly getting off-topic, so I'll close it now, please ask usage questions on our forum: https://caddy.community
Most helpful comment
It's not pretty, but I was able to have a custom 404 message with the following: