puppetlabs/puppet · error · OpenSSL::X509::CertificateError

Failed to parse CA certificates as PEM

Error message

Failed to parse CA certificates as PEM

What it means

Raised by CertProvider#load_cacerts_from_pem as OpenSSL::X509::CertificateError when the CA file exists but its text does not match CERT_DELIMITERS, i.e. it contains no '-----BEGIN CERTIFICATE-----' block. The same error class also surfaces from OpenSSL::X509::Certificate.new inside the scan block when an individual block is corrupt or truncated. Either way the trust store cannot be built and TLS setup aborts.

Source

Thrown at lib/puppet/x509/cert_provider.rb:77

    if !pem && required
      raise Puppet::Error, _("The CA certificates are missing from '%{path}'") % { path: @capath }
    end

    pem ? load_cacerts_from_pem(pem) : nil
  rescue SystemCallError => e
    raise Puppet::Error.new(_("Failed to load CA certificates from '%{capath}'") % { capath: @capath }, e)
  end

  # Load PEM encoded CA certificates.
  #
  # @param pem [String] PEM encoded certificate(s)
  # @return [Array<OpenSSL::X509::Certificate>] Array of CA certs
  # @raise [OpenSSL::X509::CertificateError] The `pem` text does not contain a valid cert
  #
  # @api private
  def load_cacerts_from_pem(pem)
    # TRANSLATORS 'PEM' is an acronym and shouldn't be translated
    raise OpenSSL::X509::CertificateError, _("Failed to parse CA certificates as PEM") if pem !~ CERT_DELIMITERS

    pem.scan(CERT_DELIMITERS).map do |text|
      OpenSSL::X509::Certificate.new(text)
    end
  end

  # Save `crls` to the configured `crlpath`.
  #
  # @param crls [Array<OpenSSL::X509::CRL>] Array of CRLs to save
  # @raise [Puppet::Error] if the CRLs cannot be saved
  #
  # @api private
  def save_crls(crls)
    save_pem(crls.map(&:to_pem).join, @crlpath, **permissions_for_setting(:hostcrl))
  rescue SystemCallError => e
    raise Puppet::Error.new(_("Failed to save CRLs to '%{crlpath}'") % { crlpath: @crlpath }, e)
  end

View on GitHub (pinned to e227c27540)

Solutions

  1. Inspect the file: `head ca.pem` must show BEGIN/END CERTIFICATE lines and `openssl x509 -in ca.pem -noout` must parse
  2. If it is DER, convert: `openssl x509 -inform der -in ca.der -outform pem -out ca.pem`
  3. Re-download the CA cert from the Puppet server CA and atomically overwrite the file
  4. Verify every BEGIN/END pair when multiple CAs are concatenated, then rerun `puppet agent -t`

Example fix

# before: file holds DER bytes
$ openssl x509 -in ca.pem -noout
unable to load certificate

# after
$ openssl x509 -inform der -in ca.der -outform pem -out ca.pem
$ openssl x509 -in ca.pem -noout
subject=CN=Puppet CA: puppet.example.com
Defensive patterns

Strategy: validation

Validate before calling

def pem_cert?(path)
  File.read(path).match?(/-----BEGIN CERTIFICATE-----/)
end

raise "#{Puppet[:localcacert]} is not PEM" unless pem_cert?(Puppet[:localcacert])

Try / catch

begin
  certs = Puppet::X509::CertProvider.new.load_cacerts(required: true)
rescue OpenSSL::X509::CertificateError => e
  abort "CA bundle at #{Puppet[:localcacert]} is not valid PEM: #{e.message}"
end

Prevention

When it happens

Trigger: load_cacerts(required: true) reads a localcacert whose PEM has no certificate delimiters, or a block OpenSSL cannot parse: binary DER data saved with a .pem name, an HTML/proxy error page captured instead of the cert, a truncated download, or cert and key files swapped.

Common situations: curl/wget fetch of the CA that hit a proxy login page; DER-formatted certs from a vendor; files mangled by Windows copies or templating that strips newlines; hand-editing ca.pem and losing the END line.

Understand the failure class

Related errors


AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21). Data as JSON: /api/errors/324ed2e01eeffed5. Report an issue: GitHub.