gofiber/fiber · error
proxy: invalid dial address
Error message
proxy: invalid dial address %q: %w
What it means
In newSSRFDialer (the Balancer-installed dial-time guard), the inbound addr from fasthttp is split via net.SplitHostPort. If the address is not a valid host:port form, the split error is wrapped. This guard is purely structural — the address fasthttp hands the dialer should always be host:port, so this fires only on a misconfigured/buggy HostClient address.
Solutions
- Inspect the quoted addr in the message — it shows exactly what was passed to the dialer.
- Ensure every Balancer.Servers and HostClient.Addr entry is host:port (or [ipv6]:port for IPv6).
- Validate upstream addresses with net.SplitHostPort at config load and fail fast.
- For IPv6 upstreams, always wrap the host in brackets: "http://[::1]:8080".
Example fix
// before: missing port on balancer entry
balancer.Servers = []string{"http://upstream"}
// after: explicit port
balancer.Servers = []string{"http://upstream:8080"} Defensive patterns
Strategy: validation
Validate before calling
func validHostPort(addr string) error {
_, _, err := net.SplitHostPort(addr); return err
} Prevention
- Always specify host:port in Balancer.Servers.
- Bracket IPv6 literals: "http://[::1]:8080".
- Validate upstreams with net.SplitHostPort at config load.
- Inspect the quoted addr in the error to find the malformed entry.
When it happens
Trigger: fasthttp invoked the dialer with an address missing a port (e.g. "host"), with an empty host (":8080"), with too many colons in a non-bracketed IPv6 literal ("::1:8080" instead of "[::1]:8080"), or with a non-numeric port.
Common situations: Balancer.Servers entry missing a port; a custom LBClient.Config.Client whose Addr is malformed; an IPv6 upstream entered without brackets; programmatic upstream construction that drops the port.
Related errors
- proxy: parse upstream
- Servers cannot be empty
- client: invalid proxy URL
- ErrUpstreamHostBlocked
- ErrUpstreamHostInvalid
AI-assisted analysis of gofiber/fiber@a105acad6c (2026-08-11).
Data as JSON: /api/errors/372102673d6fea83.
Report an issue: GitHub.
Appendix: source
Thrown at middleware/proxy/security.go:429
}
return nil
}
// newSSRFDialer returns a fasthttp DialFunc that resolves the target host
// (with a bounded timeout), rejects the connection if any resolved
// address falls in a blocked range, and then dials a validated address.
// Performing the check at dial time — rather than only up front — defeats
// DNS-rebinding attacks (the check/use gap) where a resolver returns a
// public address during validation and a private one at connect time. It
// is only installed when the active policy disallows private IPs.
//
//nolint:revive // dialDualStack mirrors fasthttp.HostClient.DialDualStack
func newSSRFDialer(dialDualStack bool) fasthttp.DialFunc {
dialer := &net.Dialer{Timeout: dnsLookupTimeout}
return func(addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, fmt.Errorf("proxy: invalid dial address %q: %w", addr, err)
}
ips, err := resolveAndValidateHost(host)
if err != nil {
return nil, err
}
return dialValidatedIPs(ips, host, port, dialDualStack, dialer.Dial)
}
}
// resolveAndValidateHost looks up host (or treats it as an IP literal),
// then enforces the SSRF blocklist on every returned address. A single
// blocked answer fails the whole resolution so a mixed public/private
// reply cannot slip past the guard.
func resolveAndValidateHost(host string) ([]net.IP, error) {
var ips []net.IP
if ip := net.ParseIP(host); ip != nil {
ips = []net.IP{ip}
} else {View on GitHub (pinned to a105acad6c)