HTTP 400 "Bad Request" error using certbot-dns-linode

Hello all,

Before today I have successfully used Let's Encrypt with certbot on Ubuntu with the Linode DNS plugin, but now (on a new installation) I am receiving an HTTP 400 error.

This is on Ubuntu 22.04 with certbot 2.6.0, and the Linode API key has R/W access to domains. These are the commands I have run to setup the environment and request the certificate.

sudo vi /root/.linode_api.ini
# Added following lines (uncommented):
# dns_linode_key = <key redacted>
# dns_linode_version = 4
sudo chmod 600 /root/.linode_api.ini

sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-linode

sudo certbot certonly --dns-linode --dns-linode-credentials /root/.linode_api.ini --dns-linode-propagation-seconds 180 -d everythingcode.net -d "*.everythingcode.net"

Which gives the following output:

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator dns-linode, Installer None
Requesting a certificate for *.everythingcode.net
Performing the following challenges:
dns-01 challenge for everythingcode.net
Cleaning up challenges
Error adding TXT record: 400 Client Error: BAD REQUEST for url: https://api.linode.com/v4/domains/2675616/records

Running the cert request command again with -v argument yields the following in the debug log:

2023-08-08 00:11:53,904:DEBUG:urllib3.connectionpool:http://localhost:None "GET /v2/connections?snap=certbot&interface=content HTTP/1.1" 200 1491
2023-08-08 00:11:54,172:DEBUG:certbot._internal.main:certbot version: 2.6.0
2023-08-08 00:11:54,172:DEBUG:certbot._internal.main:Location of certbot entry point: /snap/certbot/3024/bin/certbot
2023-08-08 00:11:54,172:DEBUG:certbot._internal.main:Arguments: ['-v', '--dns-linode', '--dns-linode-credentials', '/root/.linode_api.ini', '--dns-linode-propagation-seconds', '180', '-d', 'everythingcode.net', '-d', '*.everythingcode.net', '--preconfigured-renewal']
2023-08-08 00:11:54,173:DEBUG:certbot._internal.main:Discovered plugins: PluginsRegistry(PluginEntryPoint#apache,PluginEntryPoint#dns-linode,PluginEntryPoint#manual,PluginEntryPoint#nginx,PluginEntryPoint#null,PluginEntryPoint#standalone,PluginEntryPoint#webroot)
2023-08-08 00:11:54,184:DEBUG:certbot._internal.log:Root logging level set at 20
2023-08-08 00:11:54,184:DEBUG:certbot._internal.plugins.selection:Requested authenticator dns-linode and installer None
2023-08-08 00:11:54,186:DEBUG:certbot._internal.plugins.selection:Single candidate plugin: * dns-linode
Description: Obtain certificates using a DNS TXT record (if you are using Linode for DNS).
Interfaces: Authenticator, Plugin
Entry point: dns-linode = certbot_dns_linode._internal.dns_linode:Authenticator
Initialized: <certbot_dns_linode._internal.dns_linode.Authenticator object at 0x7fc8a83afc70>
Prep: True
2023-08-08 00:11:54,187:DEBUG:certbot._internal.plugins.selection:Selected authenticator <certbot_dns_linode._internal.dns_linode.Authenticator object at 0x7fc8a83afc70> and installer None
2023-08-08 00:11:54,187:INFO:certbot._internal.plugins.selection:Plugins selected: Authenticator dns-linode, Installer None
2023-08-08 00:11:54,233:DEBUG:certbot._internal.main:Picked account: <Account(RegistrationResource(body=Registration(key=None, contact=(), agreement=None, status=None, terms_of_service_agreed=None, only_return_existing=None, external_account_binding=None), uri='https://acme-v02.api.letsencrypt.org/acme/acct/1246256526', new_authzr_uri=None, terms_of_service=None), 9b39dde324e959b22d17b26d19c85b90, Meta(creation_dt=datetime.datetime(2023, 8, 7, 19, 40, 46, tzinfo=<UTC>), creation_host='www.everythingcode.com', register_to_eff=None))>
2023-08-08 00:11:54,233:DEBUG:acme.client:Sending GET request to https://acme-v02.api.letsencrypt.org/directory.
2023-08-08 00:11:54,235:DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): acme-v02.api.letsencrypt.org:443
2023-08-08 00:11:54,314:DEBUG:urllib3.connectionpool:https://acme-v02.api.letsencrypt.org:443 "GET /directory HTTP/1.1" 200 752
2023-08-08 00:11:54,315:DEBUG:acme.client:Received response:
HTTP 200
Server: nginx
Date: Tue, 08 Aug 2023 00:11:54 GMT
Content-Type: application/json
Content-Length: 752
Connection: keep-alive
Cache-Control: public, max-age=0, no-cache
X-Frame-Options: DENY
Strict-Transport-Security: max-age=604800

