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 is returned by the DNS resolver builder when the target string ends with a colon, implying a host:port separator was started but no port follows. For example 'host:' has a host but no port. Note that '::' (bare IPv6) is valid as a host-only address, but 'host:' or '[::1]:' are not.
Solutions
- Include a port after the colon: 'dns:///my-service:8080'.
- If no port is intended, omit the colon entirely (the resolver applies a default port if needed).
- Validate the target does not end with ':' before dialing.
- Fix string formatting that appends ':' without a port value.
Example fix
// before
addr := fmt.Sprintf("%s:", hostname) // produces 'host:'
conn, _ := grpc.Dial("dns:///" + addr)
// after
addr := fmt.Sprintf("%s:%d", hostname, port) // produces 'host:8080'
conn, _ := grpc.Dial("dns:///" + addr) Defensive patterns
Strategy: validation
Validate before calling
// Validate target does not end with ':' (incomplete port).
func hasValidPort(target string) bool {
// allow bare IPv6 like '::' but reject 'host:' with no port
if strings.HasSuffix(target, ":") {
host := strings.TrimSuffix(target, ":")
if !strings.Contains(host, ":") { // not IPv6
return false
}
}
return true
} Prevention
- Always include a port after the colon in host:port targets.
- Validate dial targets do not end with ':' before dialing.
- Use net.SplitHostPort to parse and validate host:port format.
- Fix string formatting that appends ':' without a port value.
When it happens
Trigger: Dialing with a target like 'dns:///my-service:' (trailing colon, no port), or an address string that ends with ':' after the resolver parses host and port. The resolver detects the trailing colon as an incomplete host:port pair.
Common situations: String concatenation bug that appends ':' but not a port (e.g., fmt.Sprintf("%s:", host)); config templating that adds a colon delimiter but leaves port empty; user input that omits the port after the colon.
Related errors
- dns resolver: missing address
- invalid target address
- passthrough: received empty target in Build()
- delegating_resolver: invalid target address
- delegating_resolver: unable to build the proxy resolver
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/77a46595a457e6c6.
Report an issue: GitHub.
Appendix: 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 0c51461d27)