Retry mechanism for failed downloads
Provided I understand the intent of this code, I think the following may be cleaner:

public static class DownloadUtilities
{
    private const int RETRY_COUNT = 4;

    public static void DownloadLinks(Dictionary&lt;string, string&gt; files)
    {
        Parallel.ForEach(
            files,
            new ParallelOptions { MaxDegreeOfParallelism = 20 },
            DownloadLink);
    }

    private static void DownloadLink(KeyValuePair&lt;string, string&gt; link, bool retrying = false)
    {
        int retriesRemaining = retrying ? RETRY_COUNT : 1;

        while (retriesRemaining &gt; 0 &amp;&amp; !File.Exists(link.Value))
        {
            retriesRemaining -= 1;
            try
            {
                using (var webClient = new WebClient())
                {
                    webClient.Headers.Add("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0)");
                    webClient.DownloadFile(new Uri(link.Key), link.Value);
                }
            }
            catch (WebException e)
            {

                if (e.Status != WebExceptionStatus.ProtocolError)
                {
                    throw;
                }

                var response = ex.Response as HttpWebResponse;

                if (response.StatusCode != HttpStatusCode.GatewayTimeout || 
                    response.StatusCode != HttpStatusCode.Forbidden)
                {

                    Program.FailedDownloads.Add(link.Key);
                    Logger.Error($"Failed to download {link.Key}, unhandled response code.");
                    return;
                }

                Logger.Error($"Failed to download {link.Key}, {retriesRemaining} attempts remaining");
                Logger.Error(e.Message);
            }
        }

        if (!File.Exists(link.Value))
        {
            // Lets settle for the fact it can't download, and add it to the failed list.
            Program.FailedDownloads.Add(link.Key);
            Logger.Error($"Failed to download {link.Key}, retries expired.");
        }
    }
}


Here is a summary of the changes and their reasoning:


Replaced addition of string literals and variables with string interpolation, which is generally regarded as better practice.
Making use of the HttpStatusCode enum instead of matching on the response message. This shortens the code and does not require string matching.
Moving the retry logic into the DownloadLink method, rather than extracting into an auxiliary method, since this does not add to the code and complicates behaviour.