Fix for the broad Cloudflare hint regex (bare numbers

6003/6111/9103/9109/10000, plus 'Authentication error' and 'Invalid
access token') is checked before the rate-limit, DNS and HTTP hints, so
non-Cloudflare failures get a misleading Cloudflare message.
This commit is contained in:
Jamie Curnow committed 2026-11-09 22:14:07 +10:00
1 parent fff2b0d097
commit 9b1566aa6b
1 file changed
+25 -12
+25 -12
View File
@@ -41,7 +41,10 @@ const certbotNoise = [
/^\d+ renew failure\(s\)/,
];
// Plain-language hints for failures that are common and fixable outside NPM
// Plain-language hints for failures that are common and fixable outside NPM.
// [pattern, hint, dnsProvider?]: a hint with a dnsProvider only applies to certificates using that
// provider, so a generic message (e.g. "Authentication error" from another DNS plugin) isn't
// reported as a Cloudflare problem.
const certbotHints = [
[
/expected \S+ to be a symlink/i,
@@ -53,21 +56,26 @@ const certbotHints = [
() =>
"This certificate's files in /etc/letsencrypt are damaged or missing. Restore /etc/letsencrypt from a backup, or delete this certificate and create it again.",
],
// Checked first: a rate limit is unambiguous and the most useful thing to report
[
/too many (certificates|failed authorizations|new orders)|rateLimited/i,
() => "Let's Encrypt rate limit reached. Wait before retrying (see https://letsencrypt.org/docs/rate-limits/).",
],
[
/\b9109\b.*?location: ([0-9a-f.:]+)/i,
(m) => {
const ip = m[1].replace(/\.+$/, "");
return `Cloudflare refused the API token from this server's public IP (${ip}). The token has Client IP Address Filtering: add ${ip} to the token in Cloudflare (My Profile > API Tokens), or remove the filter, then renew again.`;
},
"cloudflare",
],
[
/\b(6003|6111|9103|9109|10000)\b|Invalid request headers|Invalid access token|Authentication error/i,
// certbot-dns-cloudflare errors read "<context>: <code> <message>", e.g.
// "Error determining zone_id: 9109 Invalid access token."
/: (6003|6111|9103|9109|10000) [A-Za-z]|Invalid request headers|Invalid access token|Authentication error/i,
() =>
"Cloudflare rejected the API token. Check the token value and that it has Zone > DNS > Edit for this domain's zone.",
],
[
/too many (certificates|failed authorizations|new orders)|rateLimited/i,
() => "Let's Encrypt rate limit reached. Wait before retrying (see https://letsencrypt.org/docs/rate-limits/).",
"cloudflare",
],
[
/NXDOMAIN|DNS problem/i,
@@ -85,9 +93,10 @@ const certbotHints = [
* Certbot never prints DNS provider credentials.
*
* @param {String} stderr
* @param {String} [dnsProvider] The certificate's DNS provider, if it uses a DNS challenge
* @returns {String}
*/
const certbotFailureMessage = (stderr) => {
const certbotFailureMessage = (stderr, dnsProvider) => {
const reason = `${stderr || ""}`
.split("\n")
.map((line) => line.trim())
@@ -95,7 +104,10 @@ const certbotFailureMessage = (stderr) => {
.join(" ")
.slice(0, 800);
for (const [pattern, hint] of certbotHints) {
for (const [pattern, hint, provider] of certbotHints) {
if (provider && provider !== dnsProvider) {
continue;
}
const match = reason.match(pattern);
if (match) {
return `${hint(match)} (Certbot: ${reason})`;
@@ -109,14 +121,15 @@ const certbotFailureMessage = (stderr) => {
*
* @param {Array} args
* @param {Object} [opts]
* @param {String} [dnsProvider] Passed through so provider-specific hints only show for that provider
* @returns {Promise}
*/
const runCertbot = async (args, opts) => {
const runCertbot = async (args, opts, dnsProvider) => {
try {
return await utils.execFile(certbotCommand, args, opts);
} catch (err) {
if (err instanceof error.CommandError) {
throw new error.ValidationError(certbotFailureMessage(err.message), err);
throw new error.ValidationError(certbotFailureMessage(err.message, dnsProvider), err);
}
throw err;
}
@@ -1024,7 +1037,7 @@ const internalCertificate = {
logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`);
try {
const result = await runCertbot(args, adds.opts);
const result = await runCertbot(args, adds.opts, certificate.meta.dns_provider);
logger.info(result);
return result;
} finally {
@@ -1204,7 +1217,7 @@ const internalCertificate = {
logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`);
try {
const result = await runCertbot(args, adds.opts);
const result = await runCertbot(args, adds.opts, certificate.meta.dns_provider);
logger.info(result);
return result;
} finally {