ssh is invoked, missing dependencies, or altered permission contexts. Verifying the execution environment and explicit path definitions usually resolves the immediate connectivity or command-not-found errors.
Understanding the Migration Context
Moving a custom automation tool from Ubuntu 20.04 to Debian 12 introduces subtle but critical changes in system libraries and default configurations. While both distributions share a common heritage, Debian 12 (Bookworm) has updated its default security policies and package structures. The Secure Copy Protocol (SCP) utility itself remains standard, but the way your Python shell script invokes it may no longer align with the new OS expectations.
The core issue typically stems from one of three areas: the Python subprocess module failing to locate the correct binary, environment variables like PATH being stripped or modified, or SSH key authentication failures due to stricter host key checking. Before diving into code fixes, it is essential to confirm that the basic SCP command works manually in the terminal under the same user context as your script.
Distinguishing System vs. Manual Configurations
When troubleshooting post-migration issues, you must determine if the failure originates from the operating system's default settings or from leftover configurations specific to your previous Ubuntu setup. For instance, some users install custom SSH clients or wrapper scripts in non-standard directories. If your script previously relied on a manual installation of an SSH variant, Debian 12 might not have those binaries in the expected location. Understanding the difference between directories created manually and those managed by the package manager can help you locate where your script expects to find tools. You can investigate this by checking the output of which scp and comparing it against the paths listed in your script's configuration.
Diagnosing Subprocess Execution Errors
The most common reason for SCP failures in Python scripts is the use of subprocess.run() without explicitly defining the full path to the executable. In Ubuntu 20.04, the environment might have been more permissive or had different default search paths. Debian 12 enforces stricter adherence to POSIX standards regarding environment inheritance.
To diagnose this, modify your Python script temporarily to capture the standard error output. Use the following pattern to see exactly what the system is rejecting:
import subprocess
import sys
result = subprocess.run(
["scp", "source.txt", "user@remote:/dest/"],
capture_output=True,
text=True
)
if result.returncode != 0:
print(f"Error: {result.stderr}", file=sys.stderr)
else:
print("Success")
If the error message indicates No such file or directory, the script cannot find the scp binary. This is rarely because SCP is uninstalled, but rather because the PATH variable passed to the subprocess does not include /usr/bin. To fix this, specify the absolute path to the binary, which is typically /usr/bin/scp, or ensure the env parameter in subprocess.run inherits the current shell's environment correctly.
SSH Key and Authentication Changes
Debian 12 has deprecated older SSH protocols and ciphers by default. If your remote server still uses legacy authentication methods, or if your local SSH client is too strict about host keys, the SCP transfer will fail silently or with a generic connection error. Check your ~/.ssh/config file for any directives that might conflict with the new defaults.
You should also verify that your private keys are accessible to the user running the Python script. In Ubuntu, certain desktop environments might automatically load keys into an agent, whereas Debian's minimal installations do not. Ensure that the key permissions are set correctly (usually 600) and that the corresponding public key is present on the remote server's authorized_keys file. If you are using password-based authentication, ensure that sshpass is installed and that the password is being passed securely without triggering interactive prompts that block non-interactive script execution.
Environment Variable Propagation
Python scripts often run in a sanitized environment that excludes variables like HOME, USER, or custom proxy settings. If your SCP command relies on these variables to determine the destination or identity, they will be empty in Debian 12 unless explicitly exported. Always pass necessary environment variables via the env dictionary argument in subprocess.run.
For example, if you need to set a specific SSH config file, you can pass it directly:
env = os.environ.copy()
env["SSH_AUTH_SOCK"] = "/run/user/1000/gnupg/S.gpg-agent.ssh"
subprocess.run(["scp", ...], env=env)
This ensures that the SSH agent socket is visible to the child process, preventing authentication loops. Additionally, check if your script depends on any ROS-related environment variables, as sourcing them incorrectly can lead to undefined symbol errors that crash the script before SCP even attempts to connect. Troubleshooting these dependency chains is crucial when migrating complex automation pipelines.
Network and Firewall Restrictions
Finally, consider network-level changes. Debian 12 may have different default firewall rules (using nftables instead of iptables in some configurations). If your script works on localhost but fails on remote hosts, verify that port 22 is open and reachable. Use telnet remote_host 22 or nc -zv remote_host 22 to test connectivity outside of the Python environment. If the connection times out, the issue is network infrastructure, not your script.
Frequently Asked Questions
Why does SCP work in the terminal but fail in my Python script?
This usually happens because the terminal loads your .bashrc or .profile files, setting up environment variables and aliases that the Python subprocess does not inherit. The subprocess starts with a clean slate unless you explicitly pass the environment. Always check if your script relies on variables like PATH or SSH_AUTH_SOCK that are only set in interactive shells.
How do I fix "Host key verification failed" in Debian 12?
Debian 12 uses stricter SSH host key checking by default. If the remote server's key has changed or is not in your known_hosts file, SCP will refuse to connect. You can resolve this by manually running ssh user@host once to accept the key, or by adding -o StrictHostKeyChecking=no to your SSH arguments, though this reduces security.
Can I use pexpect instead of subprocess for SCP?
Yes, pexpect is often better for handling interactive prompts like password entry or host key acceptance. It allows you to expect specific responses from the SSH/SCP process and react accordingly. However, for simple automated transfers, using SSH keys with subprocess and explicit paths is generally more reliable and easier to debug than managing interactive states.
What if my script fails because of missing Python modules?
Ensure that all Python dependencies are installed in the virtual environment or system site-packages used by the script. Debian 12 has moved many packages to different names compared to Ubuntu 20.04. Check your requirements file and update any deprecated module names. Running pip list in the active environment helps identify missing or mismatched packages.
Does Debian 12 require sudo for SCP commands?
No, SCP does not inherently require sudo privileges. It runs as the current user. If your script requires writing to protected directories on the remote server, you need proper permissions on the remote side, not elevated privileges locally. Using sudo within the SCP command string is unnecessary and can cause syntax errors.