Skip to content

410 Gone

What is HTTP 410 Gone?

410 Gone
Imagine your favorite toy store used to sell a really cool robot toy. You rememb...
HTTP 410 Gone status code illustration

Explain Like I’m 3

Remember your old teddy bear that you donated to charity? You can’t play with it anymore because it’s gone forever - it’s at another kid’s house now! When you ask Mom “where’s my teddy bear?” she says “It’s gone, sweetie. We gave it away, remember?” That’s what 410 Gone means - something used to be there, but now it’s gone forever and it’s not coming back!

Example: You finish eating an ice cream cone. Your friend asks for a bite, but it’s all gone! Not hiding, not in the freezer - you ate it all, and there’s no more left!

Explain Like I’m 5

Imagine your favorite toy store used to sell a really cool robot toy. You remember seeing it on the shelf! But now the store says “We don’t sell that anymore - the company stopped making it.” It’s not just out of stock (which would be like 404 Not Found) - they’re never going to sell it again because it’s been discontinued. That’s 410 Gone - the thing definitely existed before, but now it’s permanently gone and won’t come back.

Example: Your school used to have a playground slide, but they removed it because it got too old. There’s just empty space now where the slide used to be. The slide is gone forever, not just being repaired!

Jr. Developer

410 Gone indicates that a resource was previously available at this URL but has been permanently removed. This is different from 404 Not Found, which doesn’t indicate whether the resource ever existed or if the condition is temporary or permanent.

Key distinctions:

  • 404 Not Found: “I don’t have that resource” (might never have existed, might exist elsewhere, might come back)
  • 410 Gone: “That resource used to be here but is permanently deleted” (definitive statement about permanent removal)

Common scenarios for 410:

  • User deletes their account and all associated content
  • Product is discontinued from an e-commerce store
  • Blog post is permanently removed
  • Limited-time offer has expired
  • API version has been sunset
  • Resource exceeded retention period (GDPR deletion)

Why use 410 instead of 404?

  1. SEO: Search engines remove 410 URLs from their index faster than 404s
  2. Client behavior: Clients should stop retrying and remove bookmarks/links
  3. Clarity: Explicitly communicates intentional permanent removal
  4. Cache control: 410 is cacheable by default, reducing repeated requests

Implementation pattern (Tombstone): Instead of completely deleting database records, mark them as deleted and return 410. This allows you to:

  • Return 410 instead of 404 for deleted resources
  • Prevent resource ID reuse
  • Maintain audit trails
  • Potentially restore deleted resources

Code Example

// Express.js: Soft delete with 410 Gone response
const express = require('express');
const app = express();
// Schema with soft delete flag
const userSchema = {
email: String,
username: String,
deleted_at: Date, // null = active, date = deleted
deletion_reason: String
};
// GET endpoint returns 410 for deleted resources
app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
if (!user) {
return res.status(404).json({
error: 'Not Found',
message: 'User not found'
});
}
if (user.deleted_at) {
return res.status(410).json({
error: 'Gone',
message: 'This user account has been permanently deleted',
deleted_at: user.deleted_at
});
}
res.json(user);
});
// DELETE endpoint: soft delete
app.delete('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'Not Found' });
}
if (user.deleted_at) {
// Already deleted
return res.status(410).json({
error: 'Gone',
message: 'User already deleted',
deleted_at: user.deleted_at
});
}
// Soft delete: mark as deleted, don't actually remove
user.deleted_at = new Date();
user.deletion_reason = req.body.reason || 'user_request';
await user.save();
res.status(204).end();
});
// Filter deleted resources from listings
app.get('/api/users', async (req, res) => {
const users = await db.users.find({
deleted_at: null // Only active users
});
res.json(users);
});

Crash Course

HTTP 410 Gone (RFC 9110 §15.5.11) indicates the target resource is no longer available at the origin server and this condition is likely to be permanent. Unlike 404, which is ambiguous about whether the resource ever existed, 410 definitively states the resource once existed but has been intentionally removed with no forwarding address.

RFC 9110 Definition: “The 410 (Gone) status code indicates that access to the target resource is no longer available at the origin server and that this condition is likely to be permanent. If the origin server does not know, or has no facility to determine, whether or not the condition is permanent, the status code 404 (Not Found) ought to be used instead.”

