Tutorial

Certificates for bare IP addresses: what Let's Encrypt's IP support actually changes

September 18, 20268 min readCertPulse Engineering

let's encrypt will issue you a certificate for a bare ip address now. this fixes something real. some services genuinely are reached by number, and until this shipped, putting a public cert on one meant paying OV prices for the privilege. it also hands you an extremely easy way to wreck your own week about eighteen months out, when that address belongs to someone else.

what an ip cert is at the x.509 level

a certificate doesn't contain "a name" in any loose sense. it contains a SubjectAltName extension, and per RFC 5280 §4.2.1.6 that extension is a sequence of GeneralName values, each one tagged with a type. a hostname goes in as a dNSName, an IA5String. an ip address goes in as an iPAddress, which isn't a string at all. it's a raw OCTET STRING. four bytes for v4, sixteen for v6, network byte order. the cert never stores the text 203.0.113.10. it stores four bytes.

that distinction is the whole ballgame, because name matching is type-aware. a client connecting to a hostname compares it against dNSName entries. a client connecting to an ip literal compares the parsed address bytes against iPAddress entries. no crossover. shove the text 203.0.113.10 into a dNSName SAN and no correctly implemented client will ever match it. it asked about an address and you handed it a domain name that happens to look like one.

the other half, and people still argue with me about this, is that the Common Name has been dead for name matching for close to a decade. chrome dropped CN fallback in version 58. that was 2017. RFC 9525 replaced RFC 6125 in 2024 and removed CN-ID from the matching rules outright. a cert with the address in the CN and nothing useful in the SAN has never worked in a modern client, and the error will not tell you that. you get a generic name mismatch. i spent a good twenty minutes blaming dns before i actually decoded the thing and saw one lonely dNSName sitting where an iPAddress should have been.

so decode the cert and read the SAN types, not the pretty-printed summary your monitoring panel shows you. openssl x509 -noout -text and look at the literal IP Address: versus DNS: prefix. at a glance in a dashboard they are visually identical and you will skim right past it.

where you can actually get one

let's encrypt only does ip under the short-lived profile. roughly six days of validity, selected through the ACME profiles mechanism, which your client has to support on purpose. recent certbot, lego and acme.sh handle it. pinned to something older and you get a flat rejection, not a graceful downgrade.

six days isn't a random number. the CA/Browser Forum's short-lived definition tightens across the SC-081v3 timeline and six sits comfortably under the seven-day line. it also means these certs carry no revocation obligation, which matters more than it sounds like it does. let's encrypt killed OCSP in 2025. on a six-day cert, expiry is revocation. there is no revoke-and-move-on story. there is only waiting.

paid side hasn't moved much. digicert, sectigo and globalsign will all sell you an ip cert, all OV, all priced like specialty items in the low hundreds a year. EV is off the table because the EV Guidelines only permit dNSName. those OV certs keep conventional validity, so if you own an address outright and want a year of not thinking about it, that's your option. otherwise let's encrypt is the only free path i know of.

hard boundary no matter which CA: nobody public issues for reserved space. no RFC 1918, no CGNAT, no link-local. that's been settled since the internal-name purge in 2016. your synology on 192.168.1.40 is never getting a public cert and anyone telling you otherwise is selling something. run a private CA and push the root out. that was always the right answer for that problem.

validation: two challenges, and one that can't work

RFC 8738 defines the ip identifier type and is blunt about it. http-01 and tls-alpn-01 may validate ip identifiers. dns-01 MUST NOT. that isn't a gap someone patches later. dns-01 proves control of a dns zone and an ip address doesn't live in one.

one wrinkle in tls-alpn-01 that bites. RFC 6066 forbids literal addresses in SNI, so the validating server can't just send the ip. RFC 8738 has it send the reverse-mapped name instead, the .in-addr.arpa or .ip6.arpa form. your challenge responder needs to be listening for that. anything that keys cert selection off SNI and assumes the value looks like a hostname it recognises will quietly fail.

what losing dns-01 costs you:

  • no wildcards. there is no such thing as a wildcard ip SAN. a /24 is 256 certificates.
  • no pre-issuance before cutover. with dns-01 you can hold a valid cert days before anything answers on the address. here you can't get the cert until the address is live, publicly reachable and answering. every migration plan that assumed cert-in-hand before traffic moves needs rewriting.
  • no validating from behind a firewall. split-horizon tricks are gone. every renewal needs the CA to reach that address from the public internet on 80 or 443.

