# 423 Locked

> The resource is locked and cannot be accessed or modified. Learn about WebDAV locks and how to handle locked resources.

Source: https://howhttpworks.com/status-codes/423
Last reviewed: 2026-10-04

> **TL;DR:** Resource is locked by another user or process and can't be modified. Wait for lock to expire or contact the lock owner.

## What is 423 Locked?

A **423 Locked** status code means the resource you're trying to access is currently locked and cannot be modified. Think of it like trying to edit a document that someone else has open with exclusive editing rights—you can view it, but you can't make changes until they release the lock.

This status code is part of the WebDAV (Web Distributed Authoring and Versioning) extension and is used to prevent conflicting modifications in collaborative environments.

## When Does This Happen?

You'll see a 423 Locked response in these common situations:

**1. Exclusive Write Lock**

```text
User A has exclusive lock on file
User B tries to modify → 423 Locked
```

**2. WebDAV File Editing**

```text
Document checked out for editing
Another user tries to edit → 423 Locked
```

**3. Administrative Lock**

```text
Resource locked by administrator
Regular user tries to modify → 423 Locked
```

**4. Processing Lock**

```text
File being processed (e.g., virus scan, conversion)
User tries to access → 423 Locked (temporary)
```

**5. Concurrent Edit Prevention**

```text
First user locks resource for editing
Second user attempts modification → 423 Locked
```

## Example Responses

**Basic WebDAV Lock:**

```http
HTTP/1.1 423 Locked
Content-Type: application/xml; charset=utf-8
Lock-Token: <urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4>

<?xml version="1.0" encoding="utf-8" ?>
<D:error xmlns:D="DAV:">
  <D:lock-token-submitted>
    <D:href>/documents/report.docx</D:href>
  </D:lock-token-submitted>
</D:error>
```

**With Lock Information:**

```http
HTTP/1.1 423 Locked
Content-Type: application/json
Lock-Token: <urn:uuid:a1b2c3d4-e5f6-4789-a012-3456789abcde>
X-Lock-Owner: alice@example.com
X-Lock-Timeout: 900

{
  "error": "Resource Locked",
  "message": "This resource is currently locked and cannot be modified",
  "lock_details": {
    "lock_token": "urn:uuid:a1b2c3d4-e5f6-4789-a012-3456789abcde",
    "locked_by": "alice@example.com",
    "locked_at": "2026-01-18T14:30:00Z",
    "lock_timeout": 900,
    "lock_expires_at": "2026-01-18T14:45:00Z",
    "lock_type": "exclusive"
  },
  "action": "Wait for lock to expire or contact the lock owner"
}
```

**Administrative Lock:**

```http
HTTP/1.1 423 Locked
Content-Type: application/json
X-Lock-Type: administrative
Retry-After: 3600

{
  "error": "Resource Locked",
  "message": "This resource is locked for administrative purposes",
  "lock_type": "administrative",
  "reason": "Security audit in progress",
  "locked_until": "2026-01-18T18:00:00Z",
  "contact": "admin@example.com"
}
```

## Real-World Example

Imagine two team members trying to edit the same document simultaneously:

**User Alice Locks Document:**

```http
LOCK /documents/quarterly-report.docx HTTP/1.1
Host: webdav.example.com
Content-Type: text/xml
Depth: 0
Timeout: Second-900

<?xml version="1.0" encoding="utf-8" ?>
<D:lockinfo xmlns:D="DAV:">
  <D:lockscope><D:exclusive/></D:lockscope>
  <D:locktype><D:write/></D:locktype>
  <D:owner>alice@example.com</D:owner>
</D:lockinfo>

Response:
HTTP/1.1 200 OK
Lock-Token: <urn:uuid:abc-123-def-456>
Content-Type: application/xml

<?xml version="1.0" encoding="utf-8" ?>
<D:prop xmlns:D="DAV:">
  <D:lockdiscovery>
    <D:activelock>
      <D:locktoken><D:href>urn:uuid:abc-123-def-456</D:href></D:locktoken>
      <D:timeout>Second-900</D:timeout>
    </D:activelock>
  </D:lockdiscovery>
</D:prop>
```

**User Bob Tries to Modify (Fails):**

```http
PUT /documents/quarterly-report.docx HTTP/1.1
Host: webdav.example.com
Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document
Content-Length: 45678

[Document content...]

Response:
HTTP/1.1 423 Locked
Content-Type: application/json
Lock-Token: <urn:uuid:abc-123-def-456>
X-Lock-Owner: alice@example.com

{
  "status": 423,
  "error": "Locked",
  "message": "Document is locked by another user",
  "details": {
    "resource": "/documents/quarterly-report.docx",
    "lock_token": "urn:uuid:abc-123-def-456",
    "locked_by": "alice@example.com",
    "locked_at": "2026-01-18T14:30:00Z",
    "lock_expires_at": "2026-01-18T14:45:00Z",
    "time_remaining": "14 minutes"
  },
  "actions": {
    "wait": "Wait for lock to expire in 14 minutes",
    "contact": "Contact alice@example.com to release lock",
    "force_unlock": "Admins can force unlock at /admin/locks/abc-123-def-456"
  }
}
```

