{"record":{"id":"878a22a5c2e22ef3","repo":"puppetlabs/puppet","slug":"the-certificate-certname-must-be-cleaned-from-t","errorCode":null,"errorMessage":"The certificate %{certname} must be cleaned from the CA first. To fix this,\nrun the following commands on the CA:\n  puppetserver ca clean --certname %{certname}\n  puppet ssl clean","messagePattern":"The certificate %(.+?) must be cleaned from the CA first\\. To fix this,\nrun the following commands on the CA:\n  puppetserver ca clean --certname %(.+?)\n  puppet ssl clean","errorType":"exception","errorClass":"Puppet::Error","httpStatus":null,"severity":"error","filePath":"lib/puppet/application/ssl.rb","lineNumber":278,"sourceCode":"  def clean(certname)\n    # make sure cert has been removed from the CA\n    if certname == Puppet[:ca_server]\n      cert = nil\n\n      begin\n        ssl_context = @machine.ensure_ca_certificates\n        route = create_route(ssl_context)\n        _, cert = route.get_certificate(certname, ssl_context: ssl_context)\n      rescue Puppet::HTTP::ResponseError => e\n        if e.response.code.to_i != 404\n          raise Puppet::Error.new(_(\"Failed to connect to the CA to determine if certificate %{certname} has been cleaned\") % { certname: certname }, e)\n        end\n      rescue => e\n        raise Puppet::Error.new(_(\"Failed to connect to the CA to determine if certificate %{certname} has been cleaned\") % { certname: certname }, e)\n      end\n\n      if cert\n        raise Puppet::Error, _(<<~END) % { certname: certname }\n          The certificate %{certname} must be cleaned from the CA first. To fix this,\n          run the following commands on the CA:\n            puppetserver ca clean --certname %{certname}\n            puppet ssl clean\n        END\n      end\n    end\n\n    paths = {\n      'private key' => Puppet[:hostprivkey],\n      'public key' => Puppet[:hostpubkey],\n      'certificate request' => Puppet[:hostcsr],\n      'certificate' => Puppet[:hostcert],\n      'private key password file' => Puppet[:passfile]\n    }\n    if options[:localca]\n      paths['local CA certificate'] = Puppet[:localcacert]\n      paths['local CRL'] = Puppet[:hostcrl]","sourceCodeStart":260,"sourceCodeEnd":296,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/application/ssl.rb#L260-L296","documentation":"Raised by `Puppet::Application::SSL#clean`: before deleting local files it queries the CA with `route.get_certificate(certname)`. If the CA still returns a certificate (any response other than 404), local cleaning is aborted because the CA would keep serving the old identity and the host would immediately get its stale certificate back. Note that connect failures raise a different 'Failed to connect' error, so this error specifically means the certificate is still present on the CA.","triggerScenarios":"Running `puppet ssl clean` while the CA still has a signed certificate for the certname; the rescue clauses let a 404 pass through (cert already cleaned) and turn non-404 HTTP errors into the connect-failure error instead.","commonSituations":"Rotating a compromised or mis-keyed host where the operator wiped the local ssl dir but forgot the CA; rebuilding a node with the same certname; decommission/recommission flows that skip the CA step.","solutions":["On the CA host: `puppetserver ca clean --certname <certname>` (removes the cert, CSR, and serial)","Then on the agent: `puppet ssl clean` — get_certificate now 404s and local files are removed","Re-run `puppet ssl bootstrap` to request a fresh certificate","If you cannot use the CA CLI, clean via the CA API and wait for it to take effect before retrying"],"exampleFix":"# before (agent only)\npuppet ssl clean\n# => The certificate node1 must be cleaned from the CA first...\n# after\n# (on the CA host)\npuppetserver ca clean --certname node1.example.com\n# (on the agent)\npuppet ssl clean && puppet ssl bootstrap","handlingStrategy":"try-catch","validationCode":"# verify the CA no longer has the cert (404) before cleaning locally\nsession = Puppet.lookup(:http_session)\nssl_context = machine.ensure_ca_certificates\nroute = session.route_to(:puppet)\nbegin\n  _ignored, cert = route.get_certificate(certname, ssl_context: ssl_context)\n  abort 'CA still serves the certificate; run puppetserver ca clean first' if cert\nrescue Puppet::HTTP::ResponseError => e\n  raise unless e.response.code.to_i == 404  # 404 = safe to clean\nend","typeGuard":null,"tryCatchPattern":"begin\n  Puppet::Application[:ssl].run unless ARGV.empty?\nrescue Puppet::Error => e\n  if e.message.include?('must be cleaned from the CA first')\n    # orchestrate: clean on CA, then retry the local clean once\n    run_on_ca('puppetserver ca clean --certname', certname)\n    retry\n  end\n  raise\nend","preventionTips":["Make CA-side cleanup the first step of every certificate rotation runbook","Script rotation as: clean on CA, then `puppet ssl clean`, then `puppet ssl bootstrap`","Never delete only the agent's ssl directory and assume the CA forgets the host"],"tags":["puppet-ssl","certificate-cleanup","ca","clean"],"backgroundTag":"certificate-cleanup-required","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}