Monthly Archives: September 2026

The 200-Day Certificate Is Already Here

Most of the coverage of shrinking certificate lifetimes is written as a warning about 2029. That framing is five months out of date. The first step already happened: since March 15, 2026, publicly trusted TLS certificates have been capped at 200 days. If your renewal process still assumes an annual cadence, it is already wrong.

The Schedule

CA/Browser Forum ballot SC-081v3 passed in April 2025 and set out a staged reduction rather than a single cliff:

before 2026-03-15    398 days
from   2026-03-15    200 days     <- in force now
from   2027-03-15    100 days
from   2029-03-15     47 days

Domain validation data reuse shrinks alongside it, from 398 days down to 10 days for SAN validation by the 2029 milestone. That second number gets less attention and matters more for anyone with a manual approval step, because it means the validation itself stops being something you do once a year and file away.

The values are deliberately awkward. 200, 100, and 47 are not round, and the reasoning is that a number you cannot map onto "twice a year" or "once a quarter" discourages anyone from building a manual calendar reminder around it. Whether that works as intended, it does communicate the intent clearly enough.

What Actually Breaks

ACME-managed web servers are fine. If nginx or Apache is fronted by certbot or an equivalent, the cadence change is invisible. The breakage is concentrated in the places nobody automated, and in my experience it is the same short list every time.

Appliances with no ACME client. Load balancer management interfaces, older firewalls, IPMI and out-of-band controllers, storage arrays, VPN concentrators. Many of these accept a certificate only through a web form. Some accept only a specific bundle format. At 398 days that was an annual annoyance somebody remembered. At 100 days it is quarterly, and at 47 days it is not survivable by hand.

Non-HTTPS services. Mail is the big one, because a mail server presents a certificate on SMTP, submission, IMAP, and POP3, and renewing the web certificate does nothing for any of them unless something copies the file and reloads the daemon. The reload is the step people forget. A renewed certificate sitting on disk while the running process holds the old one in memory is an outage waiting for the expiry date.

Pinned certificates and manually distributed trust. Anything where a partner has your specific certificate configured on their end now needs that coordination three to seven times a year instead of once.

What to Do About It

Inventory first. You cannot automate endpoints you do not know about, and every environment I have looked at has certificates nobody remembered issuing. Scan your own address space by port rather than trusting a spreadsheet.

Automate what can be automated, then deal honestly with what cannot. For the appliance that genuinely has no API, the answer is a documented runbook with an owner and a calendar entry, not an intention. Write down who renews it, how, and what breaks if they do not.

Then monitor the endpoint rather than the issuance. This is the part people skip. Certificate management tools tell you what they issued, and they cannot tell you what a client actually receives, which is where incomplete chains, stale files, and un-reloaded daemons live. A quick manual check of what a server is presenting:

openssl s_client -connect mail.example.com:465 -servername mail.example.com </dev/null 2>/dev/null \
    | openssl x509 -noout -subject -dates

For a one-off look at a public endpoint, including the chain, the SSL check on mrdns.com does the same thing without the command line. For getting the underlying configuration right on whatever you are running, goodtls.com has per-application guides, including the mail servers and databases that tend to be the ones running on expired certificates.

The one thing I would push back on is treating 2029 as the deadline. March 2027 is the milestone that hurts, because 100 days is where quarterly manual renewal stops being merely tedious and starts producing outages. That is seven months away.

Net_DNS2 v2: Breaking a Decade of Backwards Compatibility

I have maintained Net_DNS2 since 2010. It started as a cleanup of the old PEAR Net_DNS library, and for most of its life the guiding rule was simple: do not break anyone. That rule held through PHP 5.3, 5.4, 7.x, and into 8.x. With v2.0 I broke it deliberately, and I think it was overdue.

What PEAR-Era Naming Costs You

Net_DNS2 predates PSR-4, namespaces, and any modern autoloading convention. The class naming reflected that:

$r = new Net_DNS2_Resolver(['nameservers' => ['8.8.8.8']]);
$result = $r->query('example.com', 'MX');

Underscores as a pseudo-namespace worked, and it left the library carrying an autoloader that mapped underscores to directory separators, class names that grew to Net_DNS2_RR_OPENPGPKEY, and no way to use any of the type machinery PHP had spent a decade adding. Every resource record type was a loosely typed bag of public properties. Every enumerated value was an integer or a bare string, validated by convention.

The practical cost was in the bug reports. A meaningful share were people passing the wrong type into something, getting no error, and finding out later when the wire format came out malformed. The library could not tell them, because it had no way to say what it expected.

v2.0 Requires PHP 8.1

The v2.0 rewrite moved to PSR-4 and real namespaces, with the parallel rename you would expect:

$r = new \NetDNS2\Resolver(['nameservers' => ['8.8.8.8']]);
$result = $r->query('example.com', 'MX');

The floor is PHP 8.1, which is what enums require. That version choice is the entire reason the break was worth making. DNS is a protocol built almost entirely out of small enumerated sets: record types, classes, opcodes, response codes, DNSSEC algorithms, digest types. Modelling those as integer constants means every function that accepts one accepts any integer. Modelling them as backed enums means the type system rejects nonsense before a packet is ever assembled.

Most class, method, and property names survived the move. Someone upgrading is mostly changing Net_DNS2_Thing to \NetDNS2\Thing and raising their PHP requirement, which is a mechanical change a search and replace handles. That was the design constraint I set for myself: break the naming, keep the shape.

Deciding to Break Compatibility

The argument against was straightforward. Net_DNS2 has a long tail of users on old PHP, often embedded in something they inherited and do not want to touch. A hard PHP 8.1 floor strands all of them.

What changed my mind was noticing that I was writing new code to work around the absence of types, then writing tests to catch the bugs that the absent types would have caught, then answering issues from people who hit those bugs anyway. The library was subsidising PHP 5 compatibility with permanent complexity, and the people paying that tax were mostly not the people benefiting from it.

The compromise is that v1.x still exists and still gets fixes. That is the part I would recommend to anyone in the same position. A hard break is much easier to justify when the old branch does not disappear the same day, and tagging a final v1 release that people can pin to costs almost nothing.

What I Would Do Differently

I would have done it sooner. The signal was there for years: every feature request that involved better validation ran into the same wall, and I kept routing around it. Deprecation cycles have a cost too, and carrying a compatibility promise you have quietly stopped believing in is worse than announcing the break.

I would also have been more aggressive about the enum conversion in one pass. Doing it incrementally meant a stretch where some values were enums and some were still integers, and the mixed state was more confusing than either endpoint. If you are going to break, break cleanly and finish.

If you use the library and are still on v1, there is no urgency. If you are starting something new on PHP 8.1 or later, start on v2. The DNS lookup tool on mrdns.com runs on this code, so it gets exercised against real-world responses continuously, which has caught more parsing edge cases than my test suite ever did.