gofr-dev/gofr · error

failed to dial FTP server %q: %w

Error message

failed to dial FTP server %q: %w

What it means

This error wraps the goftp dial failure when Connect cannot establish a TCP connection to the FTP server at host:port. It uses %w so the root cause (DNS failure, connection refused, timeout) is preserved for errors.Is/As inspection. No login has been attempted at this point.

Source

Thrown at pkg/gofr/datasource/file/ftp/storage_adapter.go:77

	if s.cfg == nil {
		return errFTPConfigNil
	}

	if s.cfg.Host == "" || s.cfg.Port <= 0 {
		return errFTPConfigInvalid
	}

	// Set default timeout if not specified
	dialTimeout := s.cfg.DialTimeout
	if dialTimeout == 0 {
		dialTimeout = 5 * time.Second
	}

	ftpServer := fmt.Sprintf("%s:%d", s.cfg.Host, s.cfg.Port)

	conn, err := ftp.Dial(ftpServer, ftp.DialWithTimeout(dialTimeout))
	if err != nil {
		return fmt.Errorf("failed to dial FTP server %q: %w", ftpServer, err)
	}

	if err := conn.Login(s.cfg.User, s.cfg.Password); err != nil {
		_ = conn.Quit()
		return fmt.Errorf("FTP login failed for user %q: %w", s.cfg.User, err)
	}

	s.conn = conn

	return nil
}

// NewReader creates a reader for the given object.
func (s *storageAdapter) NewReader(_ context.Context, name string) (io.ReadCloser, error) {
	if name == "" {
		return nil, errEmptyObjectName
	}

View on GitHub (pinned to 187eb24962)

Solutions

  1. Verify the server is reachable: nc -vz host port (or telnet) from the same network
  2. Correct Host and Port in the Config
  3. Check firewall/security-group egress rules allow outbound TCP to the FTP port
  4. Retry with backoff for transient network outages; inspect the wrapped cause via errors.Unwrap

Example fix

// before
cfg := &ftp.Config{Host: "ftp.internal", Port: 2121} // wrong port
// after
cfg := &ftp.Config{Host: "ftp.internal", Port: 21}
if err := fs.Connect(cfg); err != nil {
    return fmt.Errorf("connect: %w", err) // unwrap to see dial cause
}
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight reachability check before Connect
host, port := cfg.Host, cfg.Port
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), 5*time.Second)
if err != nil { return fmt.Errorf("ftp server unreachable: %w", err) }
_ = conn.Close()

Type guard

func isDialError(err error) bool {
    return strings.HasPrefix(err.Error(), "failed to dial FTP server") || errors.Is(err, net.ErrUnknownNetwork) || errors.Is(err, os.ErrDeadlineExceeded)
}

Try / catch

err := fs.Connect(cfg)
var netErr net.Error
if errors.As(err, &netErr) || strings.Contains(err.Error(), "failed to dial") {
    // retry with exponential backoff; check host/port/firewall if persistent
}

Prevention

When it happens

Trigger: Calling Connect when the server hostname does not resolve, the port is wrong, the server is down, or the dial exceeds the configured dialTimeout.

Common situations: Wrong host/port in config; FTP service not running or blocked by security groups/firewall; DNS misconfiguration in containers/K8s; network egress restrictions from the runtime environment.

Understand the failure class

Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.

Related errors


AI-assisted analysis of gofr-dev/gofr@187eb24962 (2026-09-01). Data as JSON: /api/errors/1e0cc05904b72149. Report an issue: GitHub.