{"record":{"id":"2815bc910fc208b8","repo":"hashicorp/terraform","slug":"scp-failed-to-start-this-usually-means-that-scp-i","errorCode":null,"errorMessage":"SCP failed to start. This usually means that SCP is not\nproperly installed on the remote system.","messagePattern":"SCP failed to start\\. This usually means that SCP is not\nproperly installed on the remote system\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/communicator/ssh/communicator.go","lineNumber":613,"sourceCode":"\tlog.Println(\"[DEBUG] Waiting for SSH session to complete.\")\n\terr = session.Wait()\n\n\t// log any stderr before exiting on an error\n\tscpErr := stderr.String()\n\tif len(scpErr) > 0 {\n\t\tlog.Printf(\"[ERROR] scp stderr: %q\", stderr)\n\t}\n\n\tif err != nil {\n\t\tif exitErr, ok := err.(*ssh.ExitError); ok {\n\t\t\t// Otherwise, we have an ExitErorr, meaning we can just read\n\t\t\t// the exit status\n\t\t\tlog.Printf(\"[ERROR] %s\", exitErr)\n\n\t\t\t// If we exited with status 127, it means SCP isn't available.\n\t\t\t// Return a more descriptive error for that.\n\t\t\tif exitErr.ExitStatus() == 127 {\n\t\t\t\treturn errors.New(\n\t\t\t\t\t\"SCP failed to start. This usually means that SCP is not\\n\" +\n\t\t\t\t\t\t\"properly installed on the remote system.\")\n\t\t\t}\n\t\t}\n\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// checkSCPStatus checks that a prior command sent to SCP completed\n// successfully. If it did not complete successfully, an error will\n// be returned.\nfunc checkSCPStatus(r *bufio.Reader) error {\n\tcode, err := r.ReadByte()\n\tif err != nil {\n\t\treturn err","sourceCodeStart":595,"sourceCodeEnd":631,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/communicator/ssh/communicator.go#L595-L631","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","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.","Verify scp is on the remote PATH for the login shell (non-interactive shells sometimes have a minimal PATH)."],"exampleFix":"# before: Alpine target has no scp -> 'SCP failed to start'\n provisioner \"file\" { source = \"app.sh\" destination = \"/tmp/app.sh\" }\n# after: install scp first, then transfer\n provisioner \"remote-exec\" {\n   inline = [\"apk add --no-cache openssh-client\"]\n }\n provisioner \"file\" { source = \"app.sh\" destination = \"/tmp/app.sh\" }","handlingStrategy":"validation","validationCode":"// Before relying on the file provisioner, ensure scp exists on the remote.\n // (run a remote-exec check, or use an image that includes openssh-client)\n out, _ := sshExec(host, \"command -v scp\")\n if strings.TrimSpace(out) == \"\" {\n     return errors.New(\"scp missing on remote; install openssh-client\")\n }","typeGuard":null,"tryCatchPattern":"err := communicator.Upload(dst, src)\n if err != nil && strings.Contains(err.Error(), \"SCP failed to start\") {\n     // install scp on the remote, then retry the upload\n }","preventionTips":["Bake openssh-client into the target image (Alpine: apk add openssh-client).","For scp-less targets, fetch files via curl/wget in remote-exec instead of the file provisioner."],"tags":["ssh","scp","provisioner","file-transfer","remote-dependency"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T20:17:04.800Z"}