docker/cli · error

invalid SSH URL

Error message

invalid SSH URL: %w

What it means

Raised by ssh.ParseURL (ssh.go:16-22) when Go's net/url.Parse cannot parse the daemon URL at all. If the error is a *url.Error it is unwrapped to its cause before being wrapped as `invalid SSH URL: <err>`. This is a hard parse failure, distinct from semantic validation done later in newSpec.

Solutions

  1. Inspect the wrapped url.Parse error for the specific token/position at fault.
  2. Percent-encode any special characters in the URL (spaces as %20, etc.) or remove them.
  3. Quote the DOCKER_HOST value in the shell to avoid word-splitting: `export DOCKER_HOST='ssh://user@host'`.
  4. Test the URL with `python -c 'import urllib.parse; urllib.parse.urlparse(...)'` or by hand.

Example fix

# before — stray space / bad encoding
export DOCKER_HOST='ssh://user @host%zz'
# after — clean, encoded URL
export DOCKER_HOST='ssh://user@host'
Defensive patterns

Strategy: validation

Validate before calling

// Reject URLs net/url cannot parse.
if _, err := url.Parse(daemonURL); err != nil {
    return fmt.Errorf("DOCKER_HOST is not a valid URL: %w", err)
}

Try / catch

spec, err := ssh.ParseURL(daemonURL)
if err != nil {
    return fmt.Errorf("cannot parse ssh host: %w", err)
}

Prevention

When it happens

Trigger: Calling ssh.ParseURL(daemonURL) where url.Parse(daemonURL) returns an error — e.g. control characters, an unencoded space, an invalid escape sequence (`%zz`), or a missing/illegal authority that the URL parser rejects.

Common situations: DOCKER_HOST contains an ssh URL with unencoded special characters, a malformed percent-encoding, or stray whitespace from shell quoting. Copy-pasting a URL with spaces or control characters triggers it.

Related errors


AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07). Data as JSON: /api/errors/71dbb4bf4af96184. Report an issue: GitHub.

Appendix: source

Thrown at cli/connhelper/ssh/ssh.go:22

import (
	"errors"
	"fmt"
	"net/url"

	"github.com/docker/cli/cli/connhelper/internal/syntax"
)

// ParseURL creates a [Spec] from the given ssh URL. It returns an error if
// the URL is using the wrong scheme, contains fragments, query-parameters,
// or contains a password.
func ParseURL(daemonURL string) (*Spec, error) {
	u, err := url.Parse(daemonURL)
	if err != nil {
		var urlErr *url.Error
		if errors.As(err, &urlErr) {
			err = urlErr.Unwrap()
		}
		return nil, fmt.Errorf("invalid SSH URL: %w", err)
	}
	return NewSpec(u)
}

// NewSpec creates a [Spec] from the given ssh URL's properties. It returns
// an error if the URL is using the wrong scheme, contains fragments,
// query-parameters, or contains a password.
func NewSpec(sshURL *url.URL) (*Spec, error) {
	s, err := newSpec(sshURL)
	if err != nil {
		return nil, fmt.Errorf("invalid SSH URL: %w", err)
	}
	return s, nil
}

func newSpec(u *url.URL) (*Spec, error) {
	if u == nil {
		return nil, errors.New("URL is nil")

View on GitHub (pinned to 4f84911bfe)