This episode covers redirects and error handling: the redir directive with status codes 301, 302, and others, the www redirect pattern, custom error pages with handle_errors, and the respond directive for static and JSON responses.

Users rarely type a perfectly correct URL. They type www when the site is non-www, open old URLs that have moved, or land on pages that no longer exist. Episode 13 covers two tools for these situations: redirects to move users, and error handling for dealing with failed requests.
redir moves a request to another location. The chosen status determines the behavior of browsers and search engines:
example.com {
redir /old-page https://example.com/new-page permanent
redir /temp https://example.com/sementara temporary
}permanent produces 301 — for URLs that moved permanently; SEO transfers the authority.temporary produces 302 — for temporary changes, such as during maintenance.307 and 308 preserve the request method.To keep query parameters when redirecting, use a placeholder:
example.com {
redir /search /cari?{query} permanent
}redir /search /cari?{query} moves the user and carries the query parameters along. This pattern prevents search data from being lost when moving pages.
Domain consistency matters for SEO and cookies:
www.example.com {
redir https://example.com{uri} permanent
}
example.com {
root * /var/www
file_server
}Other frequently used patterns:
old.example.com {
redir https://example.com{uri} permanent
}And redirect many old URLs at once:
example.com {
redir /lama1 /baru1 permanent
redir /lama2 /baru2 permanent
root * /var/www
file_server
}Caddy's default error pages are plain. For a better user experience, create custom pages:
example.com {
handle_errors {
rewrite * /error-{http_error.code}.html
file_server
}
root * /var/www
file_server
}Finer control per status code:
example.com {
handle_errors {
@404 {
expression {http_error.status_code} == 404
}
handle @404 {
respond "Page not found" 404
}
handle {
respond "An error occurred" 500
}
}
root * /var/www
file_server
}If you only want a single page for all errors:
example.com {
handle_errors {
respond "Oops, something went wrong" {http_error.status_code}
}
root * /var/www
file_server
}respond can return text or JSON:
example.com {
respond /halo "Hello, world!" 200
handle /api/status {
respond `{"status": "ok"}` 200 {
header Content-Type application/json
}
}
}A respond with a JSON body is wrapped in backticks so the quote characters don't cause problems. Set the Content-Type header so clients recognize the JSON. This pattern is often used for health check endpoints.
A dedicated endpoint for monitoring availability:
example.com {
respond /healthz 200 "OK"
root * /var/www
file_server
}respond /healthz 200 "OK" gives monitors (uptime robots, load balancers) a lightweight endpoint to check the server. Health checks are covered in more depth in episode 16.
Episode 13 equipped you with redirects and error handling: redir with 301, 302, 307, and 308 statuses, www and old URL redirect patterns, custom error pages with handle_errors, specific and catch-all status handling, and respond for static, JSON, and health check responses.
Key takeaways:
redir ... permanent uses 301, temporary uses 302.{uri} and {query} so redirects don't lose data.handle_errors handles all custom error pages.{http_error.status_code} tells you the active error status.respond can return text, JSON, and health checks.redir before other handlers so it's processed first.In the next episode, episode 14, we move into load balancing strategies — multiple backends with round_robin, least_conn, ip_hash, and other policies, active and passive health checks, upstream configuration with weights and timeouts, and horizontal scaling and high availability use cases.