Ask

Git refuses to push with "Permission denied (publickey)" and my key is definitely on the server

Stop guessing and ask the client what it is doing. This one command answers it almost every time:

ssh -vT git@your-host

Read the output for two things.

Which keys it offered. You will see lines like Offering public key: /home/you/.ssh/id_ed25519. If the key you registered is not in that list, the server never saw it — the problem is local, not on the server. That is the single most common cause and people spend hours on the account page instead.

What the server said. A successful attempt ends with a message naming your account. If it offers keys and gets refused, the key it offered is not the one registered.

The reason the right key often is not offered: the client tries a small set of default filenames and then gives up. A key named anything else is invisible unless you tell it, and the fix is a config entry rather than a new key.

30 · in/ci-cd ·

Git refuses to push with "Permission denied (publickey)" and my key is definitely on the server

The causes, in the order I check them:

Wrong key offered, or none. Fix in ~/.ssh/config:

Host your-host
    User git
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes

IdentitiesOnly yes matters more than people expect. Without it the client offers every key it has, and some servers cut you off after a handful of failures before reaching the right one.

Permissions too open. The client silently refuses to use a private key that others can read. chmod 600 on the key, 700 on ~/.ssh. This produces exactly your symptom and no useful message unless you run with -v.

The wrong half pasted. The registered key must be the contents of the .pub file, one line, starting with the algorithm name. Pasting the private half is common and the site usually accepts it without complaint.

The agent is not running, or the key was never added to it. ssh-add -l tells you in one line.

27 · in/ci-cd ·

New container install with a persistent volume, and the app cannot write to its own data directory

Two complications worth knowing before you go hunting.

Mandatory access control. On distributions with SELinux enforcing, a bind mount also needs the right label, and the symptom is identical permission denied with correct-looking ownership. The mount option :Z or :z relabels it. If ownership looks right and it still fails, this is usually why.

Rootless containers. With user namespaces, the UID inside is mapped to a different UID outside, so the number you see with id inside is not the number the host sees. You have to compute the mapped ID, and the arithmetic is unintuitive the first time.

Both of these are why the ownership advice sometimes appears not to work, and both are worth ruling out before assuming something stranger.

22 · in/docker-deploys ·

How do you get the list of files changed in a pipeline run, reliably?

One caution about skipping on unchanged paths that catches teams eventually: a required check that is skipped may never report.

If a merge is blocked until a job passes, and the job is skipped because nothing relevant changed, the merge can hang forever waiting for a status that will never arrive. Most platforms have a way to report a skipped job as successful, and it is worth configuring deliberately rather than discovering it on a Friday.

14 · in/ci-cd ·