HTTP 410 is Gone: the resource used to be here, has been removed on purpose, and isn’t expected back. RFC 9110 section 15.5.11 frames it as web maintenance: it tells the client that the owners would like links to it removed. A server doesn’t have to mark every removed resource this way, or keep answering 410 forever.
410, 404 or a redirect
removed on purpose, nothing replaces it 410
missing, or you'd rather not say 404
moved, or something replaces it 301 or 308 to the replacement
down for a while 503
If a page has a successor, redirect to it: a 410 turns away visitors who could have been sent somewhere useful. RFC 9110 prefers 410 over 404 only when the server knows the absence is permanent.
Search engines
Google’s documentation says every 4xx except 429 is handled the same way: the URL is removed from the index. So 410 and 404 send Google the same signal. The difference matters to people and to other clients: a 410 records a decision, a 404 only an absence. Search 404 410 in the tool below to see both cards.
Retiring an API
410 is the honest last step of a deprecation. Announce the date first with the Sunset header from RFC 8594, which also defines a sunset link relation for the migration notes:
HTTP/1.1 200 OK
Sunset: Thu, 31 Dec 2026 23:59:59 GMT
Link: <https://example.com/docs/v2-migration>; rel="sunset"
Then, after that date, answer 410 with a body that points to the replacement:
HTTP/1.1 410 Gone
Content-Type: application/problem+json
{"title": "API v1 has been retired", "status": 410, "detail": "Use /v2/orders; see https://example.com/docs/v2-migration"}
Clients should stop calling and drop the URL; retrying a 410 is pointless.
Caching
410 is heuristically cacheable (RFC 9110 section 15.1), so browsers and CDNs may keep it for a while with no Cache-Control at all. That suits something truly gone. If there’s any chance you’ll restore it, send a short max-age with the 410.
In your stack
location = /2019-holiday-sale { return 410; }
location ^~ /api/v1/ { return 410; }
Redirect gone /2019-holiday-sale
app.use('/api/v1', (req, res) => res.status(410).json({ error: 'gone', use: '/v2' })); // Express
return HttpResponseGone() # Django
abort(410) # Flask
http.Error(w, "this endpoint was retired; use /v2", http.StatusGone)