grpc/grpc-go · error
dns resolver: missing port after port-separator colon
Error message
dns resolver: missing port after port-separator colon
What it means
ErrEndsWithColon (internal/resolver/dns/internal/internal.go:48) is returned by the dns resolver builder when the target name ends with a colon that is meant as the host:port separator but has no port after it. The comment (lines 43-47) clarifies '::' is a valid IPv6 host-only address, whereas '[::]:' is invalid because the trailing colon implies a port that is absent.
Source
Thrown at internal/resolver/dns/internal/internal.go:48
// resolver implementation. This allows the default net.Resolver instance to be
// overridden from tests.
type NetResolver interface {
LookupHost(ctx context.Context, host string) (addrs []string, err error)
LookupSRV(ctx context.Context, service, proto, name string) (cname string, addrs []*net.SRV, err error)
LookupTXT(ctx context.Context, name string) (txts []string, err error)
}
var (
// ErrMissingAddr is the error returned when building a DNS resolver when
// the provided target name is empty.
ErrMissingAddr = errors.New("dns resolver: missing address")
// ErrEndsWithColon is the error returned when building a DNS resolver when
// the provided target name ends with a colon that is supposed to be the
// separator between host and port. E.g. "::" is a valid address as it is
// an IPv6 address (host only) and "[::]:" is invalid as it ends with a
// colon as the host and port separator
ErrEndsWithColon = errors.New("dns resolver: missing port after port-separator colon")
)
// The following vars are overridden from tests.
var (
// TimeAfterFunc is used by the DNS resolver to wait for the given duration
// to elapse. In non-test code, this is implemented by time.After. In test
// code, this can be used to control the amount of time the resolver is
// blocked waiting for the duration to elapse.
TimeAfterFunc func(time.Duration) <-chan time.Time
// TimeNowFunc is used by the DNS resolver to get the current time.
// In non-test code, this is implemented by time.Now. In test code,
// this can be used to control the current time for the resolver.
TimeNowFunc func() time.Time
// TimeUntilFunc is used by the DNS resolver to calculate the remaining
// wait time for re-resolution. In non-test code, this is implemented by
// time.Until. In test code, this can be used to control the remainingView on GitHub (pinned to 03255a9237)
Solutions
- Append an explicit port: "dns:///host:443".
- Guard the port value: only append ":"+port when port is non-empty.
- For IPv6, use bracketed form with a real port: "dns:///[::1]:443", or pass host-only without a trailing colon.
Example fix
// before
port := os.Getenv("BACKEND_PORT") // ""
conn, _ := grpc.Dial(fmt.Sprintf("dns:///host:%s", port), ...) // "dns:///host:" -> err
// after
port := os.Getenv("BACKEND_PORT")
if port == "" { port = "443" }
conn, _ := grpc.Dial(fmt.Sprintf("dns:///host:%s", port), ...) Defensive patterns
Strategy: validation
Validate before calling
host, port := targetHost, targetPort
if strings.HasSuffix(host, ":") {
return errors.New("target host ends with ':' but no port")
}
if port == "" {
return errors.New("target port is empty")
} Try / catch
conn, err := grpc.Dial(target, ...)
if err != nil {
if errors.Is(err, internal.ErrEndsWithColon) {
log.Fatal("dns target missing port after ':'")
}
log.Fatal(err)
} Prevention
- Build host:port only when port is non-empty; default the port otherwise.
- For IPv6, use bracketed form [::1]:443 with an explicit port.
When it happens
Trigger: Dialing a dns:// target whose endpoint ends in ':' with no port, e.g. "dns:///host.example.com:" or "dns:///[::]:". The resolver interprets the trailing colon as the host/port delimiter and rejects the empty port.
Common situations: Building the target by concatenating host + ':' + port where the port variable is empty; stripping a port and leaving the colon; IPv6 address formatting mistakes; templating that emits host: when port is unset.
Related errors
- dns resolver: missing address
- passthrough: received empty target in Build()
- empty token_exchange_service_uri in options
- required field SubjectTokenPath is not specified
- required field SubjectTokenType is not specified
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/77a46595a457e6c6.
Report an issue: GitHub.