404 vs 410 Decision Tree:

  1. Does the resource exist? → Yes → Return resource (200)
  2. Did it ever exist? → No → 404 Not Found
  3. Did it exist and is permanently deleted? → Yes → 410 Gone
  4. Did it exist but might come back? → 404 Not Found
  5. Uncertain if permanent? → 404 Not Found (safer default)

Strategic Uses of 410:

  1. SEO Management

    • Search engines de-index 410 URLs faster than 404
    • Preserves crawl budget by signaling not to retry
    • Useful for cleaning up obsolete content from search results
  2. API Versioning

    • Sunset old API versions with 410
    • Signals clients to upgrade to new version
    • Include Sunset header before switching to 410
  3. Resource Lifecycle Management

    • User account deletion
    • Product discontinuation
    • Content removal (DMCA, policy violations)
    • Time-limited resources (promotions, events)
  4. GDPR/Data Retention

    • Data deleted per retention policies
    • Right to be forgotten compliance
    • Expired personal data

Implementation Patterns:

  1. Tombstone Pattern: Keep deleted marker

    • Soft delete: flag as deleted, keep database record
    • Return 410 for deleted resources
    • Prevents ID reuse, maintains audit trail
  2. Hard Delete with Cache: Remove from DB, cache 410

    • Perm…

Code Example

