Skip to content

Instantly share code, notes, and snippets.

Created February 28, 2020 15:45
Show Gist options
  • Save Radiergummi/bd7a7298a6d07e76912daa06a72707c5 to your computer and use it in GitHub Desktop.
Save Radiergummi/bd7a7298a6d07e76912daa06a72707c5 to your computer and use it in GitHub Desktop.
Updates a custom certificate on Cloudflare from inside a Let's Encrypt renewal hook. Check the blog post at for more info..
#!/usr/bin/env php
// This should be the main domain your certificate is valid for. It's used for reverse
// matching the installed certificate and resolving the file path to your certificate
// files on the local file system.
$domain = '';
// This should be your Cloudflare zone ID. You can find it in the right sidebar on your
// Cloudflaare account dashboard.
$cloudflareZone = 'your_cloudflare_zone';
// This should be the same settings file used for the Let's Encrypt Cloudflare plugin.
// See
$settings = parse_ini_file('/etc/letsencrypt/cloudflare.ini');
out('Checking for existing certificates...');
// Create a request to fetch all installed certificates.
// See
$ch = curl_init("{$cloudflareZone}/custom_certificates");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// As soon as the Let's Encrypt Cloudflare DNS plugin supports the "new" tokens, you
// should use them instead of auth email and key.
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-Auth-Email: ' . $settings['dns_cloudflare_email'],
'X-Auth-Key: ' . $settings['dns_cloudflare_api_key'],
'Content-Type: application/json',
$response = json_decode(curl_exec($ch), true);
$certificates = $response['result'] ?? [];
out('Found %d certificates.', count($certificates));
$certificateId = null;
// Iterate the certificates, try to find ours
foreach ($certificates as $certificate) {
if (in_array($domain, $certificate['hosts'], true)) {
$certificateId = $certificate['id'];
out('Found existing certificate %s for %s', $certificateId, $domain);
// If we have an existing certificate, we'll want to PATCH the certificate,
// otherwise we POST to create a new one
$requestMethod = $certificateId ? 'PATCH' : 'POST';
// Right-trim the URL to remove the last slash if we don't have an existing certificate ID
$url = rtrim("{$cloudflareZone}/custom_certificates/{$certificateId}", '/');
// Read certificate and private key from the default letsencrypt directories. You should obviously
// adjust these paths if you have non-standard locations.
$certificate = file_get_contents("/etc/letsencrypt/live/{$domain}/fullchain.pem");
$privateKey = file_get_contents("/etc/letsencrypt/live/{$domain}/privkey.pem");
// Review the documentation for these parameters here:
$payload = json_encode([
'certificate' => $certificate,
'private_key' => $privateKey,
'bundle_method' => 'optimal',
'type' => 'sni_custom'
if ($certificateId) {
out('Updating existing certificate %s on Cloudflare', $certificateId);
} else {
out('Dispatching new certificate to Cloudflare');
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $requestMethod);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-Auth-Email: ' . $settings['dns_cloudflare_email'],
'X-Auth-Key: ' . $settings['dns_cloudflare_api_key'],
'Content-Type: application/json',
$response = json_decode(curl_exec($ch), true);
if ($response['success'] ?? false) {
out('Successfully set certificate %s', $response['result']['id'] ?? '-');
error('Failed to update certificate: %s', json_encode($response, JSON_PRETTY_PRINT));
function out(string $line, ...$replacements): void
write(STDOUT, $line, $replacements);
function error(string $line, ...$replacements): void
write(STDERR, $line, $replacements);
function write($stream, string $line, $replacements = []): void
$microTime = explode(" ",microtime());
$timestamp = date("d.m.Y H:i:s", $microTime[1]) . substr((string) $microTime[0], 1, 4);
fwrite($stream, $timestamp . "\t " . vsprintf($line, $replacements) . PHP_EOL);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment