caddyserver/caddy · error

subject does not qualify for certificate: '%s'

Error message

subject does not qualify for certificate: '%s'

What it means

While splitting automation policy subjects into internal vs public issuer groups, each subject (except Tailscale domains) is checked with certmagic.SubjectQualifiesForCert. Names that cannot be in any certificate — empty strings, names with spaces, IPs with a port, a bare '*', over-253-char names, dotless names where disallowed — are rejected here during adaptation.

Source

Thrown at caddyconfig/httpcaddyfile/tlsapp.go:255

			// associate our new automation policy with this server block's hosts
			ap.SubjectsRaw = hostsNotHTTP

			// if a combination of public and internal names were given
			// for this same server block and no issuer was specified, we
			// need to separate them out in the automation policies so
			// that the internal names can use the internal issuer and
			// the other names can use the default/public/ACME issuer
			var ap2 *caddytls.AutomationPolicy
			if len(ap.Issuers) == 0 {
				var internal, external []string
				for _, s := range ap.SubjectsRaw {
					// do not create Issuers for Tailscale domains; they will be given a Manager instead
					if isTailscaleDomain(s) {
						continue
					}
					if !certmagic.SubjectQualifiesForCert(s) {
						return nil, warnings, fmt.Errorf("subject does not qualify for certificate: '%s'", s)
					}
					// we don't use certmagic.SubjectQualifiesForPublicCert() because of one nuance:
					// names like *.*.tld that may not qualify for a public certificate are actually
					// fine when used with OnDemand, since OnDemand (currently) does not obtain
					// wildcards (if it ever does, there will be a separate config option to enable
					// it that we would need to check here) since the hostname is known at handshake;
					// and it is unexpected to switch to internal issuer when the user wants to get
					// regular certificates on-demand for a class of certs like *.*.tld.
					if subjectQualifiesForPublicCert(ap, s) {
						external = append(external, s)
					} else {
						internal = append(internal, s)
					}
				}
				if len(external) > 0 && len(internal) > 0 {
					ap.SubjectsRaw = external
					apCopy := *ap
					ap2 = &apCopy

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Fix the reported subject (printed in quotes in the error) to be a valid DNS name or IP without port/scheme/spaces.
  2. If you need an internal-only name, ensure it still qualifies (use 'foo.internal' — not empty, not bare '*', no port).
  3. Check what your placeholders/env vars actually expand to at adapt time.

Example fix

# before
https://example.com:8443 {
  tls internal
}

# after
example.com:8443 {
  tls internal
}
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: A site address or tls subject like 'my site.com' (space), 'localhost:3000' leaking the port into the TLS subject, a bare '*' label, 'bad..name' double dots, or a malformed placeholder-expanded hostname.

Common situations: Env-var/placeholder-expanded hostnames coming out empty or malformed; copying URLs (with scheme or port) instead of bare hostnames into site addresses; typos creating double dots or trailing spaces.

Understand the failure class

Related errors


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/ad46e59647613a3e. Report an issue: GitHub.