opf/openproject · error · Import::JiraClient::ConnectionError

SSL error connecting to Jira server: %{message}

Error message

SSL error connecting to Jira server: %{message}

What it means

During download_attachment, OpenSSL::SSL::SSLError from the TLS handshake with the attachment host is wrapped into Import::JiraClient::ConnectionError with this message. Net::HTTP raises it when certificate verification fails (self-signed/untrusted CA, incomplete chain, hostname mismatch, expired certificate) or when TLS negotiation itself breaks; the OpenSSL detail stays in %{message}.

Source

Thrown at app/services/import/jira_client.rb:273

        case response
        when Net::HTTPSuccess
          tempfile = Tempfile.create(filename, binmode: true)
          response.read_body do |chunk|
            tempfile.write chunk
          end
          yield tempfile
        else
          status = response.code.to_i
          raise ApiError.new(I18n.t("admin.jira.client.api_error", status:), status:, response_body: response.body)
        end
      end
      nil
    rescue SsrfFilter::PrivateIPAddress
      raise SsrfError, I18n.t("admin.jira.client.ssrf_blocked")
    rescue SsrfFilter::Error => e
      raise ConnectionError, I18n.t("admin.jira.client.connection_error", message: e.message)
    rescue OpenSSL::SSL::SSLError => e
      raise ConnectionError, I18n.t("admin.jira.client.ssl_error", message: e.message)
    rescue Timeout::Error => e
      raise ConnectionError, I18n.t("admin.jira.client.connection_timeout", message: e.message)
    ensure
      File.unlink(tempfile) if tempfile
    end

    private

    def get(path, params: {})
      response = get_response(path, params:)
      handle_response(response)
    end

    def get_response(path, params: {})
      OpenProject::SsrfProtection.get(
        "#{@url}#{path}",
        headers: @headers,
        params:,

View on GitHub (pinned to d9742c43f3)

Solutions

  1. Inspect the certificate chain from the OpenProject host: openssl s_client -connect jira.host:443 -servername jira.host </dev/null and check the verify result
  2. Fix the server side first: complete chain (leaf + intermediates), hostname matching, certificate not expired
  3. For internal CAs, add the CA certificate to the OpenProject container/system trust store (Debian: copy to /usr/local/share/ca-certificates/ and run update-ca-certificates; also set NODE_EXTRA_CA_CERTS for Node-based tooling)
  4. Never work around it by disabling certificate verification — that removes the SSRF/TLS protections this client is built around

Example fix

# before — Jira uses an internal CA; downloads fail with
#   SSL error connecting to Jira server: ... certificate verify failed

# after — trust the internal CA inside the OpenProject image
# Dockerfile:
#   COPY jira-internal-ca.crt /usr/local/share/ca-certificates/
#   RUN update-ca-certificates
# docker-compose.yml (for Node-based parts of the stack):
environment:
  NODE_EXTRA_CA_CERTS: /usr/local/share/ca-certificates/jira-internal.crt
Defensive patterns

Strategy: try-catch

Try / catch

begin
  client.download_attachment(content_url, filename) { |tf| attach(tf) }
rescue Import::JiraClient::ConnectionError => e
  if e.message.start_with?("SSL error")
    report_cert_problem(host: URI.parse(content_url).host, detail: e.message)
  end
  raise
end

Prevention

When it happens

Trigger: client.download_attachment fetching an https attachment URL where the server presents a self-signed or internal-CA certificate, is missing intermediate certificates, has a CN/SAN not matching the host, has an expired certificate, or only supports TLS versions the client rejects.

Common situations: Self-hosted Jira behind a reverse proxy with an internal PKI certificate not trusted by the OpenProject container's CA store; admins renewing certs but forgetting intermediates; lab environments using self-signed certs; cert renewed but proxy still serving the old one.

Understand the failure class

Related errors


AI-assisted analysis of opf/openproject@d9742c43f3 (2026-08-21). Data as JSON: /api/errors/0aaa510f1681f845. Report an issue: GitHub.