{
  "keyChange": "https://acme-v02.api.letsencrypt.org/acme/key-change",
  "meta": {
    "caaIdentities": [
      "letsencrypt.org"
    ],
    "termsOfService": "https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf",
    "website": "https://letsencrypt.org"
  },
  "newAccount": "https://acme-v02.api.letsencrypt.org/acme/new-acct",
  "newNonce": "https://acme-v02.api.letsencrypt.org/acme/new-nonce",
  "newOrder": "https://acme-v02.api.letsencrypt.org/acme/new-order",
  "pthZqY_sHUk": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417",
  "renewalInfo": "https://acme-v02.api.letsencrypt.org/draft-ietf-acme-ari-01/renewalInfo/",
  "revokeCert": "https://acme-v02.api.letsencrypt.org/acme/revoke-cert"
}
2023-08-08 00:11:54,315:DEBUG:certbot._internal.display.obj:Notifying user: Requesting a certificate for everythingcode.net and *.everythingcode.net
2023-08-08 00:11:54,319:DEBUG:acme.client:Requesting fresh nonce
2023-08-08 00:11:54,319:DEBUG:acme.client:Sending HEAD request to https://acme-v02.api.letsencrypt.org/acme/new-nonce.
2023-08-08 00:11:54,347:DEBUG:urllib3.connectionpool:https://acme-v02.api.letsencrypt.org:443 "HEAD /acme/new-nonce HTTP/1.1" 200 0
2023-08-08 00:11:54,347:DEBUG:acme.client:Received response:
HTTP 200
Server: nginx
Date: Tue, 08 Aug 2023 00:11:54 GMT
Connection: keep-alive
Cache-Control: public, max-age=0, no-cache
Link: <https://acme-v02.api.letsencrypt.org/directory>;rel="index"
Replay-Nonce: 371CakHRF4mmHIY6B2UV_hw2_LwC5urwSJb7dWgXoonLXVA
X-Frame-Options: DENY
Strict-Transport-Security: max-age=604800


2023-08-08 00:11:54,347:DEBUG:acme.client:Storing nonce: 371CakHRF4mmHIY6B2UV_hw2_LwC5urwSJb7dWgXoonLXVA
2023-08-08 00:11:54,348:DEBUG:acme.client:JWS payload:
b'{\n  "identifiers": [\n    {\n      "type": "dns",\n      "value": "everythingcode.net"\n    },\n    {\n      "type": "dns",\n      "value": "*.everythingcode.net"\n    }\n  ]\n}'
2023-08-08 00:11:54,350:DEBUG:acme.client:Sending POST request to https://acme-v02.api.letsencrypt.org/acme/new-order:
{
  "protected": "eyJhbGciOiAiUlMyNTYiLCAia2lkIjogImh0dHBzOi8vYWNtZS12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTI0NjI1NjUyNiIsICJub25jZSI6ICIzNzFDYWtIUkY0bW1ISVk2QjJVVl9odzJfTHdDNXVyd1NKYjdkV2dYb29uTFhWQSIsICJ1cmwiOiAiaHR0cHM6Ly9hY21lLXYwMi5hcGkubGV0c2VuY3J5cHQub3JnL2FjbWUvbmV3LW9yZGVyIn0",
  "signature": "gmMwlp92T7FzLq4OjKveLWWkVCxmRlEFGmoCeqQPNs3RgFMdnnyJjPswIU2a2-4FsYbCchV87grn03TcCvGh_gT3bcUrK6_gmFd0pGGjVi9fhc9S62YtBCmtkvK9sSs9RQdT1tMABAQ39hLUV111s-etAsuhHv3Fgg9nnbb0bEIr546wTGNe9OdztsopeMnOEKHri8XYFsxmbG0Br-GGFISHk6_TDmkokEB-4624YZS0X3a42hW3aQdhD88yK4Ry0_5yyepKjCEyDK5qwckLo2ZohBJP2OJF1v0jqSy3PeFtKB8UsxBBx7cDSw7t2eAJYgu9WIKY6scnG5Z3_gfMtA",
  "payload": "ewogICJpZGVudGlmaWVycyI6IFsKICAgIHsKICAgICAgInR5cGUiOiAiZG5zIiwKICAgICAgInZhbHVlIjogImV2ZXJ5dGhpbmdjb2RlLm5ldCIKICAgIH0sCiAgICB7CiAgICAgICJ0eXBlIjogImRucyIsCiAgICAgICJ2YWx1ZSI6ICIqLmV2ZXJ5dGhpbmdjb2RlLm5ldCIKICAgIH0KICBdCn0"
}
2023-08-08 00:11:54,557:DEBUG:urllib3.connectionpool:https://acme-v02.api.letsencrypt.org:443 "POST /acme/new-order HTTP/1.1" 201 487
2023-08-08 00:11:54,557:DEBUG:acme.client:Received response:
HTTP 201
Server: nginx
Date: Tue, 08 Aug 2023 00:11:54 GMT
Content-Type: application/json
Content-Length: 487
Connection: keep-alive
Boulder-Requester: 1246256526
Cache-Control: public, max-age=0, no-cache
Link: <https://acme-v02.api.letsencrypt.org/directory>;rel="index"
Location: https://acme-v02.api.letsencrypt.org/acme/order/1246256526/200128362756
Replay-Nonce: 891FpLuWBqLilf0jXmYIP5mq8CyRi8qOLhHdiJ2monJ0a10
X-Frame-Options: DENY
Strict-Transport-Security: max-age=604800

