Migrate to RunxBuild and earn up to $50 in hosting credit on your first deposit.

Calculate your savings
unxBuild

GitLab Add SSH Key: The Five Minute Setup and the Errors That Follow

Sean

Platform Writer

Aug 17, 2026
8 min read

Generate a key with ssh-keygen -t ed25519, copy the contents of the .pub file into GitLab under Preferences and then SSH Keys, and test with ssh -T [email protected]. The whole thing is five minutes, and four of them are spent finding the settings page.

GitLab Add SSH Key: The Five Minute Setup and the Errors That Follow

The part that goes wrong is almost never the key generation. It is pasting the private key instead of the public one, or adding the key to the wrong account, or having a working key that the SSH client never offers because it is not in the agent. Each of those produces the same unhelpful Permission denied (publickey), which is why this ends up taking an hour instead of five minutes.

Table of contents

Generate the key

Check for an existing one first. Most people already have a key and do not need another.

ls -la ~/.ssh/*.pub

If id_ed25519.pub or id_rsa.pub is there, you can reuse it. The same key works across GitLab, GitHub, and any server that has your public half. There is no reason to generate one per service, and doing so mostly creates config you have to maintain.

ssh-keygen -t ed25519 -C "[email protected]"

# only if something in your chain cannot handle ed25519
ssh-keygen -t rsa -b 4096 -C "[email protected]"

Ed25519 is the default recommendation now: shorter keys, faster operations, no key-size parameter to get wrong. Use RSA 4096 only when a legacy server or appliance refuses ed25519, which is increasingly rare.

Use a passphrase. The prompt makes it optional and it is worth the twenty seconds — an unprotected private key is a file that grants access to everything the key touches to anyone who copies it. The agent means you type it once per session, not once per push.

The comment after -C is arbitrary text stored in the public key. Make it identify the machine — you@work-laptop — because in two years the GitLab key list is the only place recording which key is on which device.

Add the public key to GitLab

Copy the public key. The .pub file. Never the other one.

# macOS
pbcopy < ~/.ssh/id_ed25519.pub

# Linux with xclip
xclip -sel clip < ~/.ssh/id_ed25519.pub

# Windows, Git Bash
cat ~/.ssh/id_ed25519.pub | clip

# or just print it and copy by hand
cat ~/.ssh/id_ed25519.pub

A public key is one line beginning ssh-ed25519 AAAA... and ending with your comment. If what you copied spans many lines and starts with -----BEGIN OPENSSH PRIVATE KEY-----, stop — that is the private key. Do not paste it anywhere. If you already did, delete it from GitLab and generate a new pair.

In GitLab: click your avatar, choose Edit profile, then SSH Keys in the left sidebar, then Add new key. Paste, name it after the machine, set an expiry date, and save.

GitLab checks new keys against a list of known compromised keys and refuses anything on it. If yours is rejected for that reason, it means the private half is publicly known — generate a fresh pair rather than trying to work around it.

The expiry field is optional and worth setting. An expired key stops working and tells you so clearly, which is much better than a key from a laptop you sold in 2023 quietly retaining access.

Verify before you assume

ssh -T [email protected]

A working setup prints Welcome to GitLab, @yourusername!. Read the username in that message. Authenticating successfully as the wrong account is a real and confusing failure — everything looks fine until a push is rejected for lacking permission on a project you definitely have access to.

For a self-managed instance, substitute the host: ssh -T [email protected].

When it fails, the verbose flag says why:

ssh -vT [email protected] 2>&1 | grep -Ei 'offering|identity|authentication'

Look for the Offering public key lines. If your key is not among them, the client never tried it, and the problem is client configuration rather than anything on GitLab’s side. That single distinction resolves most of these cases.

The agent, and the config file

SSH offers a limited number of keys per connection attempt. On a machine with several, yours may never get offered before the server gives up. The agent and an explicit config entry both fix this.

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
ssh-add -l          # list loaded keys

To make it persistent, an entry in ~/.ssh/config:

Host gitlab.com
  HostName gitlab.com
  User git
  IdentityFile ~/.ssh/id_ed25519
  IdentitiesOnly yes
  AddKeysToAgent yes

IdentitiesOnly yes is the important line. Without it, SSH offers every key it can find in order, and on a machine with five keys the server may refuse the connection before reaching the right one. With it, only the file you named is offered.

On macOS, add UseKeychain yes to store the passphrase in the system keychain so you are not retyping it every reboot.

This is also the mechanism for multiple GitLab accounts — work and personal. Define a fake host alias per account with its own IdentityFile, and clone using the alias:

Host gitlab-work
  HostName gitlab.com
  User git
  IdentityFile ~/.ssh/id_ed25519_work
  IdentitiesOnly yes
git clone git@gitlab-work:group/project.git

Deploy keys are a different thing

A personal SSH key represents you and carries your permissions on every project you can see. That is wrong for a server. A CI runner or a production host should not be able to act as you.

Deploy keys are scoped to a single project and default to read-only. Add one under the project’s Settings, then Repository, then Deploy keys. Grant write access only if something genuinely needs to push.

Deploy tokens are the other option — a username and password pair for HTTPS, with a scope and an expiry, useful when SSH is awkward in the environment.

The general rule: a human gets a personal key with a passphrase, a machine gets a deploy key scoped to one project. Putting a personal key on a build server is the shortcut that turns one compromised server into access across everything you can reach.

The errors, and what each one means

  • Permission denied (publickey). Either the key is not on the account, or the client is not offering it. ssh -vT and look at the Offering lines tells you which.
  • Bad permissions / UNPROTECTED PRIVATE KEY FILE. SSH refuses keys that are group or world readable. chmod 700 ~/.ssh && chmod 600 ~/.ssh/id_ed25519.
  • Host key verification failed. The server’s fingerprint changed or is unknown. Compare against GitLab’s published fingerprints before accepting anything.
  • Could not open a connection to your authentication agent. No agent running. eval "$(ssh-agent -s)" then ssh-add.
  • Works over SSH but git push still asks for a password. The remote is an HTTPS URL. Check with git remote -v and switch it: git remote set-url origin [email protected]:group/project.git.
  • Key was working and stopped. Check the expiry date on the key in GitLab. This is the most common cause of a setup that broke without anyone changing anything.

How this fits the rest of the stack

Key management is one of those tasks that is easy once and tedious across a team — new laptop, new engineer, expired key, someone leaves and nobody is sure which servers still trust their key. Moving deployment off interactive SSH access removes most of that: RunxBuild connects to the repository and builds on push, so the credential is a platform connection rather than a key file on somebody’s machine, and access is a team permission you can revoke in one place. Deploying from GitHub on RunxBuild covers the repository connection and what happens on each push. If you are pricing a move off servers you currently reach by SSH, the RunxBuild hosting calculator breaks the service, database, storage, and bandwidth into separate line items.

Useful related references:

FAQ

How do I add an SSH key to GitLab?

Generate one with ssh-keygen -t ed25519 -C "[email protected]", copy the contents of ~/.ssh/id_ed25519.pub, then in GitLab go to your avatar, Edit profile, SSH Keys, and Add new key. Paste the public key, name it after the machine, set an expiry, and save. Verify with ssh -T [email protected].

Which file do I paste into GitLab?

The one ending in .pub — the public key. It is a single line starting ssh-ed25519 AAAA.... Never paste the file without the extension; that is your private key. If you have pasted it anywhere, delete it and generate a fresh pair immediately.

Why do I still get permission denied after adding my key?

Usually the client is not offering the key. Run ssh -vT [email protected] and look for Offering public key lines. If yours is absent, add it to the agent with ssh-add, or pin it in ~/.ssh/config with IdentityFile and IdentitiesOnly yes.

What is the difference between an SSH key and a deploy key?

A personal SSH key authenticates you and carries your permissions across every project you can access. A deploy key is scoped to a single project and is read-only by default, which is what a CI runner or production server should use. Never put a personal key on a build server.

My GitLab SSH key stopped working. What changed?

Check the key’s expiry date in GitLab — expiry is a common cause of a setup that breaks with no local change. Also verify the remote is SSH and not HTTPS with git remote -v, since an HTTPS remote prompts for credentials regardless of your key.

#gitlab add ssh key#ssh key#gitlab#ed25519#git authentication