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: infinity — COPY, 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
- 500 Internal Server Error
- 501 Not Implemented
- 502 Bad Gateway
- 503 Service Unavailable
- 504 Gateway Timeout
- 505 HTTP Version Not Supported
- 506 Variant Also Negotiates
- 507 Insufficient Storage
- 510 Not Extended
- 511 Network Authentication Required
- 520 Web Server Returns an Unknown Error
- 521 Web Server Is Down
- 522 Connection Timed Out
- 523 Origin Is Unreachable
- 524 A Timeout Occurred
- 525 SSL Handshake Failed
- 526 Invalid SSL Certificate
- 530 Unable to Resolve Origin Hostname