Why must a certificate you issue in Go set DNSNames rather than only Subject.CommonName?
answer
- two places a name sits; one is checked
- it is an extension, not the subject
- four template slices feed it
- the fallback was removed years ago
- an address is not a DNS name
basics
~20 sHostname checking reads the Subject Alternative Name extension, which the template fills from DNSNames, IPAddresses, URIs and EmailAddresses. Go's verifier stopped matching Subject.CommonName in Go 1.15, so a certificate carrying the hostname only in CommonName is rejected.
solid answer
~40 sThe name a peer checks lives in the SAN extension, not in the subject. On an `x509.Certificate` template that extension is built from `DNSNames`, `IPAddresses`, `URIs` and `EmailAddresses`; `Subject.CommonName` is a display string with a 64-character limit and no matching role. Go removed the legacy CommonName fallback in Go 1.15 and deleted the temporary opt-out in 1.17, so a CN-only certificate fails verification with an error that names the legacy Common Name field explicitly. Practical consequences for an issuing service: list *every* name the workload is reached by, put IP addresses in `IPAddresses` as `net.IP` values rather than as strings in `DNSNames`, and remember that a wildcard entry matches exactly one leftmost label — `*.example.com` covers `api.example.com` but neither `example.com` itself nor `a.b.example.com`.
code
go · 10 linestmpl := x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{CommonName: "api.internal"}, // label only
DNSNames: []string{"api.internal", "api.prod.example.com"},
IPAddresses: []net.IP{net.ParseIP("10.0.0.7")},
NotBefore: now,
NotAfter: now.Add(24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
}go deeper
Remember that the names a client checks come from the DNSNames and IPAddresses slices on the template, and that Subject.CommonName is only a label. List every hostname the service will be reached by.
Explain that the SAN extension is what hostname verification reads, that Go dropped the CommonName fallback in 1.15 and removed the opt-out in 1.17, and how IP entries and wildcard labels differ from plain DNS names.
Demonstrate the operational habit: a round-trip test that issues and then verifies for the intended hostname, so a missing SAN fails in CI rather than in somebody else's client, plus a reissue path fast enough that nobody disables verification instead.
Own the naming contract — which names a workload is allowed to claim, who approves an addition, and how quickly a new name can be issued — because a slow issuance path is what pushes teams toward skipping verification.
## Where the name a verifier checks actually lives A certificate has two places a hostname can appear. The **subject** is a distinguished name — a structured `pkix.Name` with fields like Organization, Country and CommonName — and it identifies the subject for humans and for directory lookups. The **Subject Alternative Name extension** is a list of typed identifiers: DNS names, IP addresses, URIs, email addresses. Only the second one is what a TLS client matches against the host it dialled. In Go's `x509.Certificate` template the SAN extension is assembled from four slices: - `DNSNames []string` - `IPAddresses []net.IP` - `URIs []*url.URL` - `EmailAddresses []string` Set the right one and the extension appears in the issued certificate. Set none of them and there is no SAN extension at all, no matter how carefully you filled in `Subject`. ## The CommonName history, and why it is settled For many years, verifiers fell back to matching the hostname against `Subject.CommonName` when a certificate had no SAN extension. That fallback was deprecated for a long time and then removed: Go disabled it by default in **Go 1.15**, and in **Go 1.17** even the temporary opt-out that had been provided for migration was deleted. Any Go version you would plausibly run today has no fallback whatsoever. When a certificate carries the hostname only in CommonName, verification fails with a hostname error that says, in so many words, that the certificate relies on the legacy Common Name field and should use SANs instead. This is not a Go quirk; browsers and other TLS stacks made the same change, Go simply did it cleanly. CommonName remains useful as a label. It shows up in logs and in `openssl x509 -text` output, it is what an operator reads to recognise a certificate, and by convention it repeats the primary DNS name. It is limited to 64 characters, which alone makes it unfit as the authoritative name list. ## Getting the entries right **Enumerate every name.** A workload certificate issued to a service reachable as `api.internal`, `api.prod.example.com` and through a load balancer's own name needs all three in `DNSNames`. Verification is a match against the list; there is no inference, no suffix rule, no "same organisation therefore fine". **IP addresses go in IPAddresses.** Writing `"10.0.0.7"` into `DNSNames` produces a DNS-type SAN whose value happens to look like an address, and a client dialling that IP will not match it, because the client compares an IP against IP-type entries only. The correct form is `IPAddresses: []net.IP{net.ParseIP("10.0.0.7")}` — and check that `ParseIP` did not return nil, because it reports a malformed address that way rather than with an error. **Wildcards are narrower than people think.** A `*` may appear only as the entire leftmost label, matches exactly one label, and does not cover the bare parent domain. `*.example.com` matches `api.example.com`, not `example.com` and not `a.b.example.com`. If you need the apex too, list it separately. **A CA certificate needs no SANs.** Intermediates and roots are not matched against a hostname; leaving all four slices empty on a CA template is correct. ## Catching it before a peer does This is the failure the divergence between issuing and verifying is famous for: the certificate is created without error, is written to disk without error, is loaded by the server without error, and the first thing that notices anything is wrong is somebody else's client, at a moment of your choosing only in the worst sense. The cheap defence is a round-trip test in CI — issue a certificate from the same code path production uses, then verify it against its own issuer for the hostname and usage it is meant to serve, and fail the build when it does not match. It costs milliseconds and it makes a missing SAN entry a red test instead of an incident. ## The one that surprises people Adding a name to a running service means issuing a *new* certificate; you cannot append to an existing one, because the SAN list is inside the signed portion. That is a good argument for short lifetimes and automated reissue: if adding a hostname takes an hour because the CA is a manual runbook, someone will eventually reuse a certificate for a name that is not in it, and then reach for a client that skips verification.
- How do you make a certificate valid for an IP address rather than a hostname?Put it in `IPAddresses` as a `net.IP`, typically from `net.ParseIP`. A dotted-quad string placed in `DNSNames` becomes a DNS-type entry and will not match a client that dialled the address, because IP comparison only considers IP-type SAN entries. Also check `ParseIP` for a nil result, which is how it signals a malformed address.
- What exactly does a wildcard entry like *.example.com match?Exactly one label in the leftmost position: `api.example.com` matches, `a.b.example.com` does not, and the bare `example.com` does not either. The star must be the whole label — `api*.example.com` is not portable. If the apex is needed, list it as a second entry.
- Should a CA certificate carry SAN entries?No. A CA is never matched against a hostname; it is located by name and key identifier while building a chain. Leaving `DNSNames` and the other SAN slices empty on an issuing template is correct, and adding a hostname there suggests confusion about what the certificate is for.
saying these in an interview costs you the question
- Puts the hostname only in pkix.Name.CommonName
- Writes an IP address as a string into DNSNames
- Thinks *.example.com matches a.b.example.com
- Believes CommonName is still used when no SAN is present
- Expects to add a hostname to an already-issued certificate