Skip to content
Control Plane Labs

508 Loop Detected

Server error response defined by RFC 5842 §7.2.

Last updated July 27, 2026

Common causes at a glance

  • A BIND creating a cycle between two WebDAV collections
  • A COPY or MOVE with Depth: infinity crossing a bound collection
  • A deep PROPFIND over a tree containing a binding back to an ancestor
  • Symlink loops on the filesystem exposed through a DAV collection

Reproduce it

curl -i -X COPY -H 'Depth: infinity' -H 'Destination: /b/' https://dav.example.com/a/

What 508 Loop Detected means

WebDAV bindings let more than one path point at the same resource, which is essentially a hard link for the web. Once you have that, you can build a cycle: collection /a/ contains a binding to /b/, and /b/ contains one back to /a/. Any operation walking the tree with Depth: infinityCOPY, MOVE, PROPFIND — would recurse forever.

508 is the server saying it noticed. The important qualifier is that the whole operation failed: this is an abort, not a partial success reported per-resource in a 207 Multi-Status body. A client that treats 508 as advisory and retries will loop just as reliably. Like 506, this is a code you will never encounter unless you operate a WebDAV server with binding support.

What the spec says

RFC 5842 §7.2 defines 508, in the Experimental specification “Binding Extensions to WebDAV”. The same document explains why the code was needed: §2.1.1 describes bind loops and the requirement that servers detect them rather than recurse. Servers supporting bindings advertise the bind compliance class in the DAV response header to an OPTIONS request, per RFC 4918 §10.1. Because RFC 5842 is Experimental, support is inconsistent — a server without bindings cannot create the cycle and has no reason to implement the code, which is why 508 is absent from most stacks entirely.

What actually causes it

Cycles are almost always created accidentally by a BIND that links a collection into one of its own descendants, and nothing appears wrong until the first deep traversal runs. Backup jobs are the usual trigger, since they tend to be the only thing that walks the entire tree with Depth: infinity. A related non-WebDAV case is a filesystem symlink loop inside a directory exposed over DAV: the server walks it, notices the repetition, and reports 508 even though no binding was involved. Some proxies also return 508 for a forwarding loop, which is a misuse — those are detected with the Via field.

How to debug it

Narrow the depth until the operation succeeds. That isolates the level the cycle lives at:

for d in 0 1 infinity; do
printf '%s: ' "$d"
curl -sS -o /dev/null -w '%{http_code}\n' -X PROPFIND -H "Depth: $d" \
-H 'Content-Type: application/xml' https://dav.example.com/a/
done

If Depth: 1 works and infinity returns 508, walk the tree one level at a time and compare the DAV:resource-id of each collection; a resource-id repeated in a descendant is the binding that closes the loop. Confirm the server supports bindings with OPTIONS and look for bind in the DAV header — if absent, suspect a symlink loop instead. Remove the offending binding with UNBIND rather than deleting the collection, which would take the real resource with it.

Working examples

curl

# Does this server even implement bindings?
curl -sSI -X OPTIONS https://dav.example.com/ | grep -i '^dav:'

# Increase depth until the loop bites.
for d in 0 1 infinity; do
  printf '%s: ' "$d"
  curl -sS -o /dev/null -w '%{http_code}\n' -X PROPFIND -H "Depth: $d" \
    -H 'Content-Type: application/xml' https://dav.example.com/a/
done

Python (requests)

import requests

BODY = '<?xml version="1.0"?><propfind xmlns="DAV:"><prop><resource-id/></prop></propfind>'

for depth in ("0", "1", "infinity"):
    resp = requests.request(
        "PROPFIND",
        "https://dav.example.com/a/",
        headers={"Depth": depth, "Content-Type": "application/xml"},
        data=BODY,
        timeout=60,
    )
    print(depth, resp.status_code)
    if resp.status_code == 508:
        print("bind loop at this depth; compare resource-id values to find it")
        break

Node (fetch)

const body =
  '<?xml version="1.0"?><propfind xmlns="DAV:"><prop><resource-id/></prop></propfind>';

for (const depth of ["0", "1", "infinity"]) {
  const res = await fetch("https://dav.example.com/a/", {
    method: "PROPFIND",
    headers: { Depth: depth, "Content-Type": "application/xml" },
    body,
  });
  console.log(depth, res.status);
  if (res.status === 508) {
    console.error("cycle reached at Depth:", depth);
    break;
  }
}

Reference and tooling

The authoritative list of assigned status codes is theIANA HTTP Status Code Registry. For a searchable copy with the semantics summarised, use theHTTP status code reference on this site. To see exactly what a server is sending, theheader inspector shows the raw status line and response headers, and thecurl builder assembles the request flags for you.

Other 5xx codes