#9319·certbot

Retry-After header parsing not correctly/consistently done regarding timezones

Author: osirisinferiCreated Jun 9, 2022Updated Jul 22, 2026
Labelsarea: acmepriority: unplannedstale-needs-update

See also https://github.com/certbot/certbot/pull/9275#issuecomment-1094240102

As far as I can tell, naieve datetime objects in Certbot are in the local timezone. However, when acme.retry_after() parses a timestamp as value in the Retry-After header, it converts it to UTC as far as I can tell. (email.utils.parsedate_tz() outputs the offset from the timestamps timezone to UTC in seconds. This offset is then subtracted from the headers value, so the result is a datetime in UTC.)

When the Retry-After header consists of just an integer, the local time (using datetime.now()) is being used to generate the output of retry_after(). So this is inconsistent with the behaviour mentioned above.

Due to this bug, users in a timezone > UTC will have negative amount of seconds of sleep() between polling authzs. I.e.: no delay at all. And probably users in timezones < UTC will wait the offset to UTC too long.

Also, datetime.timedelta()s first attribute is days, not seconds, so currently the value of tz_secs is incorrect when a timezone other than GMT is used in the Retry-After value. Luckily, the specification of the Retry-After header (HTTP-Date) mandates the timestamp to always be in GMT, so this should not be an issue.. But to be absolutely sure, let's fix this bug anyway :slightly_smiling_face:

Earlier I made a PR (#9276) to fix these two things:

  • The return value of retry_after() is always in the local timezone, so the delay is correctly calculated elsewhere in Certbot;
  • tz_secs is actually the offset from UTC in seconds and not erroneously 60*60*24 times larger (i.e.: days).

However, I've closed all my currently open PRs. Someone else is welcome to use the code though. Copy/pasting my explanation from the PR above as an issue as I believe this really is a bug.