k3s-io/k3s · error

proxy URL must include scheme and host

Error message

proxy URL must include scheme and host

What it means

When --etcd-s3-proxy is set (and is not the literal 'none'), the value is parsed with url.Parse and must yield both a scheme and a host before it is installed as the transport proxy. url.Parse rarely errors on sloppy input, so this explicit check is what rejects ambiguous proxy strings.

Solutions

  1. Include scheme and host: --etcd-s3-proxy=http://10.10.10.1:8080.
  2. To explicitly disable proxying use the literal --etcd-s3-proxy=none; to fall back to environment proxies, unset the flag.
  3. Validate the value with url.ParseRequestURI in CI/scripts before passing it to the server.

Example fix

# before
--etcd-s3-proxy=10.10.10.1:8080
# after
--etcd-s3-proxy=http://10.10.10.1:8080
Defensive patterns

Strategy: validation

Validate before calling

if etcdS3.Proxy != "" && etcdS3.Proxy != "none" {
    u, err := url.Parse(etcdS3.Proxy)
    if err != nil || u.Scheme == "" || u.Host == "" {
        return fmt.Errorf("proxy %q must look like http://host:port", etcdS3.Proxy)
    }
}

Type guard

func hasSchemeAndHost(s string) bool {
	u, err := url.Parse(s)
	return err == nil && u.Scheme != "" && u.Host != ""
}

Prevention

When it happens

Trigger: Setting --etcd-s3-proxy=10.10.10.1:8080 (missing scheme) or --etcd-s3-proxy=http:// (missing host); anything url.Parse accepts but that lacks scheme/host fails here.

Common situations: Reusing a proxy value from environment-style configs (which omit the scheme) in a flag that requires it; typos like 'http:/proxy:3128'; expecting the literal 'none' semantics with values like 'off' or 'disabled' (only 'none' is special).

Related errors


AI-assisted analysis of k3s-io/k3s@6ba341e396 (2026-08-15). Data as JSON: /api/errors/293e888eddfb6367. Report an issue: GitHub.

Appendix: source

Thrown at pkg/etcd/s3/s3.go:204

		if err != nil {
			return nil, err
		}
		tr.TLSClientConfig = tlsConfig
	}

	// Set a fixed proxy URL, if requested by the user. This replaces the default,
	// which calls ProxyFromEnvironment to read proxy settings from the environment.
	if etcdS3.Proxy != "" {
		var u *url.URL
		var err error
		// proxy address of literal "none" disables all use of a proxy by S3
		if etcdS3.Proxy != "none" {
			u, err = url.Parse(etcdS3.Proxy)
			if err != nil {
				return nil, errors.WithMessage(err, "failed to parse etcd-s3-proxy value as URL")
			}
			if u.Scheme == "" || u.Host == "" {
				return nil, errors.New("proxy URL must include scheme and host")
			}
		}
		tr.Proxy = http.ProxyURL(u)
	}

	creds := credentials.NewChainCredentials([]credentials.Provider{
		&credentials.Static{
			Value: credentials.Value{
				AccessKeyID:     etcdS3.AccessKey,
				SecretAccessKey: etcdS3.SecretKey,
				SessionToken:    etcdS3.SessionToken,
				SignerType:      credentials.SignatureV4,
			},
		},
		&credentials.FileAWSCredentials{},
		&credentials.IAM{},
	})

View on GitHub (pinned to 6ba341e396)