{
  "status": "pending",
  "expires": "2023-08-14T19:40:48Z",
  "identifiers": [
    {
      "type": "dns",
      "value": "*.everythingcode.net"
    },
    {
      "type": "dns",
      "value": "everythingcode.net"
    }
  ],
  "authorizations": [
    "https://acme-v02.api.letsencrypt.org/acme/authz-v3/252867756156",
    "https://acme-v02.api.letsencrypt.org/acme/authz-v3/252867756166"
  ],
  "finalize": "https://acme-v02.api.letsencrypt.org/acme/finalize/1246256526/200128362756"
}
2023-08-08 00:11:54,557:DEBUG:acme.client:Storing nonce: 891FpLuWBqLilf0jXmYIP5mq8CyRi8qOLhHdiJ2monJ0a10
2023-08-08 00:11:54,558:DEBUG:acme.client:JWS payload:
b''
2023-08-08 00:11:54,559:DEBUG:acme.client:Sending POST request to https://acme-v02.api.letsencrypt.org/acme/authz-v3/252867756156:
{
  "protected": "eyJhbGciOiAiUlMyNTYiLCAia2lkIjogImh0dHBzOi8vYWNtZS12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTI0NjI1NjUyNiIsICJub25jZSI6ICI4OTFGcEx1V0JxTGlsZjBqWG1ZSVA1bXE4Q3lSaThxT0xoSGRpSjJtb25KMGExMCIsICJ1cmwiOiAiaHR0cHM6Ly9hY21lLXYwMi5hcGkubGV0c2VuY3J5cHQub3JnL2FjbWUvYXV0aHotdjMvMjUyODY3NzU2MTU2In0",
  "signature": "pYQFhhJ3vDU3NXyLsM7BW_EeWldltnx8WgJtLaZNmrLWmCSHr3cvn5xBxwW2Zy2K4Y5LaPrZK98JHPyuMJ_7h4HQKF2tKW_GlrrpE9jUtMYnNjonS9cAA98wneR-aCQuGlcLZXCU590Q51KlvKKpbPk7papOpTziUqn1Bkt5x-IbE0Lq4qY67nR_hgvu1qXEn0-JVxdWnJcLFnCOG-Chp8GJfKuXwib8i5nWln9y1_6ME2J7lccTzhMNproDDRrD0kws_yY5kMLgvcc6wJhbGmZYUVArLQJ9uCZxUllEvhHrh4NeNc_5FABKYTtsrdLuxte5j4EmeS722PZsB5F0jg",
  "payload": ""
}
2023-08-08 00:11:54,601:DEBUG:urllib3.connectionpool:https://acme-v02.api.letsencrypt.org:443 "POST /acme/authz-v3/252867756156 HTTP/1.1" 200 392
2023-08-08 00:11:54,602:DEBUG:acme.client:Received response:
HTTP 200
Server: nginx
Date: Tue, 08 Aug 2023 00:11:54 GMT
Content-Type: application/json
Content-Length: 392
Connection: keep-alive
Boulder-Requester: 1246256526
Cache-Control: public, max-age=0, no-cache
Link: <https://acme-v02.api.letsencrypt.org/directory>;rel="index"
Replay-Nonce: 371CU1nqd_EWy_AlIRrIDbrjlq1yghP0nkKn-kwbAjqErso
X-Frame-Options: DENY
Strict-Transport-Security: max-age=604800