and the part everybody underestimates: that's true on every renewal, forever. not once at provisioning. every two days for the rest of that service's life you have to still control the address publicly and still answer an inbound challenge. tighten ingress filtering, drop in a WAF rule that rate-limits the well-known path, fail over to a different address, and renewal stops working without a sound. you find out four days later.

where it's the right tool

doh and dot resolvers. strongest case by a mile, and the one that drove the feature. clients address resolvers by number because they can't resolve a name before they have a resolver. RFC 9462's designated resolver discovery requires the designated resolver's cert to carry the ip of the unencrypted resolver for verified discovery to work. the big public resolvers have had ip SANs for years. now you can run one too.

appliance and management uis on public static addresses. out-of-band interfaces, jump hosts, anything where an operator types an address precisely because the point is that it works when dns doesn't.

load balancers probed by address. health checkers that deliberately skip dns to hit one specific backend, where you'd rather have real verification than another --insecure quietly rotting in a config file.

the pattern underneath all three: dns is the failure domain you're routing around. if your break-glass path needs resolution to work, it isn't a break-glass path.

where it's a trap

any address you lease and can hand back.

elastic ips, ephemeral public addresses, anything out of a provider pool. release it and the next tenant inherits it, along with the permanent Certificate Transparency record showing your org held a cert for that address. worse: for up to six days after you let go, a cert you still hold is valid for an address somebody else is now serving from. the short lifetime is doing real mitigation work there. on a 398-day cert this would be genuinely alarming. it's still not a property i want anywhere near a threat model.

it bites in reverse too. you pull an address from the pool and your CT monitoring lights up with a stranger's issuance history. now you're triaging a cert that was never yours, for an address that now is.

my rule: no purchase order or RIR allocation, no ip in a certificate.

operating something that expires this week

renewal stops being a monthly chore and becomes a running process. six days means renewing about every two, so the renewal path deserves the same uptime treatment as the service. use ACME Renewal Information if your client speaks it and let the CA tell you when, instead of guessing at fractions of a lifetime. and a failed renewal is a page, not a ticket. your buffer is hours.

then there's client behaviour. a client connecting to an ip literal correctly sends no SNI at all. if nginx or haproxy or envoy picks certs by SNI, that connection gets the default cert, whatever your default happens to be. make the default server block serve the ip cert, not some stale catch-all from three years ago. ipv6 has its own trap: the cert holds sixteen raw bytes, but libraries that normalise to text before comparing can disagree about abbreviated versus expanded form. test with the exact literal your clients type.

monitoring is where this rots silently, because every layer of cert tooling assumes a domain name. your probe config wants a hostname. your CT searches are keyed on domain suffixes, and an ip SAN flattened into a string list will never match a rule that says "ends with example.com". your expiry alerting, if it's threshold-based, screams permanently at any sane threshold. a 30-day warning on a six-day cert is just an alarm that's always on.

change the question. stop alerting on time-to-expiry, alert on cert age and renewal freshness. "this cert is older than four days" is the signal that renewal has stalled. and scan frequency suddenly matters in a way it never did: poll daily and you get six observations across an entire certificate's life, which can't tell a renewal blip from an outage. i build certpulse, so discount me accordingly, but this is exactly why it takes scan intervals down to fifteen minutes and treats an address as a monitored identity instead of cramming it into a hostname field. six-day lifetime, hourly is the floor for learning anything.

and this outgrows ip certs fast. SC-081v3 walks maximum validity down to 47 days by march 2029. the short-lived profile stops being exotic and starts being the preview. every assumption your tooling makes about having months of runway is about to get tested. ip certificates just get you there first.

-- alex

six days is plenty of time to be wrong about who owns an address.

This is why we built CertPulse

CertPulse connects to your AWS, Azure, and GCP accounts, enumerates every certificate, monitors your external endpoints, and watches Certificate Transparency logs. One dashboard for every cert. Alerts when auto-renewal fails. Alerts when certs approach expiry. Alerts when someone issues a cert for your domain that you didn't request.

If you're looking for complete certificate visibility without maintaining scripts, we can get you there in about 5 minutes.

Certificates for bare IP addresses: what Let's Encrypt's IP support actually changes | CertPulse