An HTTP status is part of the interface. A browser may render a friendly page, but a monitor, crawler, cache, and API client also need the status code and headers to tell the same story.

Permanent moves are not all interchangeable

308 Permanent Redirect communicates that the resource has a new permanent URI while preserving the request method. That is important for a client that sends POST, PUT, or another non-GET method. A redirect from an old page to a new page is easy to test with GET; a migration of an endpoint must also be tested with the method that real callers use.

curl -sS -D - -o /dev/null --max-redirs 0 \
  https://old.example.test/api/item

Check that Location is absolute or correctly rooted, that the target is the intended host, and that the response is not silently downgraded to a temporary redirect. A 308 is cacheable by default, so change it only with a deliberate migration plan.

Distinguish missing from removed

Use 404 Not Found when the server cannot find a current representation and the absence may be temporary or unknown. 410 Gone is stronger: it says the resource was intentionally removed and is unlikely to return. Both should have a body that helps a human, but neither should return 200 just because a custom error page rendered successfully.

The check belongs at the edge:

curl -sS -o /tmp/missing.html -w '%{http_code} %{url_effective}\n' \
  https://www.example.test/a-route-that-does-not-exist

Keep the response headers on the error path. Security headers, a useful content type, and a correct status are more valuable than a decorative error animation.

Sources