An SRE deploys an internal API gateway, Kubernetes ingress controller, or edge load balancer. The service is secured with a valid, publicly trusted TLS certificate issued for api.example.com.
However, when an automated telemetry collector, container sidecar, or monitoring probe attempts to connect directly to the service's IP address (https://198.51.100.10:443), the TLS handshake fails abruptly with:
curl: (60) SSL: no alternative certificate subject name matches target host name '198.51.100.10'
or in Go / Kubernetes runtimes:
x509: certificate is valid for api.example.com, not 198.51.100.10
Why does this happen even when DNS resolves api.example.com directly to 198.51.100.10?
The answer lies in the fundamental distinction between IP routing and X.509 cryptographic identity verification established in RFC 5280 and RFC 6125. In modern TLS clients, certificate validation does not verify where network packets are physically routed—it strictly verifies the identifier parsed from the target URI against the Subject Alternative Name (SAN) extension in the X.509 certificate.
In this deep-dive guide, we dissect the RFC 5280 ASN.1 certificate identity model, compare dNSName vs iPAddress SAN encodings, demystify TLS SNI vs HTTP Host headers, generate compliant OpenSSL IP certificates, and establish automated multi-SAN certificate lifecycle management.
1. RFC 5280 Certificate Identity Model & ASN.1 Semantics
In the X.509 Public Key Infrastructure standard (RFC 5280), every digital certificate contains a TBSCertificate (To-Be-Signed) structure. Historically, the Common Name (CN) field in the Subject DN was used for hostname verification.
The Common Name is formally deprecated. Modern TLS implementations (Go crypto/tls, OpenSSL 1.1.1+, BoringSSL, Rustls, Node.js, and web browsers) enforce RFC 6125 and strictly require the server identity to appear in the SubjectAltName (SAN) extension.
RFC 5280 X.509 CERTIFICATE STRUCTURE
┌─────────────────────────────────────────────────────────────┐
│ TBSCertificate (To-Be-Signed) │
│ ├── Version: v3 (0x2) │
│ ├── Serial Number │
│ ├── Signature Algorithm: ecdsa-with-SHA256 │
│ ├── Issuer: CN=Let's Encrypt Authority │
│ ├── Validity: Not Before / Not After │
│ ├── Subject: CN=api.example.com (DEPRECATED FOR IDENTITY!) │
│ ├── SubjectPublicKeyInfo │
│ └── Extensions (X509v3 Extensions) │
│ │ │
│ ▼ │
│ SubjectAlternativeName (SAN) ::= GeneralNames │
│ ├── [2] dNSName: "api.example.com" │
│ └── [7] iPAddress: 0xC633640A (198.51.100.10) │
└─────────────────────────────────────────────────────────────┘
ASN.1 GeneralName Specification (RFC 5280 § 4.2.1.6)
The SubjectAltName extension is defined in ASN.1 as a sequence of GeneralName choice elements:
GeneralName ::= CHOICE {
otherName [0] OtherName,
rfc822Name [1] IA5String,
dNSName [2] IA5String,
x400Address [3] ORAddress,
directoryName [4] Name,
ediPartyName [5] EDIPartyName,
uniformResourceIdentifier [6] IA5String,
iPAddress [7] OCTET STRING,
registeredID [8] OBJECT IDENTIFIER
}
Why dNSName and iPAddress Cannot Be Interchanged
Notice the critical structural difference:
dNSName(Tag [2]): Encoded as anIA5String(ASCII string representing an FQDN, e.g."api.example.com").iPAddress(Tag [7]): Encoded as anOCTET STRING(raw binary bytes):- For IPv4: Exactly 4 bytes (
0xC6 0x33 0x64 0x0Afor198.51.100.10). - For IPv6: Exactly 16 bytes (
0x20 0x01 0x0D 0xB8 ...for2001:db8::10).
- For IPv4: Exactly 4 bytes (
⚠️ THE ASCII IP MISTAKE: If you generate an OpenSSL config with
DNS.1 = 198.51.100.10, OpenSSL encodes the IP string as adNSName(Tag [2]). When a client connects tohttps://198.51.100.10, the TLS verifier treats the target as an IP address and searches strictly for Tag [7] (iPAddress), completely ignoring thedNSNameentry and throwing a validation error!
2. Technical Comparison: FQDN SAN vs IP Address SAN
| Identifier Type | Correct SAN Entry | Valid for https://api.example.com | Valid for https://198.51.100.10 | Wildcard Support | Public CA Availability |
|---|---|---|---|---|---|
| Fully Qualified Domain Name (FQDN) | DNS:api.example.com | ✅ Valid | ❌ Fails validation | ✅ Yes (*.example.com) | Free & Instant (Let's Encrypt, ZeroSSL, AWS ACM) |
| Public IPv4 Address | IP:198.51.100.10 | ❌ Fails validation | ✅ Valid | ❌ No (RFC 6125 forbids IP wildcards) | Paid Commercial CAs (DigiCert, Sectigo, GlobalSign) or ZeroSSL |
| Public IPv6 Address | IP:2001:db8::10 | ❌ Fails validation | ✅ Valid | ❌ No | Commercial CAs / ZeroSSL |
| Private RFC 1918 / Loopback IP | IP:10.0.4.12 or IP:127.0.0.1 | ❌ Fails validation | ✅ Valid (Internal PKI only) | ❌ No | ❌ Forbidden by CA/Browser Forum Baseline Requirements |
| Dual Identity (Hybrid SAN) | DNS:api.example.com + IP:198.51.100.10 | ✅ Valid | ✅ Valid | ✅ (On DNS only) | Supported by Commercial CAs & Private Internal CAs |
3. The 4-Layer Connection Architecture: Network vs TLS vs HTTP
To diagnose TLS handshake failures, you must separate the four distinct layers involved in establishing an encrypted session:
THE 4-LAYER CLIENT REQUEST STACK
Layer 1: DNS Resolution Plane
User requests "api.example.com" ──► DNS resolves to 198.51.100.10
│
▼
Layer 2: Network / TCP Transport Layer
Client initiates TCP 3-way handshake to 198.51.100.10:443 (Destination IP)
│
▼
Layer 3: TLS Cryptographic Session Layer
- Client sends TLS ClientHello with SNI = "api.example.com"
- Server returns Certificate containing SAN = "DNS:api.example.com"
- Client matches URI Hostname ("api.example.com") against SAN ──► VALIDATED!
│
▼
Layer 4: Application / HTTP Protocol Layer
Encrypted HTTP/1.1 or HTTP/2 request:
Host: api.example.com (or :authority in HTTP/2)
Diagnostic Comparison: curl Connection Modes
Observe how the client behavior changes across different invocation patterns:
# Mode 1: Standard FQDN Connection (DNS + SNI + FQDN SAN)
curl -v https://api.example.com/
# -> SNI: api.example.com | Host: api.example.com | Verifies DNS:api.example.com
# Mode 2: Direct IP Connection (NO DNS + NO SNI by default + Requires IP SAN)
curl -v https://198.51.100.10/
# -> SNI: (empty or 198.51.100.10) | Host: 198.51.100.10 | Verifies IP:198.51.100.10
# Mode 3: SRE Debugging via --resolve (Forces IP without breaking TLS!)
curl -v --resolve api.example.com:443:198.51.100.10 https://api.example.com/
# -> Routes packets to 198.51.100.10, BUT sends SNI: api.example.com and verifies DNS:api.example.com!
💡 THE SRE
--resolveSUPERPOWER: During DNS outages or when validating a single backend server behind an Anycast CDN, never connect via raw IP. Always usecurl --resolve <FQDN>:443:<IP> https://<FQDN>/to preserve SNI and hostname certificate validation while forcing the network route to your target IP.
4. Inspecting Certificate SANs with OpenSSL
When troubleshooting certificate errors, inspect the remote endpoint's live X.509 extensions:
# 1. Connect with SNI and extract SAN extensions
openssl s_client -connect 198.51.100.10:443 -servername api.example.com -showcerts </dev/null 2>/dev/null | \
openssl x509 -noout -text | grep -A 3 "Subject Alternative Name"
Expected Output for Compliant Certificates
Scenario A: Standard FQDN Certificate
X509v3 Subject Alternative Name:
DNS:api.example.com, DNS:www.example.com
Scenario B: Compliant IP Address Certificate
X509v3 Subject Alternative Name:
IP Address:198.51.100.10, IP Address:2001:db8::10
Scenario C: Dual FQDN + IP Certificate
X509v3 Subject Alternative Name:
DNS:api.example.com, IP Address:198.51.100.10
5. Generating an RFC 5280-Compliant IP Certificate with OpenSSL
If you are issuing certificates for internal services, appliances, or bare-metal Kubernetes nodes via Private PKI or HashiCorp Vault, use this OpenSSL configuration.
Step 1: Create openssl-ip.cnf
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
C = US
ST = California
L = San Francisco
O = Pingzoapp Infrastructure
CN = 198.51.100.10
[v3_req]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth, clientAuth
subjectAltName = @alt_names
[alt_names]
# DNS Hostnames
DNS.1 = api.example.com
DNS.2 = api-internal.example.net
# IPv4 Addresses (Encoded as OCTET STRING under RFC 5280 Tag [7])
IP.1 = 198.51.100.10
IP.2 = 10.0.4.12
IP.3 = 127.0.0.1
# IPv6 Addresses
IP.4 = 2001:0db8:85a3:0000:0000:8a2e:0370:7334
Step 2: Generate Private Key and CSR
openssl req -new -nodes -newkey rsa:2048 \
-keyout server.key \
-out server.csr \
-config openssl-ip.cnf
Step 3: Self-Sign or Sign with Private CA
openssl x509 -req -days 365 \
-in server.csr \
-signkey server.key \
-out server.crt \
-extensions v3_req \
-extfile openssl-ip.cnf
Step 4: Verify the ASN.1 Binary SAN Structure
openssl x509 -in server.crt -noout -text | grep -A 4 "Subject Alternative Name"
6. Kubernetes, cert-manager & Service Mesh Implementation
In Kubernetes environments using cert-manager, you must explicitly populate the ipAddresses array alongside dnsNames:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: internal-gateway-tls
namespace: istio-system
spec:
secretName: internal-gateway-tls-secret
duration: 2160h # 90 days
renewBefore: 360h # 15 days
subject:
organizations:
- Pingzoapp
# FQDN Identifiers (dNSName Tag [2])
dnsNames:
- gateway.internal.local
- istio-ingress.internal.local
# IP Identifiers (iPAddress Tag [7])
ipAddresses:
- 10.96.0.1 # Kubernetes Service ClusterIP
- 192.168.1.100 # Node / MetalLB Ingress IP
- 127.0.0.1
issuerRef:
name: vault-cluster-issuer
kind: ClusterIssuer
7. Public CA vs Private PKI Trust Boundaries
Why can't you get a free Let's Encrypt certificate for your internal IP 10.0.4.12 or public elastic IP?
CA/BROWSER FORUM TRUST BOUNDARY
Public Trust Store (Mozilla / Apple / Google / Microsoft)
┌─────────────────────────────────────────────────────────────┐
│ Public Certificate Authorities (Let's Encrypt, DigiCert) │
│ │
│ Rule 1: MUST validate domain control via ACME / DNS-01 │
│ Rule 2: FORBIDDEN from issuing certs for RFC 1918 IPs │
│ (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, .local)│
│ Rule 3: Public IP certs require Organization Validation │
│ and BGP / WHOIS IP block ownership verification. │
└─────────────────────────────────────────────────────────────┘
When to Use Which Identity
| Architecture Model | Primary SAN Type | Recommended Use Cases | Management Overhead |
|---|---|---|---|
| Model A: Pure FQDN | DNS:api.example.com | Public web apps, SaaS APIs, microservices with service discovery. | Lowest: IP changes require zero certificate rotation. |
| Model B: Pure IP SAN | IP:198.51.100.10 | Legacy appliances, IoT devices, hardcoded IP firmware, air-gapped SCADA. | High: Changing or reassigning IP invalidates certificate immediately. |
| Model C: Hybrid FQDN + IP | DNS:... + IP:... | Kubernetes Ingress, Envoy proxies, internal zero-trust mTLS gateways. | Medium: Requires synchronized rotation when cluster node IPs change. |
8. SRE Troubleshooting & Diagnostic Runbook
Follow this 5-step checklist when encountering certificate verification failures:
TLS IDENTITY DECISION WORKFLOW
Client encounters TLS Handshake Failure
│
▼
1. Inspect URI Scheme & Target:
Is the client connecting via FQDN (https://domain) or raw IP (https://1.2.3.4)?
│
┌──────┴────────────────────────┐
▼ ▼
Target is FQDN Target is RAW IP
│ │
2. Query DNS A/AAAA 2. Inspect Remote SAN via openssl:
Does FQDN point to IP? Does SAN contain "IP Address:1.2.3.4"?
│ │
├── NO ──► Fix DNS Record ├── NO ──► Reissue Cert with IP SAN
└── YES └── YES
│ │
3. Inspect Remote SAN: 3. Is the IP Private (10.x / 192.168.x)?
Does SAN have "DNS:domain"? │
│ ├── YES ──► Distribute Private Root CA
├── NO ──► Reissue Cert └── NO ──► Verify CA Chain & Expiry
└── YES ──► Check SNI header
Common SRE Diagnostic Commands
# 1. Check if server presents different certificates depending on SNI
echo "--- Testing with SNI ---"
openssl s_client -connect 198.51.100.10:443 -servername api.example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer
echo "--- Testing without SNI (Default Fallback Cert) ---"
openssl s_client -connect 198.51.100.10:443 </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer
# 2. Check remaining validity days on live endpoint
echo | openssl s_client -connect api.example.com:443 -servername api.example.com 2>/dev/null | \
openssl x509 -noout -dates
9. SRE Production Checklist for TLS Certificates
- SAN Invariance: All client-facing identities (both DNS hostnames and static IPs) are declared in the
SubjectAlternativeNameextension. - Tag Precision: Ensure IP addresses are defined under RFC 5280 Tag [7] (
IP Address:), not Tag [2] (DNS:). - SNI Alignment: Reverse proxies (NGINX, Envoy, HAProxy) have default fallback certificates configured for direct-IP probes lacking SNI.
- Trust Store Distribution: For internal IP certificates signed by a private CA, verify that the root CA certificate is installed in
/etc/ssl/certsand language trust stores (Java keystore, Node.jsNODE_EXTRA_CA_CERTS). - Automated Expiry Alerts: Alerts fire at 30 days and 7 days prior to certificate expiration.
Conclusion & Next Steps
In the modern TLS ecosystem, the golden rule of X.509 certificate validation is simple:
The certificate identity must match the exact identifier the client verifies in its URI, not merely the IP address where packets are routed.
By understanding the ASN.1 structure of dNSName vs iPAddress SANs, leveraging curl --resolve for routing-isolated validation, and managing certificate identities through automated declarative pipelines, you can eliminate certificate identity mismatches across your production fleet.
Inspect and Validate TLS Certificates with Pingzoapp
Avoid unexpected TLS outages, certificate expiration surprises, and SAN mismatch errors.
With Pingzoapp, you get:
- Comprehensive SSL/TLS Monitoring: Multi-region certificate inspection alerting you on expiration, invalid chains, and weak cipher suites.
- Interactive SSL Inspector: Analyze live TLS certificates, SAN entries, and trust chains instantly using our free SSL Inspector.
- DNS & Network Diagnostics: Audit
A/AAAArecords and nameserver consistency with the DNS Lookup Tool. - Instant Multi-Channel Alerts: Get notified via WhatsApp, Telegram, SMS, Slack, and Discord weeks before certificates expire.
👉 Start Monitoring SSL Certificates Free with Pingzoapp and protect your endpoints against TLS downtime.
Stop Finding Out About Outages from Angry Users
Get instant WhatsApp & Discord alerts the second your API, website, or server goes down. Setup in 30 seconds with 60-second checks.