entry-32
Permission denied, and the server was fine
A read-only audit of two Oracle Cloud VMs and a GCP VM failed to log in. Where the error was Permission denied (publickey), the keys were already authorized. Batch mode never offered them, because the private keys have passphrases.
revisions rev. 1 first set 2026-10-23 last revised 2026-10-23 2 min read
Two of my cloud VMs are on Oracle Cloud, and one is on GCP. An automated
read-only audit tried to log in to all three and failed on all of them. Two
returned Permission denied (publickey). The third just timed out.
I was about to open the provider consoles
My first diagnosis was wrong: I thought the servers did not accept my keys. One Oracle VM did accept its original launch key, and that seemed to confirm it. So the plan was to add keys, and I was about to open the provider consoles to do it.
The servers already had the keys
The keys were already authorized on the servers. The problem was on my
side: my private key files are passphrase protected. Automated tools run
SSH in batch mode, which cannot prompt for a passphrase, so the key is
skipped and the server answers Permission denied (publickey). That is the same error the server
reports when it rejects a key.
Two checks, both on my side
The intended public key was already present in authorized_keys. I
checked the private key with this command, replacing the sample path
with my key’s path. It failed because the key has a passphrase:
ssh-keygen -y -P '' -f /path/to/private-key
ssh-add -l showed the key was not loaded in the agent.
Loading the keys, then checking the timeout
I ran ssh-add on the three keys once per session and entered their
passphrases. That cleared the key failures.
The third VM’s timeout had another cause: its ephemeral public IP had changed after a stop/start. Once that separate address problem was accounted for, all three VMs connected.
What I check before I blame the server
I check ssh-add -l, and whether the key has a passphrase, before I
blame the server. Permission denied (publickey) only means no offered
key worked. That includes keys that were never offered.
For addresses, I prefer a Tailscale name over an ephemeral public IP.
It’s the same lesson as the API key my coding agent was actually sending: compare what is actually being sent, not what you think is being sent.
Related posts: Patching four cloud servers took more than apt upgrade.