carrierwaveuploader/carrierwave · error · CarrierWave::DownloadError

could not download file: #

Error message

could not download file: #{e.message}

What it means

Raised as CarrierWave::DownloadError by CarrierWave::Downloader::Base#download! when the underlying Net::HTTP request to a remote URL fails for any reason (DNS failure, timeout, SSL error, or a non-2xx HTTP status, which response.value turns into an error). The downloader retries the request up to uploader.download_retry_count times (default 0) waiting download_retry_wait_time seconds (default 5) between attempts, then gives up and wraps the last exception message in this error.

Solutions

  1. Verify the URL actually returns the file with 2xx status (curl -I <url>) and fix or reject it
  2. Rescue CarrierWave::DownloadError where you assign the remote URL and show a friendly validation message
  3. Enable retries in an initializer: config.download_retry_count = 3 and config.download_retry_wait_time = 5
  4. For persistent failures, check DNS/firewall/proxy egress rules and TLS certificate validity from the app host

Example fix

# before
user.avatar = params[:avatar_url] # raises CarrierWave::DownloadError on bad URL

# after
begin
  user.avatar = params[:avatar_url]
rescue CarrierWave::DownloadError => e
  user.errors.add(:avatar_url, e.message)
end
Defensive patterns

Strategy: try-catch

Validate before calling

require 'net/http'
def downloadable?(url, timeout: 5)
  uri = URI.parse(url)
  return false unless uri.is_a?(URI::HTTP)
  res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', open_timeout: timeout, read_timeout: timeout) { |http| http.head(uri.request_uri) }
  res.is_a?(Net::HTTPSuccess) && (res['content-length'].nil? || res['content-length'].to_i > 0)
rescue StandardError
  false
end
return unless downloadable?(params[:avatar_url])

Try / catch

begin
  record.avatar = params[:avatar_url] # or uploader.download!(url)
rescue CarrierWave::DownloadError => e
  record.errors.add(:avatar_url, :download_failed, message: e.message)
end

Prevention

When it happens

Trigger: Calling uploader.download!('http://...') or assigning a remote URL attribute (e.g. user.avatar = 'http://example.com/photo.jpg' with remote_avatar_url form fields) where the host is unreachable, the URL returns 404/500 (response.value raises on non-2xx), the TLS certificate is invalid, or the connection times out after the retry budget is exhausted.

Common situations: Apps that let users upload 'avatar by URL'; the linked server is down or blocks the request; a redirect chain ends in an error; using default config.download_retry_count = 0 so even one transient network hiccup surfaces as this error.

Related errors


AI-assisted analysis of carrierwaveuploader/carrierwave@b5f0abe10e (2026-08-21). Data as JSON: /api/errors/3b071ceefca402a2. Report an issue: GitHub.

Appendix: source

Thrown at lib/carrierwave/downloader/base.rb:53

            if ::SsrfFilter::VERSION.to_f < 1.1
              response = SsrfFilter.get(uri, headers: headers) do |req|
                request = req
              end
            else
              response = SsrfFilter.get(uri, headers: headers, request_proc: ->(req) { request = req }) do |res|
                res.body # ensure to read body
              end
            end
            response.uri = request.uri
            response.value
          end
        rescue StandardError => e
          if @current_download_retry_count < @uploader.download_retry_count
            @current_download_retry_count += 1
            sleep @uploader.download_retry_wait_time
            retry
          else
            raise CarrierWave::DownloadError, "could not download file: #{e.message}"
          end
        end
        CarrierWave::Downloader::RemoteFile.new(response)
      end

      ##
      # Processes the given URL by parsing it, and escaping if necessary. Public to allow overriding.
      #
      # === Parameters
      #
      # [url (String)] The URL where the remote file is stored
      #
      def process_uri(source)
        uri = Addressable::URI.parse(source)
        uri.host = uri.normalized_host
        # Perform decode first, as the path is likely to be already encoded
        uri.path = encode_path(decode_uri(uri.path)) if uri.path =~ CarrierWave::Utilities::Uri::PATH_UNSAFE
        uri.query = encode_non_ascii(uri.query) if uri.query

View on GitHub (pinned to b5f0abe10e)