nats-io/nats-server · warning

failed to read IPv4 address data

Error message

failed to read IPv4 address data: %w

What it means

The v2 header declared a valid (>=12 byte) IPv4 address length, but reading that many bytes from the connection with io.ReadFull failed (connection closed, timeout, or reset) before the full address payload arrived. The underlying I/O error is wrapped in the %w so the cause is preserved.

Solutions

  1. Ensure the upstream proxy writes the complete PROXY header atomically (single write) so it is never split by disconnects.
  2. Check whether clients/probes are connecting to a PROXY-enabled listener without speaking PROXY protocol and move them to a non-PROXY listener.
  3. Handle this as a transient connection error: retry the connection; use errors.Is/As on the wrapped cause to distinguish io.EOF/io.ErrUnexpectedEOF from timeouts.

Example fix

// before: partial writes from proxy
tcpConn.Write(headerSig)
// peer may disconnect here
// after
buf := buildFullV2Header(addr)
tcpConn.Write(buf) // one atomic write
Defensive patterns

Strategy: retry

Type guard

func isTransientHeaderRead(err error) bool {
    return errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, os.ErrDeadlineExceeded)
}

Try / catch

_, err := connectWithProxy()
if err != nil && strings.Contains(err.Error(), "failed to read IPv4 address data") {
    if isTransientHeaderRead(errors.Unwrap(err)) {
        time.Sleep(backoff)
        return connectWithProxy() // retry transient disconnect
    }
}

Prevention

When it happens

Trigger: Client disconnects immediately after sending the PROXY header signature and length but before the 12-byte address data; network timeout mid-header; LB health-check probes that send a partial header then close.

Common situations: Health checks / port scanners hitting a PROXY-enabled port with half-formed headers; flaky mobile clients; LB connection draining between header write and body write.

Related errors


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/8891b372453ee245. Report an issue: GitHub.

Appendix: source

Thrown at server/client_proxyproto.go:389

				return nil, fmt.Errorf("failed to discard UNSPEC address address data: %w", err)
			}
		}
		return nil, nil
	default:
		return nil, fmt.Errorf("%w: unsupported address family 0x%02x", errProxyProtoUnsupported, family)
	}
	return addr, err
}

// parseIPv4Addr parses IPv4 address data from PROXY protocol header
func parseIPv4Addr(conn net.Conn, addrLen uint16) (*proxyProtoAddr, error) {
	// IPv4: 4 (src IP) + 4 (dst IP) + 2 (src port) + 2 (dst port) = 12 bytes minimum
	if addrLen < proxyProtoAddrSizeIPv4 {
		return nil, fmt.Errorf("IPv4 address data too short: %d bytes", addrLen)
	}
	addrData := make([]byte, addrLen)
	if _, err := io.ReadFull(conn, addrData); err != nil {
		return nil, fmt.Errorf("failed to read IPv4 address data: %w", err)
	}
	return &proxyProtoAddr{
		srcIP:   net.IP(addrData[0:4]),
		dstIP:   net.IP(addrData[4:8]),
		srcPort: binary.BigEndian.Uint16(addrData[8:10]),
		dstPort: binary.BigEndian.Uint16(addrData[10:12]),
	}, nil
}

// parseIPv6Addr parses IPv6 address data from PROXY protocol header
func parseIPv6Addr(conn net.Conn, addrLen uint16) (*proxyProtoAddr, error) {
	// IPv6: 16 (src IP) + 16 (dst IP) + 2 (src port) + 2 (dst port) = 36 bytes minimum
	if addrLen < proxyProtoAddrSizeIPv6 {
		return nil, fmt.Errorf("IPv6 address data too short: %d bytes", addrLen)
	}
	addrData := make([]byte, addrLen)
	if _, err := io.ReadFull(conn, addrData); err != nil {
		return nil, fmt.Errorf("failed to read IPv6 address data: %w", err)

View on GitHub (pinned to 3a66a489d2)