MHSanaei/3x-ui · error

parse proxy url: %w

Error message

parse proxy url: %w

What it means

NewHTTPClient in internal/util/netproxy/netproxy.go parses the admin-configured proxy URL with net/url.Parse and wraps any failure as 'parse proxy url: %w'. url.Parse only fails on genuinely malformed URLs (control characters, unmatched brackets, missing scheme with a colon misplaced), not on unknown schemes — those fail later at the scheme switch. Note this address is deliberately exempt from SSRF filtering because it is admin-configured.

Source

Thrown at internal/util/netproxy/netproxy.go:35

// NewHTTPClient returns an *http.Client whose transport honors proxyURL.
//
// An empty proxyURL yields a plain client (unchanged behavior). socks5/socks5h
// URLs are dialed through golang.org/x/net/proxy; http/https URLs use the
// standard library proxy support. Any other scheme returns an error so callers
// can log it and fall back to a direct connection.
//
// The proxy address is intentionally not subjected to SSRF filtering: it is
// admin-configured and is commonly a loopback/private address (for example a
// local Xray SOCKS inbound).
func NewHTTPClient(proxyURL string, timeout time.Duration) (*http.Client, error) {
	if proxyURL == "" {
		return &http.Client{Timeout: timeout}, nil
	}

	parsed, err := url.Parse(proxyURL)
	if err != nil {
		return nil, fmt.Errorf("parse proxy url: %w", err)
	}

	transport := baseTransport()

	switch strings.ToLower(parsed.Scheme) {
	case "socks5", "socks5h":
		var auth *proxy.Auth
		if parsed.User != nil {
			password, _ := parsed.User.Password()
			auth = &proxy.Auth{User: parsed.User.Username(), Password: password}
		}
		dialer, err := proxy.SOCKS5("tcp", parsed.Host, auth, proxy.Direct)
		if err != nil {
			return nil, fmt.Errorf("create socks5 dialer: %w", err)
		}
		if contextDialer, ok := dialer.(proxy.ContextDialer); ok {
			transport.DialContext = contextDialer.DialContext
		} else {

View on GitHub (pinned to ad32144c42)

Solutions

  1. Re-enter the proxy URL exactly as scheme://[user:pass@]host:port, URL-encoding any special characters in user/password.
  2. Wrap IPv6 literals in brackets: socks5://[::1]:1080.
  3. Trim whitespace/newlines from the configured value before passing it in.
  4. Test the URL in isolation: url.Parse it in a scratch program to see the underlying error text carried by %w.

Example fix

// before
proxyURL := "socks5://user:pa:ss@10.0.0.1:1080" // colon in password breaks parsing
client, err := netproxy.NewHTTPClient(proxyURL, timeout)

// after
proxyURL := "socks5://user:pa%3Ass@10.0.0.1:1080"
client, err := netproxy.NewHTTPClient(proxyURL, timeout)
Defensive patterns

Strategy: validation

Validate before calling

func validProxyURL(raw string) bool {
	raw = strings.TrimSpace(raw)
	if raw == "" { return true } // empty means direct
	u, err := url.Parse(raw)
	return err == nil && u.Scheme != "" && u.Host != ""
}

Try / catch

client, err := netproxy.NewHTTPClient(proxyURL, timeout)
if err != nil {
    return fmt.Errorf("bad proxy setting %q: %w", proxyURL, err)
}

Prevention

When it happens

Trigger: Calling NewHTTPClient with a proxyURL containing control chars, spaces, 'socks5://user:pa:ss@1.2.3.4:1080' (colon inside password unescaped), 'http//[::1]:8080' (missing colon), or an empty scheme followed by junk. Set via whatever setting feeds this function (e.g. panel settings for outbound HTTP requests through a proxy).

Common situations: Typing a proxy URL in panel settings with an unencoded special character in the password; a trailing newline or space pasted from a terminal; IPv6 proxy address without brackets; env var with stray quotes.

Related errors


AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15). Data as JSON: /api/errors/5e63030767c4cfe6. Report an issue: GitHub.