MHSanaei/3x-ui · warning
unsupported URL scheme %q
Error message
unsupported URL scheme %q
What it means
SanitizeHTTPURL rejects any URL whose scheme is not exactly http or https after url.Parse. This is the first gate of the panel's SSRF-hardened URL intake (used for things like URL-test/health-check endpoints): non-HTTP schemes are refused before any DNS or request happens, because schemes like file://, gopher://, or unix-socket-style URLs have no safe outbound semantics here.
Source
Thrown at internal/web/service/url_safety.go:26
"strings"
"time"
"github.com/mhsanaei/3x-ui/v3/internal/util/netsafe"
)
// SanitizeHTTPURL validates and normalizes an http(s) URL without resolving
// DNS. Use SanitizePublicHTTPURL at the point of an outbound request.
func SanitizeHTTPURL(raw string) (string, error) {
raw = strings.TrimSpace(raw)
if raw == "" {
return "", nil
}
u, err := url.Parse(raw)
if err != nil {
return "", err
}
if u.Scheme != "http" && u.Scheme != "https" {
return "", fmt.Errorf("unsupported URL scheme %q", u.Scheme)
}
if u.Host == "" || u.Hostname() == "" {
return "", fmt.Errorf("URL host is required")
}
clean := &url.URL{
Scheme: u.Scheme,
Host: u.Host,
Path: u.Path,
RawPath: u.RawPath,
RawQuery: u.RawQuery,
Fragment: u.Fragment,
}
return clean.String(), nil
}
// SanitizePublicHTTPURL validates and normalizes an http(s) URL, then blocks
// private/internal targets unless the caller explicitly allows them.
func SanitizePublicHTTPURL(raw string, allowPrivate bool) (string, error) {View on GitHub (pinned to ad32144c42)
Solutions
- Prefix the input with http:// or https:// before storing it (most common fix for user-entered hosts).
- Validate the field at the UI/API layer with a scheme allowlist so the user gets feedback before save.
- If the target legitimately needs another scheme, it does not belong in an HTTP-checked field — route it through a different, purpose-built config path.
Example fix
// before
raw := "example.com/health"
clean, err := service.SanitizeHTTPURL(raw) // unsupported URL scheme ""
// after
raw := "example.com/health"
if !strings.Contains(raw, "://") {
raw = "https://" + raw
}
clean, err := service.SanitizeHTTPURL(raw) Defensive patterns
Strategy: validation
Validate before calling
// Enforce scheme before storing/using a URL
u, err := url.Parse(strings.TrimSpace(raw))
if err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" {
if !strings.Contains(raw, "://") && raw != "" {
raw = "https://" + raw // auto-upgrade bare hosts
}
} Type guard
func isHTTPURL(raw string) bool {
u, err := url.Parse(strings.TrimSpace(raw))
return err == nil && (u.Scheme == "http" || u.Scheme == "https") && u.Host != ""
} Try / catch
clean, err := service.SanitizeHTTPURL(raw)
if err != nil {
if strings.Contains(err.Error(), "unsupported URL scheme") {
// fix input to http(s), never broaden the allowlist
}
} Prevention
- Collect URLs from users through a field that documents 'full https:// URL required'.
- Reject non-http schemes at the API boundary with a 400, not deep in outbound code.
When it happens
Trigger: Passing 'ftp://host/file', 'file:///etc/passwd', 'javascript:...', a scheme with wrong case is fine (url.Parse lowercases), but 'HTTPS://x' with embedded whitespace or a URL like 'example.com/path' (no scheme at all, so u.Scheme == "" and it fails here rather than at the host check) will trigger it. Note: a schemeless input does NOT hit this branch only if it parses with empty scheme — empty scheme is also 'unsupported'.
Common situations: Users pasting a bare domain into a field that expects a full URL; config imports carrying legacy gopher/socks URLs; typo'd schemes like 'http//host'.
Related errors
- URL host is required
- invalid host %q
- download xray checksum: %w
- blocked private/internal address %s
- cannot resolve host %s: %w
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/6767e829c6f6bb62.
Report an issue: GitHub.