hashicorp/terraform · error

Upload failed

Error message

Upload failed: %v

What it means

The file provisioner's copyFiles function wraps UploadDir failures with 'Upload failed: %v'. When the source path is a directory (info.IsDir() is true at resource_provisioner.go:186), the provisioner calls comm.UploadDir to recursively transfer it to the remote machine. Any error from the communicator (SSH, WinRM) during directory upload propagates with this wrapper.

Solutions

  1. Verify the remote destination path is writable by the SSH/WinRM user specified in the connection block.
  2. Check that the source directory exists and is readable locally with `ls -la <source>`.
  3. Increase the connection timeout or add a bastion host if the SSH connection is unstable.
  4. Reduce the directory size or split into smaller transfers if the connection is dropping due to transfer volume.

Example fix

# before
connection {
  type        = "ssh"
  host        = var.ip
  user        = "ubuntu"
  timeout     = "30s"
}
provisioner "file" {
  source      = "./config-dir"
  destination = "/etc/myapp"
}

# after — increase timeout and ensure remote dir exists
connection {
  type        = "ssh"
  host        = var.ip
  user        = "ubuntu"
  timeout     = "5m"
}
provisioner "remote-exec" {
  inline = ["sudo mkdir -p /etc/myapp && sudo chown ubuntu:ubuntu /etc/myapp"]
}
provisioner "file" {
  source      = "./config-dir"
  destination = "/etc/myapp"
}
Defensive patterns

Strategy: retry

Validate before calling

// Before provisioning, verify the source directory exists and is readable
info, err := os.Stat(src)
if err != nil {
    return fmt.Errorf("source directory not accessible: %w", err)
}
if !info.IsDir() {
    return fmt.Errorf("source is not a directory: %s", src)
}
// Check destination path will be writable (best-effort)
// via a pre-flight remote-exec check

Try / catch

// Retry directory upload with exponential backoff
maxRetries := 3
for i := 0; i < maxRetries; i++ {
    err := comm.UploadDir(dst, src)
    if err == nil {
        break
    }
    if i == maxRetries-1 {
        return fmt.Errorf("Upload failed after %d attempts: %v", maxRetries, err)
    }
    time.Sleep(time.Duration(1<<i) * time.Second)
}

Prevention

When it happens

Trigger: Running a file provisioner whose 'source' points to a directory, when the communicator's UploadDir method fails. Common causes: SSH/WinRM connection drops mid-transfer, permission denied on the remote destination, insufficient disk space on remote, or the communicator returns an internal I/O error.

Common situations: Uploading a directory of configuration files or scripts to a remote instance via the file provisioner during 'terraform apply'. The remote machine has restrictive permissions on the destination path. The SSH connection is unstable or times out during a large directory transfer.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/c2aa550abe28b2a4. Report an issue: GitHub.

Appendix: source

Thrown at internal/builtin/provisioners/file/resource_provisioner.go:188

		return err
	}

	// disconnect when the context is canceled, which will close this after
	// Apply as well.
	go func() {
		<-ctx.Done()
		comm.Disconnect()
	}()

	info, err := os.Stat(src)
	if err != nil {
		return err
	}

	// If we're uploading a directory, short circuit and do that
	if info.IsDir() {
		if err := comm.UploadDir(dst, src); err != nil {
			return fmt.Errorf("Upload failed: %v", err)
		}
		return nil
	}

	// We're uploading a file...
	f, err := os.Open(src)
	if err != nil {
		return err
	}
	defer f.Close()

	err = comm.Upload(dst, f)
	if err != nil {
		return fmt.Errorf("Upload failed: %v", err)
	}

	return err
}

View on GitHub (pinned to d32a084675)