hashicorp/terraform · error

SCP failed to start. This usually means that SCP is not prop

Error message

SCP failed to start. This usually means that SCP is not
properly installed on the remote system.

What it means

Returned by Communicator.scpSession (communicator.go:613). After session.Wait() the SSH command returns an *ssh.ExitError; if ExitStatus()==127 (command not found) Terraform maps it to this message. Exit 127 from the shell means the `scp` binary is not installed or not on PATH on the REMOTE system, so file upload/download via SCP cannot proceed.

Source

Thrown at internal/communicator/ssh/communicator.go:613

	log.Println("[DEBUG] Waiting for SSH session to complete.")
	err = session.Wait()

	// log any stderr before exiting on an error
	scpErr := stderr.String()
	if len(scpErr) > 0 {
		log.Printf("[ERROR] scp stderr: %q", stderr)
	}

	if err != nil {
		if exitErr, ok := err.(*ssh.ExitError); ok {
			// Otherwise, we have an ExitErorr, meaning we can just read
			// the exit status
			log.Printf("[ERROR] %s", exitErr)

			// If we exited with status 127, it means SCP isn't available.
			// Return a more descriptive error for that.
			if exitErr.ExitStatus() == 127 {
				return errors.New(
					"SCP failed to start. This usually means that SCP is not\n" +
						"properly installed on the remote system.")
			}
		}

		return err
	}

	return nil
}

// checkSCPStatus checks that a prior command sent to SCP completed
// successfully. If it did not complete successfully, an error will
// be returned.
func checkSCPStatus(r *bufio.Reader) error {
	code, err := r.ReadByte()
	if err != nil {
		return err

View on GitHub (pinned to c9def3e214)

Solutions

  1. Install an SCP client on the remote: `apk add openssh-client` (Alpine), `apt-get install openssh-client` (Debian/Ubuntu), or the equivalent for the OS.
  2. Switch the file transfer to a method the remote supports: use `provisioner "remote-exec"` with curl/wget to fetch the file, or bake the file into the image.
  3. Verify scp is on the remote PATH for the login shell (non-interactive shells sometimes have a minimal PATH).

Example fix

# before: Alpine target has no scp -> 'SCP failed to start'
 provisioner "file" { source = "app.sh" destination = "/tmp/app.sh" }
# after: install scp first, then transfer
 provisioner "remote-exec" {
   inline = ["apk add --no-cache openssh-client"]
 }
 provisioner "file" { source = "app.sh" destination = "/tmp/app.sh" }
Defensive patterns

Strategy: validation

Validate before calling

// Before relying on the file provisioner, ensure scp exists on the remote.
 // (run a remote-exec check, or use an image that includes openssh-client)
 out, _ := sshExec(host, "command -v scp")
 if strings.TrimSpace(out) == "" {
     return errors.New("scp missing on remote; install openssh-client")
 }

Try / catch

err := communicator.Upload(dst, src)
 if err != nil && strings.Contains(err.Error(), "SCP failed to start") {
     // install scp on the remote, then retry the upload
 }

Prevention

When it happens

Trigger: Using the `file` provisioner (or any SCP-based transfer) with connection type "ssh" against a remote host that lacks the `scp` binary — minimal/container images (Alpine, distroless), embedded devices, or locked-down bastions without openssh-client.

Common situations: Target is an Alpine container without `openssh-client` installed; a network appliance with no scp; PATH on the remote does not include /usr/bin; Windows target where scp is not in the default PATH.

Related errors


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/2815bc910fc208b8. Report an issue: GitHub.