benbjohnson/litestream · error
webdav: cannot read file %q: %w
Error message
webdav: cannot read file %q: %w
What it means
OpenLTXFile with a known size uses client.ReadStreamRange to fetch only the requested byte range. If the range request fails and the object is not simply missing, the error is wrapped as 'cannot read file'. Note: os.ErrNotExist is returned unchanged when the server reports the file not found, so this error means a real read failure.
Source
Thrown at webdav/replica_client.go:317
}
func (c *ReplicaClient) OpenLTXFile(ctx context.Context, level int, minTXID, maxTXID ltx.TXID, offset, size int64) (_ io.ReadCloser, err error) {
client, err := c.init(ctx)
if err != nil {
return nil, err
}
filename := litestream.LTXFilePath(c.Path, level, minTXID, maxTXID)
internal.OperationTotalCounterVec.WithLabelValues(ReplicaClientType, "GET").Inc()
if size > 0 {
rc, err := client.ReadStreamRange(filename, offset, size)
if err != nil {
if os.IsNotExist(err) || gowebdav.IsErrNotFound(err) {
return nil, os.ErrNotExist
}
return nil, fmt.Errorf("webdav: cannot read file %q: %w", filename, err)
}
return internal.LimitReadCloser(rc, size), nil
}
if offset > 0 {
rc, err := client.ReadStream(filename)
if err != nil {
if os.IsNotExist(err) || gowebdav.IsErrNotFound(err) {
return nil, os.ErrNotExist
}
return nil, fmt.Errorf("webdav: cannot read file %q: %w", filename, err)
}
if _, err := io.CopyN(io.Discard, rc, offset); err != nil {
if err == io.EOF || err == io.ErrUnexpectedEOF {
_ = rc.Close()
return io.NopCloser(bytes.NewReader(nil)), nil
}View on GitHub (pinned to 4ed7a308f6)
Solutions
- Confirm the server supports Range requests (check for Accept-Ranges in a HEAD response)
- Check credentials and URL correctness in the litestream config
- Retry the restore; transient network failures are often intermittent
- Bypass proxies or configure them to pass Range headers through
Defensive patterns
Strategy: retry
Validate before calling
// verify server supports ranges before relying on partial reads
resp, err := http.Head(fileURL)
if err == nil && resp.Header.Get("Accept-Ranges") != "bytes" { log.Warn("WebDAV server lacks Range support") } Try / catch
rc, err := OpenLTXFile(ctx, info)
if err != nil {
if errors.Is(err, os.ErrNotExist) { /* object genuinely missing */ }
else if isTransient(err) { retry with backoff }
} Prevention
- Choose a WebDAV server with Range request support
- Configure proxies to pass Range headers
- Retry restores on transient network failures
When it happens
Trigger: ReadStreamRange (HTTP Range GET) returns a non-404 error: server not honoring Range headers, TLS failures, auth rejection, or connection errors during the ranged read.
Common situations: WebDAV server without Range support (litestream relies on it for partial reads), intermediate proxies stripping Range headers, expired sessions/token mid-restore.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
Related errors
- cannot specify url & path for webdav replica
- cannot specify url & webdav-url for webdav replica
- webdav-url required for webdav replica
- cannot read wal header: %w
- cannot read last synced wal page: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/aabf7adb2b26decd.
Report an issue: GitHub.