HTTP 405 response on /acme/finalize

My domain is: ast-cloud.duckdns.org

I ran this command: certbot certonly --webroot --webroot-path /var/www/html -d ast-cloud.duckdns.org -m vagran.ast@gmail.com --no-eff-email --agree-tos --cert-name webdav -n

It produced this output:
An unexpected error occurred:
acme.errors.ClientError: <Response [405]>

My web server is (include version): nginx 1.18.0

The operating system my web server runs on is (include version): Ubuntu 22.04

My hosting provider, if applicable, is:

I can login to a root shell on my machine (yes or no, or I don't know): yes

I'm using a control panel to manage my site (no, or provide the name and version of the control panel): no

The version of my client is (e.g. output of certbot --version or certbot-auto --version if you're using Certbot): certbot 4.1.1

The issue happens after cooldown period after maximal certificates issuing attempts (I have previously messed with my setup, had several reissuing and lost the results).

Certbot should be able to provide you with more information like the error message provided by the ACME server. Can you share it? Maybe you need to check the log.

2 Likes

Here is the log:

2025-08-02 05:41:29,266:DEBUG:certbot._internal.main:certbot version: 4.1.1
2025-08-02 05:41:29,267:DEBUG:certbot._internal.main:Location of certbot entry point: /usr/local/bin/certbot
2025-08-02 05:41:29,267:DEBUG:certbot._internal.main:Arguments: ['--webroot', '--webroot-path', '/var/www/html', '-d', 'ast-cloud.duckdns.org', '-m', 'artyom.lebedev@gmail.com', '--no-eff-email', '--agree-tos', '--cert-name', 'webdav', '-n']
2025-08-02 05:41:29,267:DEBUG:certbot._internal.main:Discovered plugins: PluginsRegistry(PluginEntryPoint#manual,PluginEntryPoint#null,PluginEntryPoint#standalone,PluginEntryPoint#webroot)
2025-08-02 05:41:29,276:DEBUG:certbot._internal.log:Root logging level set at 30
2025-08-02 05:41:29,277:DEBUG:certbot._internal.plugins.selection:Requested authenticator webroot and installer None
2025-08-02 05:41:29,277:DEBUG:certbot._internal.plugins.selection:Single candidate plugin: * webroot
Description: Saves the necessary validation files to a .well-known/acme-challenge/ directory within the nominated webroot path. A separate HTTP server must be running and serving files from the webroot path. HTTP challenge only (wildcards not supported).
Interfaces: Authenticator, Plugin
Entry point: EntryPoint(name='webroot', value='certbot._internal.plugins.webroot:Authenticator', group='certbot.plugins')
Initialized: <certbot._internal.plugins.webroot.Authenticator object at 0xffffa5ecefb0>
Prep: True
2025-08-02 05:41:29,277:DEBUG:certbot._internal.plugins.selection:Selected authenticator <certbot._internal.plugins.webroot.Authenticator object at 0xffffa5ecefb0> and installer None
2025-08-02 05:41:29,277:INFO:certbot._internal.plugins.selection:Plugins selected: Authenticator webroot, Installer None
2025-08-02 05:41:29,464: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/1622937587', new_authzr_uri=None, terms_of_service=None), 08ef42db297a8782b1fc635d772bd2b5, Meta(creation_dt=datetime.datetime(2024, 3, 17, 9, 18, 59, tzinfo=<UTC>), creation_host='eb6eebd119ad', register_to_eff=None))>
2025-08-02 05:41:29,465:DEBUG:acme.client:Sending GET request to https://acme-v02.api.letsencrypt.org/directory.
2025-08-02 05:41:29,466:DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): acme-v02.api.letsencrypt.org:443
2025-08-02 05:41:29,887:DEBUG:urllib3.connectionpool:https://acme-v02.api.letsencrypt.org:443 "GET /directory HTTP/1.1" 200 995
2025-08-02 05:41:29,888:DEBUG:acme.client:Received response:
HTTP 200
Server: nginx
Date: Sat, 02 Aug 2025 05:41:29 GMT
Content-Type: application/json
Content-Length: 995
Connection: keep-alive
Cache-Control: public, max-age=0, no-cache
X-Frame-Options: DENY
Strict-Transport-Security: max-age=604800

{
  "fsDXND24Yhg": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417",
  "keyChange": "https://acme-v02.api.letsencrypt.org/acme/key-change",
  "meta": {
    "caaIdentities": [
      "letsencrypt.org"
    ],
    "profiles": {
      "classic": "https://letsencrypt.org/docs/profiles#classic",
      "shortlived": "https://letsencrypt.org/docs/profiles#shortlived (not yet generally available)",
      "tlsserver": "https://letsencrypt.org/docs/profiles#tlsserver"
    },
    "termsOfService": "https://letsencrypt.org/documents/LE-SA-v1.5-February-24-2025.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",
  "renewalInfo": "https://acme-v02.api.letsencrypt.org/acme/renewal-info",
  "revokeCert": "https://acme-v02.api.letsencrypt.org/acme/revoke-cert"
}
2025-08-02 05:41:29,888:DEBUG:certbot._internal.display.obj:Notifying user: Requesting a certificate for ast-cloud.duckdns.org
2025-08-02 05:41:29,900:DEBUG:acme.client:Requesting fresh nonce
2025-08-02 05:41:29,900:DEBUG:acme.client:Sending HEAD request to https://acme-v02.api.letsencrypt.org/acme/new-nonce.
2025-08-02 05:41:30,038:DEBUG:urllib3.connectionpool:https://acme-v02.api.letsencrypt.org:443 "HEAD /acme/new-nonce HTTP/1.1" 200 0
2025-08-02 05:41:30,038:DEBUG:acme.client:Received response:
HTTP 200
Server: nginx
Date: Sat, 02 Aug 2025 05:41:29 GMT
Connection: keep-alive
Cache-Control: public, max-age=0, no-cache
Link: <https://acme-v02.api.letsencrypt.org/directory>;rel="index"
Replay-Nonce: baVJhnvwVvsGnOgUJnYCCiqP6bhY1uNIQIF_RhcOuYhRRup--yM
X-Frame-Options: DENY
Strict-Transport-Security: max-age=604800


2025-08-02 05:41:30,038:DEBUG:acme.client:Storing nonce: baVJhnvwVvsGnOgUJnYCCiqP6bhY1uNIQIF_RhcOuYhRRup--yM
2025-08-02 05:41:30,038:DEBUG:acme.client:JWS payload:
b'{\n  "identifiers": [\n    {\n      "type": "dns",\n      "value": "ast-cloud.duckdns.org"\n    }\n  ]\n}'
2025-08-02 05:41:30,044:DEBUG:acme.client:Sending POST request to https://acme-v02.api.letsencrypt.org/acme/new-order:
{
  "protected": "eyJhbGciOiAiUlMyNTYiLCAia2lkIjogImh0dHBzOi8vYWNtZS12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTYyMjkzNzU4NyIsICJub25jZSI6ICJiYVZKaG52d1Z2c0duT2dVSm5ZQ0NpcVA2YmhZMXVOSVFJRl9SaGNPdVloUlJ1cC0teU0iLCAidXJsIjogImh0dHBzOi8vYWNtZS12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL25ldy1vcmRlciJ9",
  "signature": "hpcsY1Gw5NTDdJFDRlYKf71dwgv_j-T4rQ0l1qW8WQQnXz2wrQ46X6UvRW63PhFmuM_tbyQVPYYDbwtXPllJNSk2ODw9hN2XWxZx0-ykit0wT82qEnj0T79P48WVvMTJvtkeBj8v7Cdziwaa1RyvibtE97rLmDZnJrX0KSqgVBbNJByoHObC-8ldBRNcbHDpcQ1cT_LsOMEexMqvwrwofVC7mURxP4pJbRd923hbZjioo8qeO_pjbN6XKiY3yg-0BMl3guQeByp-l_kGGSQcB43a4IsHrxMPJwckkJJ1BiI09lIw8m0cci5zHtlfkhGedRCjpfRLea1-ByQNbwf1Pg",
  "payload": "ewogICJpZGVudGlmaWVycyI6IFsKICAgIHsKICAgICAgInR5cGUiOiAiZG5zIiwKICAgICAgInZhbHVlIjogImFzdC1jbG91ZC5kdWNrZG5zLm9yZyIKICAgIH0KICBdCn0"
}
2025-08-02 05:41:30,237:DEBUG:urllib3.connectionpool:https://acme-v02.api.letsencrypt.org:443 "POST /acme/new-order HTTP/1.1" 201 353
2025-08-02 05:41:30,237:DEBUG:acme.client:Received response:
HTTP 201
Server: nginx
Date: Sat, 02 Aug 2025 05:41:30 GMT
Content-Type: application/json
Content-Length: 353
Connection: keep-alive
Boulder-Requester: 1622937587
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/1622937587/413515975781
Replay-Nonce: baVJhnvwAbzLfm24hG1ugcPEoBjEAG9w0dWx2ki0UD4XFPZsBzw
X-Frame-Options: DENY
Strict-Transport-Security: max-age=604800

{
  "status": "ready",
  "expires": "2025-08-09T05:36:06Z",
  "identifiers": [
    {
      "type": "dns",
      "value": "ast-cloud.duckdns.org"
    }
  ],
  "authorizations": [
    "https://acme-v02.api.letsencrypt.org/acme/authz/1622937587/561654569861"
  ],
  "finalize": "https://acme-v02.api.letsencrypt.org/acme/finalize/1622937587/413515975781"
}
2025-08-02 05:41:30,237:DEBUG:acme.client:Storing nonce: baVJhnvwAbzLfm24hG1ugcPEoBjEAG9w0dWx2ki0UD4XFPZsBzw
2025-08-02 05:41:30,237:DEBUG:acme.client:JWS payload:
b''
2025-08-02 05:41:30,241:DEBUG:acme.client:Sending POST request to https://acme-v02.api.letsencrypt.org/acme/authz/1622937587/561654569861:
{
  "protected": "eyJhbGciOiAiUlMyNTYiLCAia2lkIjogImh0dHBzOi8vYWNtZS12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTYyMjkzNzU4NyIsICJub25jZSI6ICJiYVZKaG52d0FiekxmbTI0aEcxdWdjUEVvQmpFQUc5dzBkV3gya2kwVUQ0WEZQWnNCenciLCAidXJsIjogImh0dHBzOi8vYWNtZS12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2F1dGh6LzE2MjI5Mzc1ODcvNTYxNjU0NTY5ODYxIn0",
  "signature": "vSH4s5dGlucyRsPJe_Z0CctSpSt-FHorvSAzfUYs3EvGP_TBPVGcrxWEnIVnpOAaxSlUHHyTkDOtxi3kOPAjOYCGoVMa3XgxEQfaesWnRfwggz6BZ3P9UTa9dRYGxwdYXR3lnL6lNLwwJE7tAPMjLAsw2-zIR8z4KEb_xlZtZVGKsCM4lsNLaBnbEpFeUwuloOkyUDdEUG6rAdoFvKPkeTIW3rWwReKpH1Hc8J6alJkk8pQ5a3jgj05-yfHDTRqLgB6cdrKIOwmd8WICG4-gLhTRiwFupHJE83rAfPrytqrl-DwKbzg_oTAfJEsh2pt4SVk-n2BBEYfe_kz_ADgAPQ",
  "payload": ""
}
2025-08-02 05:41:30,381:DEBUG:urllib3.connectionpool:https://acme-v02.api.letsencrypt.org:443 "POST /acme/authz/1622937587/561654569861 HTTP/1.1" 200 786
2025-08-02 05:41:30,381:DEBUG:acme.client:Received response:
HTTP 200
Server: nginx
Date: Sat, 02 Aug 2025 05:41:30 GMT
Content-Type: application/json
Content-Length: 786
Connection: keep-alive
Boulder-Requester: 1622937587
Cache-Control: public, max-age=0, no-cache
Link: <https://acme-v02.api.letsencrypt.org/directory>;rel="index"
Replay-Nonce: AEoSsj4nHs6At2hGUMWeRVgDuPOb_926pF0_nNoyJ3H6GlR2l2c
X-Frame-Options: DENY
Strict-Transport-Security: max-age=604800

{
  "identifier": {
    "type": "dns",
    "value": "ast-cloud.duckdns.org"
  },
  "status": "valid",
  "expires": "2025-08-30T06:35:02Z",
  "challenges": [
    {
      "type": "http-01",
      "url": "https://acme-v02.api.letsencrypt.org/acme/chall/1622937587/561654569861/an2f_A",
      "status": "valid",
      "validated": "2025-07-31T06:34:12Z",
      "token": "LTaTLnfjpoLCmMKYV-wL0wtkZv-hsINAhdGm3QBPkIU",
      "validationRecord": [
        {
          "url": "http://ast-cloud.duckdns.org/.well-known/acme-challenge/LTaTLnfjpoLCmMKYV-wL0wtkZv-hsINAhdGm3QBPkIU",
          "hostname": "ast-cloud.duckdns.org",
          "port": "80",
          "addressesResolved": [
            "130.61.21.112"
          ],
          "addressUsed": "130.61.21.112"
        }
      ]
    }
  ]
}
2025-08-02 05:41:30,382:DEBUG:acme.client:Storing nonce: AEoSsj4nHs6At2hGUMWeRVgDuPOb_926pF0_nNoyJ3H6GlR2l2c
2025-08-02 05:41:30,382:DEBUG:certbot._internal.client:CSR: CSR(file=None, data=b'-----BEGIN CERTIFICATE REQUEST-----\nMIHuMIGVAgEAMAAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARCkJqXGVHxgMuP\nuTZQqEZiSE3i+ZE9RFmDof7Ao1N6JyjeuAgFJ1ePTF2JOZpv3yh/EbIGhJGQIJDW\n4s1z44QooDMwMQYJKoZIhvcNAQkOMSQwIjAgBgNVHREEGTAXghVhc3QtY2xvdWQu\nZHVja2Rucy5vcmcwCgYIKoZIzj0EAwIDSAAwRQIgMUI/bu2CPqPlXZ7hUBoOUL7z\nQNJCAMiKXLDG33/3m/4CIQD3YHAmBmGtlYQ5sE+AaOXbTBZ65UelPZP/+MtseYpK\ncw==\n-----END CERTIFICATE REQUEST-----\n', form='pem')
2025-08-02 05:41:30,382:DEBUG:certbot._internal.client:Will poll for certificate issuance until 2025-08-02 05:43:00.382788
2025-08-02 05:41:30,383:DEBUG:acme.client:JWS payload:
b'{\n  "csr": "MIHuMIGVAgEAMAAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARCkJqXGVHxgMuPuTZQqEZiSE3i-ZE9RFmDof7Ao1N6JyjeuAgFJ1ePTF2JOZpv3yh_EbIGhJGQIJDW4s1z44QooDMwMQYJKoZIhvcNAQkOMSQwIjAgBgNVHREEGTAXghVhc3QtY2xvdWQuZHVja2Rucy5vcmcwCgYIKoZIzj0EAwIDSAAwRQIgMUI_bu2CPqPlXZ7hUBoOUL7zQNJCAMiKXLDG33_3m_4CIQD3YHAmBmGtlYQ5sE-AaOXbTBZ65UelPZP_-MtseYpKcw"\n}'
2025-08-02 05:41:30,386:DEBUG:acme.client:Sending POST request to https://acme-v02.api.letsencrypt.org/acme/finalize/1622937587/413515975781:
{
  "protected": "eyJhbGciOiAiUlMyNTYiLCAia2lkIjogImh0dHBzOi8vYWNtZS12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTYyMjkzNzU4NyIsICJub25jZSI6ICJBRW9Tc2o0bkhzNkF0MmhHVU1XZVJWZ0R1UE9iXzkyNnBGMF9uTm95SjNINkdsUjJsMmMiLCAidXJsIjogImh0dHBzOi8vYWNtZS12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2ZpbmFsaXplLzE2MjI5Mzc1ODcvNDEzNTE1OTc1NzgxIn0",
  "signature": "RFo2A3ul_NrW_qk0NBPMzquE0QXrMILJB5cwADyP5oqCw2qGRih3wi5tITgWMUDjjoNKvEbTDLyaaed6ILYCLhy_vD9OVfSnjqPLfmWF9c8wNDbicqqcrgovB33MbqhOCf16NSM7CNs8LTKZ_MoOmnvKQBdN0ukwJft8pMoaYk7M_wmC_R1DEjH9TFJPiSJhefUUQNbGa4YpaFh9fOn3krfRJqrhU3X8fF457Kip2YvXKDfHgoSneNZlJJBkijxjcp25gNcxqet6R5vW4E9_3VSfGnbk1LH89rGhVcTY8OPiDXgbo-WkebtiWi1JXatUm-kJm2hehQwDRvSqqj9WSw",
  "payload": "ewogICJjc3IiOiAiTUlIdU1JR1ZBZ0VBTUFBd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFSQ2tKcVhHVkh4Z011UHVUWlFxRVppU0UzaS1aRTlSRm1Eb2Y3QW8xTjZKeWpldUFnRkoxZVBURjJKT1pwdjN5aF9FYklHaEpHUUlKRFc0czF6NDRRb29ETXdNUVlKS29aSWh2Y05BUWtPTVNRd0lqQWdCZ05WSFJFRUdUQVhnaFZoYzNRdFkyeHZkV1F1WkhWamEyUnVjeTV2Y21jd0NnWUlLb1pJemowRUF3SURTQUF3UlFJZ01VSV9idTJDUHFQbFhaN2hVQm9PVUw3elFOSkNBTWlLWExERzMzXzNtXzRDSVFEM1lIQW1CbUd0bFlRNXNFLUFhT1hiVEJaNjVVZWxQWlBfLU10c2VZcEtjdyIKfQ"
}
2025-08-02 05:42:06,523:DEBUG:urllib3.connectionpool:https://acme-v02.api.letsencrypt.org:443 "POST /acme/finalize/1622937587/413515975781 HTTP/1.1" 405 150
2025-08-02 05:42:06,523:DEBUG:acme.client:Received response:
HTTP 405
Server: nginx
Date: Sat, 02 Aug 2025 05:42:06 GMT
Content-Type: text/html
Content-Length: 150
Connection: keep-alive

<html>
<head><title>405 Not Allowed</title></head>
<body>
<center><h1>405 Not Allowed</h1></center>
<hr><center>nginx</center>
</body>
</html>

2025-08-02 05:42:06,524:DEBUG:certbot._internal.log:Exiting abnormally:
Traceback (most recent call last):
  File "/usr/local/bin/certbot", line 8, in <module>
    sys.exit(main())
  File "/root/certbot_venv/lib/python3.10/site-packages/certbot/main.py", line 19, in main
    return internal_main.main(cli_args)
  File "/root/certbot_venv/lib/python3.10/site-packages/certbot/_internal/main.py", line 1879, in main
    return config.func(config, plugins)
  File "/root/certbot_venv/lib/python3.10/site-packages/certbot/_internal/main.py", line 1585, in certonly
    lineage = _get_and_save_cert(le_client, config, domains, certname, lineage)
  File "/root/certbot_venv/lib/python3.10/site-packages/certbot/_internal/main.py", line 143, in _get_and_save_cert
    lineage = le_client.obtain_and_enroll_certificate(domains, certname)
  File "/root/certbot_venv/lib/python3.10/site-packages/certbot/_internal/client.py", line 524, in obtain_and_enroll_certificate
    cert, chain, key, _ = self.obtain_certificate(domains)
  File "/root/certbot_venv/lib/python3.10/site-packages/certbot/_internal/client.py", line 448, in obtain_certificate
    cert, chain = self.obtain_certificate_from_csr(csr, orderr)
  File "/root/certbot_venv/lib/python3.10/site-packages/certbot/_internal/client.py", line 338, in obtain_certificate_from_csr
    orderr = self.acme.finalize_order(
  File "/root/certbot_venv/lib/python3.10/site-packages/acme/client.py", line 311, in finalize_order
    self.begin_finalization(orderr)
  File "/root/certbot_venv/lib/python3.10/site-packages/acme/client.py", line 237, in begin_finalization
    res = self._post(orderr.body.finalize, wrapped_csr)
  File "/root/certbot_venv/lib/python3.10/site-packages/acme/client.py", line 466, in _post
    return self.net.post(*args, **kwargs)
  File "/root/certbot_venv/lib/python3.10/site-packages/acme/client.py", line 840, in post
    return self._post_once(*args, **kwargs)
  File "/root/certbot_venv/lib/python3.10/site-packages/acme/client.py", line 855, in _post_once
    response = self._check_response(response, content_type=content_type)
  File "/root/certbot_venv/lib/python3.10/site-packages/acme/client.py", line 710, in _check_response
    raise errors.ClientError(response)
acme.errors.ClientError: <Response [405]>
2025-08-02 05:42:06,525:ERROR:certbot._internal.log:An unexpected error occurred:
2025-08-02 05:42:06,525:ERROR:certbot._internal.log:acme.errors.ClientError: <Response [405]>

Huh.. That's weird.

That looks more as an error message from the front end before Boulder (the ACME server software) than that it's an ACME error message itself.

I'm not entirely sure how such a thing could happen, but it might have to do with your "maximal certificates issuing attempts". Perhaps it will go away on its own? :man_shrugging:

1 Like

@vagran, welcome to the community! :slightly_smiling_face:

I see from the log that after polling for the status of an authorization, the certbot immediately issued a finalize request to the order. That looks inappropriate to me, and it is a client side issue.

By the way, server string is nginx. Do you have a proxy in place?

1 Like

Good point, thanks. Before this happened, I also run certbot delete command to reset all the state of the certbot. May it somehow make it inconsistent? What is the best way to completely reset it? Just removing all /etc/letsencrypt directory content?

Meanwile, I tried once again and get this error:

2025-08-02 08:42:04,847:DEBUG:urllib3.connectionpool:https://acme-v02.api.letsencrypt.org:443 "POST /acme/finalize/1622937587/413515975781 HTTP/1.1" 403 267
2025-08-02 08:42:04,847:DEBUG:acme.client:Received response:
HTTP 403
Server: nginx
Date: Sat, 02 Aug 2025 08:42:04 GMT
Content-Type: application/problem+json
Content-Length: 267
Connection: keep-alive
Boulder-Requester: 1622937587
Cache-Control: public, max-age=0, no-cache
Link: <https://acme-v02.api.letsencrypt.org/directory>;rel="index"
Replay-Nonce: s8fHADmc1ic2MX1r8Xkjc_2LrpiEzOpOoFLakhrlD0lcuz5Ege4

{
  "type": "urn:ietf:params:acme:error:caa",
  "detail": "Error finalizing order :: rechecking caa: During secondary validation: While processing CAA for ast-cloud.duckdns.org: DNS problem: query timed out looking up CAA for ast-cloud.duckdns.org",
  "status": 403
}

Next tries, 405 again.

However, DNS timeout is probably unrelated, I see periodically some similar problems when just resolving duckdns entries.

I also upgraded certbot to 4.1.1 after first receiving this error. It was 2.9.0 before, having the same error.

What's wrong with that? I'm more puzzled why Certbot even bothers to request the status of the authz, as the result from the new-order request is an order object that already has the status of "ready". So Certbot could have gone to the "finalize" request immediately, right?

Good to check too, but I'm pretty sure (like, 99.9999 %) Boulder is behind a nginx frontend. If OP also has a nginx proxy in use, why would that proxy give a 405 error just at the finalize request and not earlier? Looks more like some protection rule from Let's Encrypts end to me. Although not sure why nginx would be giving the error and not Boulder itself.

2 Likes

Nothing wrong with that, it can check it any time. You are right, it is useless to loop through the authorizations, since the state of the order object is already "ready".

What is interesting, that the problem here is also duckdns.org domain related as with another recent issue. May be just coincidence?

1 Like

By the way, server string is nginx . Do you have a proxy in place?

No, I do not have proxy.

Does this pattern repeat? That is, do you get a 403 CAA Recheck error followed by the 405 Method Not Allowed followed again by CAA Recheck then 405 and so on?

Because that might hint at a Let's Encrypt server issue. The CAA Recheck is done during the Finalize to ensure your DNS still allows LE to issue. Because of duckdns query problems the recheck fails and the cert is not issued. But, to even get a CAA Recheck fail means the LE nginx frontend must have allowed the POST request method to proceed to the CAA Recheck.

Repeating the same POST should continue to see the same CAA Recheck failure if the duckdns problems persist. And, I'm not exactly sure how the Recheck fail would later cause a 405. It is a very unusual error so just gathering info in hopes to isolate the problem.

3 Likes

Does this pattern repeat? That is, do you get a 403 CAA Recheck error followed by the 405 Method Not Allowed followed again by CAA Recheck then 405 and so on?

No, the 403 error occurred only once. Now I have the rate limit error "too many certificates (5) already issued for this exact set of identifiers in the last 168h0m0s", which is sad since I still did not get any certificate due to 405 error.

You can add --dry-run to your command to use the Staging system. That might be useful anyway to see if any errors occur with it. It will not affect any existing production certs.

I see 6 certs issued in last couple days so it has been successful at least this much. Perhaps something else went wrong after a successful Finalize that made it seem like the cert request failed?

What does this show

sudo certbot certificates

3 Likes
# certbot certificates
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
No certificates found.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

As I have written above, I run certbot delete previously to reset the state. And just on the next run I found, that I have reached the attempt limit. The problem occurred after cool down period expired (so I think it might be some not well tested edge case).

I tried dry-run, and at first run it said that challenge failed due to refused connection. So I found that my web-server is not running. It probably was not the case, when the problem first occurred, but after that I bypassed my docker startup scripts to troubleshoot it manually. And it did not complain on it, possibly because there was some unfinalized order on letsencrypt server side, which it tried to finalize each time, so it did not try to challenge again.

Next run without --dry-run successfully obtained the certificate. So the problem is now gone. Looks like this unfinalized order has been expired on the server side.

And yes, the certificate obtained at 2025-08-02 was not received by certbot, it had the same 405 error, and next request followed by 403 limit exceeded as I wrote above. May that is the one which closed this unfinalized order, so that it started to work at next attempt today.

If the Finalize fails then Let's Encrypt does not issue the certificate. A cert was definitely issued (6 of them recently). There must have been a successful Finalize for each one.

The Certbot delete only deletes your local copy of the cert. The Let's Encrypt rate limit is based on how many it issued. You cannot delete a cert from the LE database. Deleting your copy of that issued cert can cause problems if you have systems that still reference that file and is generally discouraged.

Mind you, I am not discounting the 405 as an error. There are a lot of "moving parts" in your system (docker, cert deletes, DNS query problems, other failures, ...) which makes it difficult to debug.

As for getting certs and deleting them ... that is generally not a good practice. Certs, and the entire Certbot directory structure, should be in persistent storage. It would be good to retain all the Certbot logs too so as to better see the sequences involved. These are normally in /var/log/letsencrypt although must also be in persistent storage if using containers.

4 Likes