**User Bob with Valid Lock Token (Succeeds):**

```http
PUT /documents/quarterly-report.docx HTTP/1.1
Host: webdav.example.com
If: (<urn:uuid:abc-123-def-456>)  ← Valid lock token
Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document

[Document content...]

Response:
HTTP/1.1 204 No Content
Lock-Token: <urn:uuid:abc-123-def-456>
```

## 423 vs Other Access Control Codes

| Code    | Meaning      | Cause                       | Can Retry?             |
| ------- | ------------ | --------------------------- | ---------------------- |
| **423** | Locked       | Resource locked (temporary) | Yes, when unlocked     |
| **403** | Forbidden    | No permission (permanent)   | No, need authorization |
| **401** | Unauthorized | Not authenticated           | Yes, with credentials  |
| **409** | Conflict     | State conflict              | Yes, after resolving   |

## Important Characteristics

**Temporary vs Permanent:**

```text
423 Locked: Temporary restriction
- Resource is locked by another user/process
- Will become available when unlocked
- Usually has timeout

403 Forbidden: Permanent restriction
- User lacks permission
- Won't change without authorization change
```

**Lock Types:**

```text
Exclusive Lock:
- Only lock holder can modify
- Others get 423 until lock released

Shared Lock:
- Multiple readers allowed
- Writers get 423
- Prevents writes, allows reads
```

**WebDAV Lock Headers:**

```http
Lock-Token: <urn:uuid:lock-token-id>
Timeout: Second-3600
Depth: 0 or infinity
If: (<lock-token>)  ← Required to modify locked resource
```

## Common Mistakes

**❌ Using 423 for permission issues**

```http
HTTP/1.1 423 Locked  ← Wrong! User lacks permission
Message: You don't have permission to access this resource

# Should be:
HTTP/1.1 403 Forbidden
Message: You don't have permission to access this resource
```

**❌ Not providing lock information**

```http
HTTP/1.1 423 Locked
Content-Type: text/plain

Resource is locked.  ← Unhelpful, no details about lock
```

**❌ Not implementing lock timeout**

```http
LOCK /resource → Lock created with no timeout
(User closes browser, lock never released)  ← Resource locked forever!
```

**✅ Correct usage with full information**

```http
HTTP/1.1 423 Locked
Content-Type: application/json
Lock-Token: <urn:uuid:lock-id>
Retry-After: 900

{
  "error": "Locked",
  "lock_owner": "user@example.com",
  "lock_expires_at": "2026-01-18T15:00:00Z",
  "retry_after": 900
}
```

## Getting 423 Locked right

**Always Include Lock Details:**

```http
HTTP/1.1 423 Locked
Content-Type: application/json
Lock-Token: <urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4>
X-Lock-Owner: alice@example.com
Retry-After: 600

{
  "error": "Resource Locked",
  "message": "This resource is currently locked",
  "lock": {
    "token": "urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4",
    "owner": "alice@example.com",
    "type": "exclusive",
    "scope": "write",
    "created_at": "2026-01-18T14:30:00Z",
    "expires_at": "2026-01-18T14:40:00Z",
    "timeout_seconds": 600
  },
  "suggestions": [
    "Wait for lock to expire",
    "Contact lock owner to release",
    "Try again after lock expiration"
  ]
}
```

**Implement Lock Timeouts:**

```javascript
// Express.js example
const locks = new Map()

app.lock('/documents/:id', (req, res) => {
  const docId = req.params.id
  const timeout = parseInt(req.headers.timeout?.replace('Second-', '')) || 900

  if (locks.has(docId)) {
    return res.status(423).json({
      error: 'Already locked',
      lock: locks.get(docId)
    })
  }

  const lockToken = `urn:uuid:${uuidv4()}`
  const expiresAt = Date.now() + timeout * 1000

  locks.set(docId, {
    token: lockToken,
    owner: req.user.email,
    expiresAt
  })

  // Auto-remove lock after timeout
  setTimeout(() => locks.delete(docId), timeout * 1000)

  res.status(200).set('Lock-Token', `<${lockToken}>`).json({ lockToken, expiresAt })
})

app.put('/documents/:id', (req, res) => {
  const docId = req.params.id
  const providedToken = req.headers.if?.match(/<(.+)>/)?.[1]

  const lock = locks.get(docId)

  if (lock) {
    if (lock.expiresAt < Date.now()) {
      locks.delete(docId) // Lock expired
    } else if (lock.token !== providedToken) {
      return res.status(423).json({
        error: 'Locked',
        lock_owner: lock.owner,
        lock_expires: new Date(lock.expiresAt)
      })
    }
  }

  // Proceed with update
  updateDocument(docId, req.body)
  res.status(204).send()
})
```