// Comprehensive 410 implementation with tombstone pattern
const express = require('express');
const app = express();
// Soft delete schema
const articleSchema = {
title: String,
content: String,
author_id: String,
deleted_at: Date,
deletion_metadata: {
deleted_by: String,
reason: String, // 'author_request', 'policy_violation', 'expired', 'gdpr'
can_restore: Boolean
}
};
// Middleware to check for deleted resources
function checkDeleted(req, res, next) {
if (req.resource && req.resource.deleted_at) {
return res.status(410)
.set('Cache-Control', 'public, max-age=86400') // Cache for 24 hours
.json({
error: 'Gone',
message: 'This resource has been permanently deleted',
deleted_at: req.resource.deleted_at,
reason: req.resource.deletion_metadata.reason
});
}
next();
}
// Load resource middleware
async function loadArticle(req, res, next) {
const article = await db.articles.findById(req.params.id);
if (!article) {
return res.status(404).json({
error: 'Not Found',
message: 'Article not found'
});
}
req.resource = article;
next();
}
// GET endpoint
app.get('/api/articles/:id', loadArticle, checkDeleted, (req, res) => {
res.json(req.resource);
});
// DELETE endpoint with soft delete
app.delete('/api/articles/:id', loadArticle, checkDeleted, async (req, res) => {
const { reason = 'user_request' } = req.body;
// Soft delete
req.resource.deleted_at = new Date();
req.resource.deletion_metadata = {
deleted_by: req.user.id,
reason,
can_restore: reason !== 'gdpr' && reason !== 'policy_violation'
};
await req.resource.save();
// Also create audit log
await db.audit_log.create({
action: 'DELETE',
resource_type: 'article',
resource_id: req.resource.id,
user_id: req.user.id,
timestamp: new Date(),
reason
});
res.status(204).end();
});
// API version sunset example
const oldApiRouter = express.Router();
oldApiRouter.use((req, res) => {
res.status(410)
.set('Cache-Control', 'public, max-age=2592000') // 30 days
.json({
error: 'Gone',
message: 'API v1 has been sunset',
sunset_date: '2024-01-01',
migration_guide: 'https://api.example.com/docs/v1-to-v2-migration',
new_endpoint: req.url.replace('/v1/', '/v2/')
});
});
app.use('/api/v1', oldApiRouter);
// Expired time-limited resource
app.get('/api/promotions/:code', async (req, res) => {
c...

Deep Dive

HTTP 410 Gone represents a semantic commitment to permanent resource unavailability. While RFC 9110 defines it simply as “likely permanent,” production implementations must balance between providing this definitive signal and managing the practical challenges of data retention, audit requirements, and potential restoration scenarios.

Semantic Precision: 410 vs 404 vs 403:

  • 404 Not Found: Epistemic uncertainty - “I don’t know about this resource”

    • Resource never existed
    • Resource might exist elsewhere
    • Temporary unavailability
    • Server lacks information
  • 410 Gone: Epistemic certainty - “This resource existed here and is permanently removed”

    • Definitive knowledge of prior existence
    • Intentional permanent removal
    • No forwarding address
    • Authority to make permanence claim
  • 403 Forbidden: Access control - “The resource exists but you can’t access it”

    • Resource exists but is protected
    • Different from deletion
    • Consider 410 for hidden resources instead of 403 to prevent enumeration

The Tombstone Pattern: Implementation Trade-offs:

Soft deletion (tombstoning) maintains metadata about deleted resources:

Advantages:

  • Can return 410 instead of ambiguous 404
  • Audit trail for compliance (GDPR, SOX, HIPAA)
  • Prevents resource ID reuse vulnerabilities
  • Enables restoration within grace period
  • Supports analytics on deleted resources

Disadvantages:

  • Database bloat from deleted records
  • Query performance impact (must filter deleted=true)
  • Privacy concerns (“deleted” data still persists)
  • Complexity in ensuring deleted data isn’t leaked
  • GDPR “right to be forgotten” may require hard deletion

Hybrid Strategy:

  1. Soft delete initially → return 410
  2. After grace period (30-90 days) → hard delete
  3. Cache 410 response for hard-deleted IDs
  4. Eventually expire cache entries (1-5 years)

SEO and Cache Implications:

RFC 9110 specifies 410 responses are cacheable by default (unlike 404, which is also cacheable but less aggressively). This has important implications:

Search Engine Behavior:

  • Google removes 410 URLs from index faster than 404s
  • Crawlers stop retrying 410 URLs, preserving crawl budget
  • Reduces load from bot traffic for deleted resources
  • Important for large-scale content deletions

Client Caching:

  • Browsers and proxies can cache 410 indefinitely
  • Set explicit Cache-Control headers for cache duration
  • Consider shorter cache for soft deletes (might restore)
  • Longer…

Code Example

// Production-grade soft delete with comprehensive lifecycle management
const Redis = require('ioredis');
const redis = new Redis();
// Soft delete implementation with audit trail
class ResourceLifecycleManager {
constructor(db, cacheClient) {
this.db = db;
this.cache = cacheClient;
this.gracePeriod = 90 * 24 * 60 * 60 * 1000; // 90 days
}
// Soft delete with tombstone
async softDelete(collection, id, metadata) {
const resource = await this.db[collection].findById(id);
if (!resource) {
const err = new Error('Resource not found');
err.statusCode = 404;
throw err;
}
if (resource.deleted_at) {
const err = new Error('Resource already deleted');
err.statusCode = 410;
err.deleted_at = resource.deleted_at;
throw err;
}
// Mark as deleted with metadata
resource.deleted_at = new Date();
resource.deletion_metadata = {
deleted_by: metadata.user_id,
reason: metadata.reason,
ip_address: metadata.ip,
user_agent: metadata.user_agent,
can_restore: this.canRestore(metadata.reason),
hard_delete_scheduled: new Date(Date.now() + this.gracePeriod)
};
await resource.save();
// Create audit log entry
await this.db.audit_log.create({
action: 'SOFT_DELETE',
resource_type: collection,
resource_id: id,
user_id: metadata.user_id,
metadata: resource.deletion_metadata,
timestamp: new Date()
});
// Cache deletion for fast lookups
await this.cache.setex(
`deleted:${collection}:${id}`,
this.gracePeriod / 1000,
JSON.stringify({
deleted_at: resource.deleted_at,
reason: metadata.reason
})
);
return resource;
}
// Check if resource can be restored
canRestore(reason) {
const nonRestorableReasons = ['gdpr', 'policy_violation', 'legal_requirement'];
return !nonRestorableReasons.includes(reason);
}
// Restore soft-deleted resource
async restore(collection, id, userId) {
const resource = await this.db[collection].findById(id);
if (!resource || !resource.deleted_at) {
const err = new Error('No deleted resource found');
err.statusCode = 404;
throw err;
}
if (!resource.deletion_metadata.can_restore) {
const err = new Error('Resource cannot be restored');
err.statusCode = 403;
throw err;
}
// Restore resource
const deletedAt = resource.deleted_at;
resource.deleted_at = null;
resource.restored_at = new Date();
resource.restored_by = userId;
await resource.save();
// Audit log
await this.db.audit_log.create({
action: 'RESTORE',
resource_type: collection,
resource_id: id,
user_id: userId,
metadata: { originally_deleted_at: deletedAt },
timestamp: new Date()
});
// Remove from deleted cache
await this.cache.del(`deleted:${collection}:${id}`);...

Frequently Asked Questions

What's the difference between 404 Not Found and 410 Gone?

404 is ambiguous - the resource might never have existed, might be temporarily unavailable, or might exist elsewhere. 410 is definitive - the resource definitely existed at this URL and has been permanently, intentionally removed. Use 410 when you want to explicitly signal permanent deletion, especially for SEO purposes or to tell clients to stop retrying.

Should I always use 410 when deleting resources?

Not necessarily. Use 410 when: (1) You want search engines to de-index the URL, (2) You want clients to stop retrying and remove bookmarks, (3) You're certain the deletion is permanent, (4) You maintain deletion metadata (tombstone pattern). Use 404 when: (1) You hard-delete without tombstones, (2) The resource might be restored, (3) You're uncertain about permanence.

How long should I keep returning 410 for deleted resources?

It depends on your use case. Common patterns: (1) Soft delete: return 410 during grace period (30-90 days), then hard delete and switch to 404. (2) Tombstone forever: keep deleted markers indefinitely to prevent ID reuse. (3) Hard delete with cache: delete from DB but cache 410 response for 1-5 years. (4) API versioning: return 410 indefinitely for sunset versions.

Does 410 help with SEO?

Yes, significantly. Search engines like Google remove 410 URLs from their index faster than 404s. Crawlers also stop retrying 410 URLs, preserving your crawl budget. This is useful when permanently removing content - you want search engines to stop showing dead links in results. However, if you might restore the content, use 404 instead.

Can clients cache 410 responses?

Yes, 410 is cacheable by default per RFC 9110. You can control cache duration with Cache-Control headers. Suggested durations: (1) Soft deletes (might restore): 1 day, (2) Hard deletes: 30+ days, (3) API sunset: 30-90 days, (4) Permanent tombstones: 1+ years. Aggressive caching reduces server load from repeated requests for deleted resources.

Should I return 410 or 404 for GDPR 'right to be forgotten' deletions?

This is debated. Arguments for 404: GDPR requires deletion of personal data, and 410 implies you still have metadata about the deleted resource. Arguments for 410: You can return 410 without identifying information, and tombstones are necessary for audit compliance. Conservative approach: return 404 for GDPR deletions to minimize privacy risk.

What should I include in a 410 response body?

Include: (1) Clear message that resource is permanently deleted, (2) Deletion timestamp if appropriate, (3) For API versioning: migration guide and new endpoint, (4) For expired resources: expiration date, (5) Optional: reason for deletion if not sensitive. Don't include: sensitive deletion metadata, who deleted it (privacy), detailed internal information. Keep it simple and informative.

Common Causes

  • User deleted their account permanently
  • Content removed for policy violations or legal reasons
  • Product discontinued from e-commerce catalog
  • Blog post or article permanently deleted by author
  • Limited-time offer or promotion has expired
  • API version has been sunset and deprecated
  • Resource exceeded data retention period (GDPR, compliance)
  • DMCA takedown or copyright claim processed
  • Event or booking time has passed (past event)
  • Subscription or access period has permanently ended
  • Content violated terms of service and was removed
  • Organization closed or account deactivated

Implementation Guidance

  • Understand this is permanent - the resource is gone and won’t return
  • Remove bookmarks, links, and references to this URL
  • For API clients: stop retrying this endpoint and remove from cache
  • If you’re a content owner: contact site admin if deletion was unintended
  • For API versioning: follow migration guide to new version/endpoint
  • For web pages: update or remove links to prevent 410 errors
  • Search for alternative or replacement content if available
  • If you believe the deletion was a mistake: contact support immediately (within grace period)
  • For expired offers: check for current promotions or alternatives
  • For web crawlers: remove URL from index and stop requesting
  • Update sitemaps to remove deleted URLs
  • Consider using Wayback Machine if you need to view historical content

Comments