Skip to the content.

curl –retry in C#

To retry a request in C# the way curl does, add --retry N to the command you pass to Curl.ExecuteAsync. CurlDotNet then repeats the transfer up to N more times when it fails with a transient error, waiting between attempts like curl: one second first, doubling each time, capped at ten minutes, and honouring a Retry-After header from the server.

The curl command

using CurlDotNet;

// Up to 3 retries for timeouts, DNS failures and HTTP 408, 429, 500, 502, 503 and 504
var result = await Curl.ExecuteAsync("curl --retry 3 -m 10 https://httpbin.org/status/200");
Console.WriteLine(result.StatusCode);   // 200

-m / --max-time limits each attempt, not the whole sequence. Use --retry-max-time to cap the total time spent retrying.

What is retried

Failure Retried with --retry Also needs
Timeout (-m expired or --speed-limit tripped, exit 28) yes  
Host or proxy name did not resolve (exit 6, 5) yes  
HTTP 408, 429, 500, 502, 503, 504 yes  
Connection failed (exit 7) no --retry-connrefused (CurlDotNet retries any connect failure with it; curl only a refused connection)
Any other error (other 4xx/5xx with -f, TLS, …) no --retry-all-errors

Transient HTTP statuses are retried with or without -f. When the last attempt still gets, say, a 503, the result depends on -f: without it you get a normal CurlResult with StatusCode 503; with it CurlHttpReturnedErrorException (exit 22) is thrown.

using CurlDotNet;
using CurlDotNet.Exceptions;

try
{
    var result = await Curl.ExecuteAsync(
        "curl -f --retry 4 --retry-connrefused --retry-max-time 60 https://httpbin.org/status/503");
    Console.WriteLine(result.Body);
}
catch (CurlHttpReturnedErrorException ex)
{
    Console.WriteLine($"Still failing after retries: HTTP {ex.StatusCode}");
}
catch (CurlCouldntConnectException ex)
{
    Console.WriteLine($"Server unreachable: {ex.Message}");
}
catch (CurlTimeoutException)
{
    Console.WriteLine("Gave up: the last attempt timed out");
}

The retry options

Option Meaning
--retry N Retry up to N times after the first attempt.
--retry-delay SECONDS Wait this long between attempts instead of the doubling back-off. Decimals work: --retry-delay 0.5 (a CurlDotNet extension; the curl tool takes whole seconds).
--retry-max-time SECONDS Stop retrying once this many seconds have passed since the first attempt, or when a server’s Retry-After wait would go past that point.
--retry-all-errors Retry every failure, not only the transient ones.
--retry-connrefused Also retry when the connection cannot be made.

The waits without --retry-delay are 1, 2, 4, 8 … seconds, up to 600. Like curl, the wait before each retry is the larger of that delay (or your --retry-delay) and the server’s Retry-After: <seconds> header, capped at 600 seconds; Retry-After: 0 is treated as no hint. Only the seconds form of Retry-After is read, not an HTTP date.

When --retry-max-time runs out after a failed attempt, the last attempt’s own result or exception stands, exactly as in curl. -w output is written once, for the final attempt, and %{num_retries} reports how many retries happened.

Retries without a command string

CurlOptions has the same settings as properties, so you can build the request without any quoting:

using CurlDotNet;
using CurlDotNet.Core;

var options = new CurlOptions
{
    Url = "https://httpbin.org/get",
    Retry = 3,
    RetryDelay = 2,          // seconds; 0 means curl's doubling back-off
    RetryMaxTime = 30,
    RetryConnRefused = true,
    MaxTime = 10
};

var result = await Curl.ExecuteAsync(options);
Console.WriteLine(result.StatusCode);

CurlSettings.WithRetries(count, delayMs) is the .NET-style shortcut. It retries any failure (it turns on --retry-all-errors) with a fixed delay:

using CurlDotNet;
using CurlDotNet.Core;

var settings = new CurlSettings().WithRetries(3, 500);   // 3 retries, 500 ms apart
var result = await Curl.ExecuteAsync("curl https://httpbin.org/get", settings);
Console.WriteLine(result.StatusCode);

Downloads and output files

With -o, a failed attempt that already wrote body bytes is truncated before the next attempt, as in curl, so the file ends up holding one complete copy. An attempt that wrote only headers (for example -i -f on an error status) is left in the file.

To continue a large download that stopped in an earlier run instead of starting over, run the command again with -C -, which resumes from the size of the existing file:

using CurlDotNet;

var result = await Curl.ExecuteAsync("curl --retry 5 -C - -o big.iso https://example.com/big.iso");
Console.WriteLine(result.OutputFiles[0]);

See curl download file in C# for -C - and the other output options.

Gotchas