{
  "identifier": {
    "type": "dns",
    "value": "everythingcode.net"
  },
  "status": "pending",
  "expires": "2023-08-14T19:40:48Z",
  "challenges": [
    {
      "type": "dns-01",
      "status": "pending",
      "url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/252867756156/9Cy5xg",
      "token": "ubIxL87jZQqPEvbxvvP_81j4nHP2xg21yp4R3yxHgM0"
    }
  ],
  "wildcard": true
}
2023-08-08 00:11:54,602:DEBUG:acme.client:Storing nonce: 371CU1nqd_EWy_AlIRrIDbrjlq1yghP0nkKn-kwbAjqErso
2023-08-08 00:11:54,602:DEBUG:acme.client:JWS payload:
b''
2023-08-08 00:11:54,604:DEBUG:acme.client:Sending POST request to https://acme-v02.api.letsencrypt.org/acme/authz-v3/252867756166:
{
  "protected": "eyJhbGciOiAiUlMyNTYiLCAia2lkIjogImh0dHBzOi8vYWNtZS12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTI0NjI1NjUyNiIsICJub25jZSI6ICIzNzFDVTFucWRfRVd5X0FsSVJySURicmpscTF5Z2hQMG5rS24ta3diQWpxRXJzbyIsICJ1cmwiOiAiaHR0cHM6Ly9hY21lLXYwMi5hcGkubGV0c2VuY3J5cHQub3JnL2FjbWUvYXV0aHotdjMvMjUyODY3NzU2MTY2In0",
  "signature": "JI6QXMjyvPPdnaqL99x6LSKsCaVZgWBC1PmPGgXaYWLs4Igrqvo-S3bpSahEfxsgXiMRYOa-MPKfQ4YFqkwu1V8sJ0ZU8kK3uINJPDcDTBXs4rIML98hxRy4zf17tvCmtDgZaO57idiazmqEaRW_5rF8XjHczEzy7D6uQI4tGZhCVn8neVmDW4Uoj13CxOP1gdOQgcgyDfOuxX7ml1qZ8iOOAr_FgIx6anD6t-KOKtBcVB6jsY6PRpJoORCacSVEohAnVaZcMMB9oKh8DedF2zmpk5P_O05QZs__G_dGvmdtpz6Q4p63_ch6H0PcSNrqbsT63Lz_1q2i_8jw-pj2Zw",
  "payload": ""
}
2023-08-08 00:11:54,632:DEBUG:urllib3.connectionpool:https://acme-v02.api.letsencrypt.org:443 "POST /acme/authz-v3/252867756166 HTTP/1.1" 200 802
2023-08-08 00:11:54,633:DEBUG:acme.client:Received response:
HTTP 200
Server: nginx
Date: Tue, 08 Aug 2023 00:11:54 GMT
Content-Type: application/json
Content-Length: 802
Connection: keep-alive
Boulder-Requester: 1246256526
Cache-Control: public, max-age=0, no-cache
Link: <https://acme-v02.api.letsencrypt.org/directory>;rel="index"
Replay-Nonce: 371C36MYvxuuq4HjJiUrjFxnDOaZSmZ67DYS-1Y1AbxAqHk
X-Frame-Options: DENY
Strict-Transport-Security: max-age=604800

