Bitbucket Pipelines: "Host key verification failed" and "Permission denied (publickey)"
A step that runs ssh, scp, rsync or git over SSH needs two things: the remote host in the build's known_hosts, and a key the host accepts. Without the first, ssh stops before it offers any key:
Host key verification failed.
Without the second, the host turns the connection down:
deploy@203.0.113.10: Permission denied (publickey).
Host key verification failed
Atlassian's page Set up Pipelines SSH keys on Linux keeps known hosts next to the key. In Repository settings, SSH keys under Pipelines, add the host's address and select Fetch to see its fingerprint. Compare it with the one the server reports for itself, then add the host. Later builds check the host against it.
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub # on the server
Pipelines adds the fingerprints of Bitbucket and GitHub to every build by itself, without listing them there. Since October 2026 that includes ssh.bitbucket.org, the host Git over SSH is moving to (the schedule). Atlassian closed BCLOUD-24132 as fixed on October 20, with the comment "Added ssh.bitbucket.org to known hosts list". Before that, a step running ssh -T git@ssh.bitbucket.org stopped here.
When a server is rebuilt or gets new host keys, the saved fingerprint no longer matches, and ssh stops the same way. Remove the host from the list, check the new fingerprint, and fetch it again.
Where those known hosts do not reach
- Inside
docker buildor Docker Compose. Pipelines writes the entries into the build container during Build setup, not into containers that the docker service starts. Atlassian's article Pipelines: host key verification failed while cloning private repository with docker-in-docker (updated September 25, 2025) adds them in the Dockerfile, before the clone. The same applies whennpm install,yarn installorcomposer installin the Dockerfile pulls a dependency from a private repository.RUN mkdir ~/.ssh && curl https://bitbucket.org/site/ssh >> ~/.ssh/known_hosts - A private address, from a self-hosted runner. Fetch reaches only public hosts and addresses. Atlassian's article Troubleshooting "Host key verification failed" error on Bitbucket Cloud Pipeline Runners (updated September 26, 2025) has the step scan it instead:
- step: runs-on: - self.hosted - linux script: - ssh-keyscan -H 172.20.40.175 >> ~/.ssh/known_hosts
ssh-keyscan in a script trusts whatever answers at that moment. Atlassian's page Using SSH keys in Bitbucket Pipelines uses it in its examples, and warns that turning the check off with StrictHostKeyChecking=no leaves you open to man-in-the-middle attacks. A known_hosts file committed to the repository, made once from a host you checked, avoids both (Use multiple SSH keys in your pipeline):
- mkdir -p ~/.ssh
- cat my_known_hosts >> ~/.ssh/known_hosts
Permission denied (publickey)
The key comes from the same settings page. Generate keys makes a pair in Bitbucket. Use my own keys takes a pair you made, in the Private key and Public key fields, then Save key pair. Pipelines adds the private key as the default identity in ~/.ssh/config for steps on Atlassian's machines and on Linux runners, so ssh in the step uses it without -i.
- The public key is on the server, for the user you connect as. It goes into that user's
~/.ssh/authorized_keys, for example withssh-copy-id. When you create the files yourself, Atlassian's page setschmod 700 ~/.sshandchmod 600 ~/.ssh/authorized_keys. - The key in the settings is the one on the server. A saved key cannot be edited; the page says to delete it and add a new one. After that, or after another Generate keys, the public key on the server is the old one until you install the new one.
- The command runs where the key is. The identity is in the build container's ssh config. A
docker buildgets the key only through--ssh default=$BITBUCKET_SSH_KEY_FILE, the default variable that holds its location. Other ways to pass a key into the build are among the options Pipelines refuses ("authorization denied by plugin pipelines"). - Another Bitbucket repository takes the public key under its own Access keys (submodules). Pushing back to the repository the build runs in is a different case: "Permission denied to update branch".
Atlassian also notes that everyone with write access to the repository can reach the remote host through this key.
A second key, and "Load key ... invalid format"
Pipelines holds one SSH key per repository. Use multiple SSH keys in your pipeline keeps more keys as secured repository variables. Variables cannot hold line breaks, so the private key is base64-encoded first:
base64 -w 0 < deploy_key # on macOS: base64 < deploy_key
In the step, decode it into a file only you can read, and pass it with -i:
- (umask 077; echo $DEPLOY_KEY | base64 --decode > ~/.ssh/deploy_key)
- ssh -i ~/.ssh/deploy_key deploy@203.0.113.10 'echo ok'
The page warns that secured variables can be read by everyone with write access, and that repository variables are passed to child processes. It recommends a key made for Pipelines, not your personal key, and deployment variables with deployment permissions to narrow who can use it.
Atlassian's pipes use the repository's Pipelines key unless you give one in SSH_KEY, and that one has to be base64-encoded the same way. A key pasted as it is fails like this (Atlassian's article Bitbucket Cloud Pipe "atlassian/scp-deploy" error, updated September 26, 2025):
debug1: Trying private key: /root/.ssh/pipelines_id Load key "/root/.ssh/pipelines_id": invalid format debug1: No more authentication methods to try. user@xx.xx.xx.xx: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
Your own image
Not every Docker image has an SSH client. The default Pipelines image does. With your own image, install it in the script, for example apt-get update -y and apt-get install -y ssh (Atlassian's Linux page). Without it the step stops at ssh: command not found.
Bitbucket Pipelines in IntelliJ IDEA
Pipeline Inbox for Bitbucket Pipelines, a plugin by Lesstep for JetBrains IDEs 2024.1 or later, lists the pipelines of all your Bitbucket Cloud repositories in an IDE tool window, with their steps, the failed step and the command it stopped at, and the step's log as Bitbucket writes it. The list, steps and logs are free; running, re-running and stopping pipelines are Pro (US$1.50 a month, with a 30-day trial).
Get it on JetBrains Marketplace
Atlassian, Bitbucket and Bitbucket Pipelines are trademarks of Atlassian; Docker is a trademark of Docker, Inc.; GitHub is a trademark of GitHub, Inc.; IntelliJ IDEA is a trademark of JetBrains s.r.o. This page is not affiliated with or endorsed by Atlassian, Docker, GitHub or JetBrains.
More: "Cannot connect to the Docker daemon" · "authorization denied by plugin pipelines" (Docker) · "Container 'docker' exceeded memory limit." · Pipelines did not run after a push · "Container 'Build' exceeded memory limit." · "Exceeded build time limit of 120 minutes" · The cache is not used · "unexpected system error" · "command not found" · Submodules not cloned · "We couldn't clone the repository" · "No online runner available" · "No space left on device" · "Step exceeded processing limits" · The artifact is missing in the next step · The build passed before and fails now · Build setup takes too long · "You've run out of build minutes" · "Permission denied to update branch" · "You have reached your pull rate limit" (Docker Hub) · "There is an error in your bitbucket-pipelines.yml" · "This pipeline was paused" (deployments) · A variable is empty · Test results not showing · Bitbucket Pipelines plugins for IntelliJ IDEA, compared · 日本語