HTTP

423 - Locked

Hitting a 423 Locked means the resource is currently locked by another process or user—a file is in use, a database record has an active lock, or concurrent access is blocked. This client-side error (4xx) happens when servers enforce resource locking to prevent conflicts. Most common in WebDAV operations or file systems, but also appears when database transactions lock records, concurrent edits are prevented, or distributed locks are active.

#Common Causes

  • Frontend: Multiple users editing same resource simultaneously. File upload in progress locks resource. Long-running operation holds lock. Previous operation didn't release lock.
  • Backend: WebDAV lock mechanism active. Database row-level locking. File system lock on file. Distributed lock (Redis, etc.) not released. Transaction holds lock too long.
  • Infrastructure: File server locks files during access. Database connection holds locks. Distributed locking service (Redis, etc.) maintains locks.

Solutions

  1. 1Step 1: Diagnose - Check DevTools Network tab Response headers—look for Retry-After header. Review Lock-Token header if present. Check if resource is locked by current user.
  2. 2Step 2: Diagnose - Server logs show which resource is locked and by whom. Review lock expiration times. Check if locks are being released properly. Verify distributed lock status.
  3. 3Step 3: Fix - Client-side: Wait for Retry-After duration before retrying. Implement exponential backoff for retries. Check Lock-Token and release if holding. Show user-friendly "resource in use" message.
  4. 4Step 4: Fix - Server-side: Return 423 with Retry-After header. Implement lock timeout mechanisms. Release locks on operation completion. Add lock status endpoints.
  5. 5Step 5: Fix - Infrastructure: Configure lock timeouts. Review distributed lock expiration. Ensure locks are released on errors. Monitor lock contention.

</>Code Examples

Fetch API: Handle 423 with Retry-After
1// Client-side: Handle 423 by respecting Retry-After header
2async function updateResourceWithLockHandling(id, data, maxRetries = 5) {
3  for (let attempt = 0; attempt < maxRetries; attempt++) {
4    const response = await fetch(`/api/resources/${id}`, {
5      method: 'PUT',
6      headers: {
7        'Content-Type': 'application/json',
8        'If-Match': '*',
9      },
10      body: JSON.stringify(data),
11    });
12    
13    if (response.status === 423) {
14      if (attempt < maxRetries - 1) {
15        // Resource locked - check Retry-After header
16        const retryAfter = parseInt(response.headers.get('Retry-After') || '0');
17        const lockToken = response.headers.get('Lock-Token');
18        
19        const delay = retryAfter > 0 
20          ? retryAfter * 1000 
21          : Math.pow(2, attempt) * 1000; // Exponential backoff fallback
22        
23        console.log(`Resource locked, retrying in ${Math.ceil(delay / 1000)} seconds...`);
24        
25        // Show user-friendly message
26        showUserMessage({
27          type: 'info',
28          message: 'Resource is currently being edited by another user. Please wait...',
29          duration: delay,
30        });
31        
32        await new Promise(resolve => setTimeout(resolve, delay));
33        continue;
34      } else {
35        throw new Error('Resource remains locked after maximum retries');
36      }
37    }
38    
39    return response.json();
40  }
41}
42
43// Check lock status before attempting update
44async function checkLockStatus(id) {
45  const response = await fetch(`/api/resources/${id}/lock`, {
46    method: 'HEAD',
47  });
48  
49  if (response.status === 423) {
50    const retryAfter = response.headers.get('Retry-After');
51    return {
52      locked: true,
53      retryAfter: retryAfter ? parseInt(retryAfter) : null,
54      lockToken: response.headers.get('Lock-Token'),
55    };
56  }
57  
58  return { locked: false };
59}
Express.js: Resource Locking with Timeout
1// Server-side: Implement resource locking
2const express = require('express');
3const Redis = require('ioredis');
4const app = express();
5
6const redis = new Redis();
7const LOCK_TIMEOUT = 300; // 5 minutes
8
9// Lock resource
10async function lockResource(resourceId, userId, timeout = LOCK_TIMEOUT) {
11  const lockKey = `lock:resource:${resourceId}`;
12  const lockValue = `${userId}:${Date.now()}`;
13  
14  // Try to acquire lock
15  const result = await redis.set(lockKey, lockValue, 'EX', timeout, 'NX');
16  return result === 'OK';
17}
18
19// Release lock
20async function releaseLock(resourceId, userId) {
21  const lockKey = `lock:resource:${resourceId}`;
22  const lockValue = await redis.get(lockKey);
23  
24  if (lockValue && lockValue.startsWith(`${userId}:`)) {
25    await redis.del(lockKey);
26    return true;
27  }
28  
29  return false;
30}
31
32// Check lock status
33async function getLockStatus(resourceId) {
34  const lockKey = `lock:resource:${resourceId}`;
35  const lockValue = await redis.get(lockKey);
36  
37  if (lockValue) {
38    const [userId, timestamp] = lockValue.split(':');
39    const age = Date.now() - parseInt(timestamp);
40    const ttl = await redis.ttl(lockKey);
41    
42    return {
43      locked: true,
44      userId: userId,
45      age: age,
46      ttl: ttl,
47    };
48  }
49  
50  return { locked: false };
51}
52
53// Update endpoint with locking
54app.put('/api/resources/:id', async (req, res) => {
55  const resourceId = req.params.id;
56  const userId = req.user.id;
57  
58  // Try to acquire lock
59  const locked = await lockResource(resourceId, userId);
60  
61  if (!locked) {
62    const lockStatus = await getLockStatus(resourceId);
63    const retryAfter = lockStatus.ttl || 60;
64    
65    return res.status(423)
66      .set('Retry-After', retryAfter.toString())
67      .set('Lock-Token', `resource-${resourceId}`)
68      .json({
69        error: 'Locked',
70        message: 'Resource is currently locked by another user',
71        retryAfter: retryAfter,
72        lockStatus: lockStatus,
73      });
74  }
75  
76  try {
77    // Perform update
78    const updated = await db.resources.update(resourceId, req.body);
79    
80    // Release lock after update
81    await releaseLock(resourceId, userId);
82    
83    res.json(updated);
84  } catch (error) {
85    // Release lock on error
86    await releaseLock(resourceId, userId);
87    throw error;
88  }
89});
90
91// Lock status endpoint
92app.head('/api/resources/:id/lock', async (req, res) => {
93  const lockStatus = await getLockStatus(req.params.id);
94  
95  if (lockStatus.locked) {
96    res.status(423)
97      .set('Retry-After', lockStatus.ttl.toString())
98      .set('Lock-Token', `resource-${req.params.id}`)
99      .send();
100  } else {
101    res.status(200).send();
102  }
103});
Nginx: Pass Lock Headers
1# Nginx: Pass lock-related headers
2server {
3    listen 80;
4    server_name api.example.com;
5    
6    location /api/ {
7        proxy_pass http://backend;
8        proxy_set_header Host $host;
9        proxy_set_header X-Real-IP $remote_addr;
10        
11        # Pass lock-related headers
12        proxy_set_header Lock-Token $http_lock_token;
13        proxy_set_header Timeout $http_timeout;
14        
15        # Pass Retry-After to client
16        proxy_pass_header Retry-After;
17    }
18}

Related Errors

Provider Information

This error code is specific to HTTP services. For more information, refer to the official HTTP documentation.

423 - Locked | HTTP Error Reference | Error Code Reference