{
  "identifier": {
    "type": "dns",
    "value": "everythingcode.net"
  },
  "status": "pending",
  "expires": "2023-08-14T19:40:48Z",
  "challenges": [
    {
      "type": "http-01",
      "status": "pending",
      "url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/252867756166/UXj5Dw",
      "token": "aUjXvpaImGjbHMWY030svHB8W7ABT0yat44ZlpcxdoY"
    },
    {
      "type": "dns-01",
      "status": "pending",
      "url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/252867756166/mWYEcA",
      "token": "aUjXvpaImGjbHMWY030svHB8W7ABT0yat44ZlpcxdoY"
    },
    {
      "type": "tls-alpn-01",
      "status": "pending",
      "url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/252867756166/6EmgSw",
      "token": "aUjXvpaImGjbHMWY030svHB8W7ABT0yat44ZlpcxdoY"
    }
  ]
}
2023-08-08 00:11:54,633:DEBUG:acme.client:Storing nonce: 371C36MYvxuuq4HjJiUrjFxnDOaZSmZ67DYS-1Y1AbxAqHk
2023-08-08 00:11:54,633:INFO:certbot._internal.auth_handler:Performing the following challenges:
2023-08-08 00:11:54,634:INFO:certbot._internal.auth_handler:dns-01 challenge for everythingcode.net
2023-08-08 00:11:54,634:INFO:certbot._internal.auth_handler:dns-01 challenge for everythingcode.net
2023-08-08 00:11:54,636:DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): api.linode.com:443
2023-08-08 00:11:54,677:DEBUG:urllib3.connectionpool:https://api.linode.com:443 "GET /v4/domains HTTP/1.1" 200 400
2023-08-08 00:11:54,680:DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): api.linode.com:443
2023-08-08 00:11:54,713:DEBUG:urllib3.connectionpool:https://api.linode.com:443 "GET /v4/domains/2675616/records HTTP/1.1" 200 None
2023-08-08 00:11:54,714:DEBUG:lexicon.providers.linode4:list_records: []
2023-08-08 00:11:54,716:DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): api.linode.com:443
2023-08-08 00:11:54,756:DEBUG:urllib3.connectionpool:https://api.linode.com:443 "POST /v4/domains/2675616/records HTTP/1.1" 400 109
2023-08-08 00:11:54,757:DEBUG:certbot.plugins.dns_common_lexicon:Encountered error adding TXT record: 400 Client Error: BAD REQUEST for url: https://api.linode.com/v4/domains/2675616/records
Traceback (most recent call last):
  File "/snap/certbot/3024/lib/python3.8/site-packages/certbot/plugins/dns_common_lexicon.py", line 50, in add_txt_record
    self.provider.create_record(rtype='TXT', name=record_name, content=record_content)
  File "/snap/certbot-dns-linode/current/lib/python3.8/site-packages/lexicon/providers/base.py", line 79, in create_record
    return self._create_record(rtype, name, content)
  File "/snap/certbot-dns-linode/current/lib/python3.8/site-packages/lexicon/providers/linode4.py", line 43, in _create_record
    self._post(
  File "/snap/certbot-dns-linode/current/lib/python3.8/site-packages/lexicon/providers/base.py", line 181, in _post
    return self._request("POST", url, data=data, query_params=query_params)
  File "/snap/certbot-dns-linode/current/lib/python3.8/site-packages/lexicon/providers/linode4.py", line 165, in _request
    response.raise_for_status()
  File "/snap/certbot/3024/lib/python3.8/site-packages/requests/models.py", line 1021, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 400 Client Error: BAD REQUEST for url: https://api.linode.com/v4/domains/2675616/records
2023-08-08 00:11:54,758:DEBUG:certbot._internal.error_handler:Encountered exception:
Traceback (most recent call last):
  File "/snap/certbot/3024/lib/python3.8/site-packages/certbot/plugins/dns_common_lexicon.py", line 50, in add_txt_record
    self.provider.create_record(rtype='TXT', name=record_name, content=record_content)
  File "/snap/certbot-dns-linode/current/lib/python3.8/site-packages/lexicon/providers/base.py", line 79, in create_record
    return self._create_record(rtype, name, content)
  File "/snap/certbot-dns-linode/current/lib/python3.8/site-packages/lexicon/providers/linode4.py", line 43, in _create_record
    self._post(
  File "/snap/certbot-dns-linode/current/lib/python3.8/site-packages/lexicon/providers/base.py", line 181, in _post
    return self._request("POST", url, data=data, query_params=query_params)
  File "/snap/certbot-dns-linode/current/lib/python3.8/site-packages/lexicon/providers/linode4.py", line 165, in _request
    response.raise_for_status()
  File "/snap/certbot/3024/lib/python3.8/site-packages/requests/models.py", line 1021, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 400 Client Error: BAD REQUEST for url: https://api.linode.com/v4/domains/2675616/records

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/snap/certbot/3024/lib/python3.8/site-packages/certbot/_internal/auth_handler.py", line 88, in handle_authorizations
    resps = self.auth.perform(achalls)
  File "/snap/certbot/3024/lib/python3.8/site-packages/certbot/plugins/dns_common.py", line 76, in perform
    self._perform(domain, validation_domain_name, validation)
  File "/snap/certbot-dns-linode/current/lib/python3.8/site-packages/certbot_dns_linode/_internal/dns_linode.py", line 57, in _perform
    self._get_linode_client().add_txt_record(domain, validation_name, validation)
  File "/snap/certbot/3024/lib/python3.8/site-packages/certbot/plugins/dns_common_lexicon.py", line 53, in add_txt_record
    raise errors.PluginError('Error adding TXT record: {0}'.format(e))
certbot.errors.PluginError: Error adding TXT record: 400 Client Error: BAD REQUEST for url: https://api.linode.com/v4/domains/2675616/records

2023-08-08 00:11:54,759:DEBUG:certbot._internal.error_handler:Calling registered functions
2023-08-08 00:11:54,759:INFO:certbot._internal.auth_handler:Cleaning up challenges
2023-08-08 00:11:54,760:DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): api.linode.com:443
2023-08-08 00:11:54,799:DEBUG:urllib3.connectionpool:https://api.linode.com:443 "GET /v4/domains HTTP/1.1" 200 400
2023-08-08 00:11:54,801:DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): api.linode.com:443
2023-08-08 00:11:54,842:DEBUG:urllib3.connectionpool:https://api.linode.com:443 "GET /v4/domains/2675616/records HTTP/1.1" 200 None
2023-08-08 00:11:54,843:DEBUG:lexicon.providers.linode4:list_records: []
2023-08-08 00:11:54,843:DEBUG:lexicon.providers.linode4:delete_records: []
2023-08-08 00:11:54,845:DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): api.linode.com:443
2023-08-08 00:11:54,887:DEBUG:urllib3.connectionpool:https://api.linode.com:443 "GET /v4/domains HTTP/1.1" 200 400
2023-08-08 00:11:54,889:DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): api.linode.com:443
2023-08-08 00:11:54,926:DEBUG:urllib3.connectionpool:https://api.linode.com:443 "GET /v4/domains/2675616/records HTTP/1.1" 200 None
2023-08-08 00:11:54,928:DEBUG:lexicon.providers.linode4:list_records: []
2023-08-08 00:11:54,928:DEBUG:lexicon.providers.linode4:delete_records: []
2023-08-08 00:11:54,928:DEBUG:certbot._internal.log:Exiting abnormally:
Traceback (most recent call last):
  File "/snap/certbot/3024/lib/python3.8/site-packages/certbot/plugins/dns_common_lexicon.py", line 50, in add_txt_record
    self.provider.create_record(rtype='TXT', name=record_name, content=record_content)
  File "/snap/certbot-dns-linode/current/lib/python3.8/site-packages/lexicon/providers/base.py", line 79, in create_record
    return self._create_record(rtype, name, content)
  File "/snap/certbot-dns-linode/current/lib/python3.8/site-packages/lexicon/providers/linode4.py", line 43, in _create_record
    self._post(
  File "/snap/certbot-dns-linode/current/lib/python3.8/site-packages/lexicon/providers/base.py", line 181, in _post
    return self._request("POST", url, data=data, query_params=query_params)
  File "/snap/certbot-dns-linode/current/lib/python3.8/site-packages/lexicon/providers/linode4.py", line 165, in _request
    response.raise_for_status()
  File "/snap/certbot/3024/lib/python3.8/site-packages/requests/models.py", line 1021, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 400 Client Error: BAD REQUEST for url: https://api.linode.com/v4/domains/2675616/records

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/snap/certbot/3024/bin/certbot", line 8, in <module>
    sys.exit(main())
  File "/snap/certbot/3024/lib/python3.8/site-packages/certbot/main.py", line 19, in main
    return internal_main.main(cli_args)
  File "/snap/certbot/3024/lib/python3.8/site-packages/certbot/_internal/main.py", line 1864, in main
    return config.func(config, plugins)
  File "/snap/certbot/3024/lib/python3.8/site-packages/certbot/_internal/main.py", line 1597, in certonly
    lineage = _get_and_save_cert(le_client, config, domains, certname, lineage)
  File "/snap/certbot/3024/lib/python3.8/site-packages/certbot/_internal/main.py", line 141, in _get_and_save_cert
    lineage = le_client.obtain_and_enroll_certificate(domains, certname)
  File "/snap/certbot/3024/lib/python3.8/site-packages/certbot/_internal/client.py", line 517, in obtain_and_enroll_certificate
    cert, chain, key, _ = self.obtain_certificate(domains)
  File "/snap/certbot/3024/lib/python3.8/site-packages/certbot/_internal/client.py", line 428, in obtain_certificate
    orderr = self._get_order_and_authorizations(csr.data, self.config.allow_subset_of_names)
  File "/snap/certbot/3024/lib/python3.8/site-packages/certbot/_internal/client.py", line 496, in _get_order_and_authorizations
    authzr = self.auth_handler.handle_authorizations(orderr, self.config, best_effort)
  File "/snap/certbot/3024/lib/python3.8/site-packages/certbot/_internal/auth_handler.py", line 88, in handle_authorizations
    resps = self.auth.perform(achalls)
  File "/snap/certbot/3024/lib/python3.8/site-packages/certbot/plugins/dns_common.py", line 76, in perform
    self._perform(domain, validation_domain_name, validation)
  File "/snap/certbot-dns-linode/current/lib/python3.8/site-packages/certbot_dns_linode/_internal/dns_linode.py", line 57, in _perform
    self._get_linode_client().add_txt_record(domain, validation_name, validation)
  File "/snap/certbot/3024/lib/python3.8/site-packages/certbot/plugins/dns_common_lexicon.py", line 53, in add_txt_record
    raise errors.PluginError('Error adding TXT record: {0}'.format(e))
certbot.errors.PluginError: Error adding TXT record: 400 Client Error: BAD REQUEST for url: https://api.linode.com/v4/domains/2675616/records
2023-08-08 00:11:54,929:ERROR:certbot._internal.log:Error adding TXT record: 400 Client Error: BAD REQUEST for url: https://api.linode.com/v4/domains/2675616/records

Any help is appreciated! I am not new to server management, just FYI. Thanks!

1 Like

Signed in to say that I'm running into the exact same issue, but using an existing installation. Seems to have just stopped working since logs show a successful DNS challenge about 8 hours ago.

I confirmed my Linode API key still works and is not expired.

I have also confirmed my API key works, and several hours later I am still receiving this error from certbot.

Just for clarity the error is issued by linode api. Certbot is just reporting it.

And, it's possible the HTTPS request from Certbot plug-in to the API has somehow become faulty resulting in the API rejecting it with a 400 Bad Request. But, I didn't see any recent changes to the certbot linode-dns plug-in to explain that. And, certainly not in the last 8 hours as reported by @ChrisL

I realize this may not be very helpful. Perhaps someone with direct experience with linode api or certbot internals can provide further help.

But, have you tried asking linode if there is any change to their API server? Could they have gotten stricter about the format of the authentication token they accept for example. Or started checking the requesting IP or something like that?

You might also try the acme.sh ACME Client at least as a test. It has support for linode DNS too and might point to a problem at linode or at Certbot

4 Likes

I think MikeMcQ is correct, and this is a problem with Linode related to some recent undocumented API change that will have to be fixed on their end. I hit their API manually and it is now returning a 400 when providing a TXT record without an equal sign in it, which includes all of the challenge responses I saw in my testing.

{
    "errors": [
        {
            "reason": "TXT target must contain a valid unquoted equals sign delimiter",
            "field": "target"
        }
    ]
}
2 Likes

Yep, just sent a manual TXT record update and it failed with the same error as ebp's. Don't know why I didn't test that when I posted earlier.

Thanks @ebp and @MikeMcQ. Nothing in their changelogs suggests a change within the past 20 hours. I'll open a ticket with Linode.

4 Likes

My ticket on Linode got addressed and they have reverted the recent change. I confirmed that it does work again with one of my domains. Should be fixed now!

5 Likes

Awesome, thanks for taking that on!

4 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.