**Provide Lock Management Interface:**

```javascript
// View active locks
app.get('/admin/locks', adminOnly, (req, res) => {
  const activeLocks = Array.from(locks.entries()).map(([id, lock]) => ({
    resource_id: id,
    ...lock,
    expires_in_seconds: Math.max(0, (lock.expiresAt - Date.now()) / 1000)
  }))

  res.json({ locks: activeLocks })
})

// Force unlock (admin only)
app.delete('/admin/locks/:token', adminOnly, (req, res) => {
  const token = req.params.token

  for (const [id, lock] of locks.entries()) {
    if (lock.token === token) {
      locks.delete(id)
      return res.json({ message: 'Lock removed', resource_id: id })
    }
  }

  res.status(404).json({ error: 'Lock not found' })
})
```

## WebDAV Lock Implementation

**Creating a Lock:**

```http
LOCK /documents/report.docx HTTP/1.1
Host: webdav.example.com
Content-Type: text/xml
Depth: 0
Timeout: Second-3600

<?xml version="1.0"?>
<D:lockinfo xmlns:D="DAV:">
  <D:lockscope><D:exclusive/></D:lockscope>
  <D:locktype><D:write/></D:locktype>
  <D:owner>alice@example.com</D:owner>
</D:lockinfo>
```

**Refreshing a Lock:**

```http
LOCK /documents/report.docx HTTP/1.1
Host: webdav.example.com
If: (<urn:uuid:lock-token>)
Timeout: Second-1800
```

**Unlocking:**

```http
UNLOCK /documents/report.docx HTTP/1.1
Host: webdav.example.com
Lock-Token: <urn:uuid:lock-token>
```

## Implementation Examples

**Node.js/Express:**

```javascript
const locks = new Map()

function checkLock(req, res, next) {
  const resourceId = req.params.id
  const lock = locks.get(resourceId)

  if (!lock) return next()

  if (lock.expiresAt < Date.now()) {
    locks.delete(resourceId)
    return next()
  }

  const providedToken = req.headers.if?.match(/<(.+)>/)?.[1]

  if (lock.token !== providedToken) {
    return res.status(423).json({
      error: 'Locked',
      lock_token: lock.token,
      lock_owner: lock.owner,
      expires_at: new Date(lock.expiresAt).toISOString()
    })
  }

  next()
}

app.put('/files/:id', checkLock, (req, res) => {
  // Update file
  res.status(204).send()
})
```

**Python/Django:**

```python
from django.http import JsonResponse
from django.utils import timezone
from datetime import timedelta
import uuid

locks = {}

def check_lock(view_func):
    def wrapper(request, resource_id, *args, **kwargs):
        lock = locks.get(resource_id)

        if lock and lock['expires_at'] > timezone.now():
            provided_token = request.META.get('HTTP_IF', '').strip('<>')

            if lock['token'] != provided_token:
                return JsonResponse({
                    'error': 'Locked',
                    'lock_owner': lock['owner'],
                    'expires_at': lock['expires_at'].isoformat()
                }, status=423)

        return view_func(request, resource_id, *args, **kwargs)

    return wrapper

@check_lock
def update_resource(request, resource_id):
    # Update resource
    return JsonResponse({'status': 'updated'})
```

## Try It Yourself

Visit our [request builder](https://howhttpworks.com/tools/playground) to simulate locked resources:

1. Set method to **LOCK**
2. Set path to **/webdav/document.docx**
3. Click **Send request** to create lock
4. Try **PUT** to same resource without lock token
5. See 423 response with lock details

## Try it with curl

Write to a resource that another client holds a WebDAV lock on, without supplying the lock token.

```bash
curl -i -T report.docx https://dav.example.com/docs/report.docx
```

Example output (illustrative, not captured from a real server):

```http
HTTP/1.1 423 Locked
Content-Type: application/xml

<d:error xmlns:d="DAV:"><d:lock-token-submitted/></d:error>
```

`-T` uploads the file with `PUT`. To make the write succeed, send the token you received from `LOCK` in an `If: (<opaquelocktoken:...>)` header.

## Related Status Codes

- [403 Forbidden](https://howhttpworks.com/status-codes/403) - Permission denied (permanent)
- [409 Conflict](https://howhttpworks.com/status-codes/409) - Request conflicts with current state
- [412 Precondition Failed](https://howhttpworks.com/status-codes/412) - Conditional request failed
- [507 Insufficient Storage](https://howhttpworks.com/status-codes/507) - Server out of storage (WebDAV)
