duplicati/duplicati · error · HttpRequestException
Failed to start upload session
Error message
Failed to start upload session
What it means
ChunkedUploadWithResumeAsync starts a resumable session by POSTing metadata; GCS must return a Location header with the upload URI. If the Location header is absent or empty, Duplicati throws HttpRequestException (Unknown error) 'Failed to start upload session' with the response StatusCode attached. A second, defensive Exception with the same message covers the unreachable case where the header existed but the returned value is still whitespace.
Source
Thrown at Duplicati/Library/Backend/GoogleServices/GoogleCommon.cs:117
/// <param name="readWriteTimeout">The read write timeout.</param>
/// <param name="method">The HTTP Method.</param>
/// <typeparam name="TRequest">The type of data to upload as metadata.</typeparam>
/// <typeparam name="TResponse">The type of data returned from the upload.</typeparam>
public static async Task<TResponse> ChunkedUploadWithResumeAsync<TRequest, TResponse>(JsonWebHelperHttpClient oauth, TRequest requestdata, string url, Stream stream, TimeSpan shortTimeout, TimeSpan readWriteTimeout, CancellationToken cancelToken, HttpMethod method)
where TRequest : class
where TResponse : class
{
using var req = await oauth.CreateRequestAsync(url, method, cancelToken);
if (requestdata != null)
req.Content = JsonContent.Create(requestdata);
req.Headers.Add("X-Upload-Content-Type", "application/octet-stream");
req.Headers.Add("X-Upload-Content-Length", stream.Length.ToString());
var uploaduri = await Utility.Utility.WithTimeout(shortTimeout, cancelToken, async ct =>
{
using var resp = await oauth.GetResponseAsync(req, HttpCompletionOption.ResponseContentRead, ct).ConfigureAwait(false);
if (!resp.Headers.TryGetValues("Location", out var locationValues) || string.IsNullOrWhiteSpace(locationValues.FirstOrDefault()))
throw new HttpRequestException(HttpRequestError.Unknown, "Failed to start upload session", null, resp.StatusCode);
return locationValues.First();
}).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(uploaduri))
throw new Exception("Failed to start upload session");
return await ChunkedUploadAsync<TResponse>(oauth, uploaduri, stream, shortTimeout, readWriteTimeout, cancelToken).ConfigureAwait(false);
}
/// <summary>
/// Helper method that performs a chunked upload, and queries for http status after each chunk
/// </summary>
/// <returns>The response item</returns>
/// <param name="oauth">The Oauth instance</param>
/// <param name="uploaduri">The resumeable uploaduri</param>
/// <param name="stream">The stream with data to upload.</param>
/// <param name="shortTimeout">The short request timeout.</param>
View on GitHub (pinned to 3f348be3e3)
Solutions
- Confirm the bucket exists and the account has storage.objects.create on it.
- Capture the initiation response status/body — a non-2xx here usually carries the real error.
- Ensure no intermediary (proxy/SDK wrapper) strips the Location response header.
- Retry once for transient 5xx; if it persists, escalate with the captured status code.
Example fix
// before
if (!resp.Headers.TryGetValues("Location", out var locationValues) || string.IsNullOrWhiteSpace(locationValues.FirstOrDefault()))
throw new HttpRequestException(HttpRequestError.Unknown, "Failed to start upload session", null, resp.StatusCode);
// after (include body + status for diagnosis)
if (!resp.Headers.TryGetValues("Location", out var locationValues) || string.IsNullOrWhiteSpace(locationValues.FirstOrDefault()))
{
var body = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
throw new HttpRequestException(HttpRequestError.Unknown,
$"Failed to start upload session (status {(int)resp.StatusCode}): {body}",
null, resp.StatusCode);
} Defensive patterns
Strategy: try-catch
Validate before calling
// Verify the account can create objects in the bucket before initiating // storage.objects.get on a known key, or check IAM roles/storage.objectCreator
Try / catch
try { await GoogleCommon.ChunkedUploadWithResumeAsync<...>(...); }
catch (HttpRequestException ex) when (ex.Message.Contains("Failed to start upload session"))
{
// status code carries the real cause (404 bucket, 403 perms, 5xx transient)
_logger.LogError(ex, "Upload session init failed with status {Status}", ex.StatusCode);
throw;
} Prevention
- Ensure the bucket exists and the account has storage.objects.create before uploads.
- Confirm no proxy strips the Location response header.
- Capture the session-init response body on failure for diagnosis.
When it happens
Trigger: The session-initiation POST returns 2xx but no Location header, or returns a non-2xx (the body of the if is only reached on the TryGetValues path). Common when auth/permission/quota prevents session creation but the response shape omits Location.
Common situations: Service account lacks permission to create objects in the bucket; bucket does not exist (so init 404s and no Location); resumable-upload feature disabled; a proxy strips the Location header; transient GCS error returning an empty body.
Related errors
- Upload succeeded, but no data was returned, status code: {0}
- Unexpected status code: {0}
- Upload succeeded prematurely. Uploaded: {0}, total size: {1}
- Upload succeeded, but no data was returned
- Put object failed
AI-assisted analysis of duplicati/duplicati@3f348be3e3 (2026-08-13).
Data as JSON: /api/errors/af4fd179e9f618ed.
Report an issue: GitHub.