Skip to main content

GitLab - Using an SSH key to connect to private repo

Step-by-step guide on configuring a GitLab CI/CD runner to securely use a private SSH key for accessing and cloning private repositories.

· 5 min read
View raw
Christophe
Markdown, WSL and Docker lover ~ Insatiable curious.
Cet article existe aussi en français — lire la version française.

This article is over a year old but was reviewed on July 30, 2026 — the content is still accurate.

GitLab - Using an SSH key to connect to private repo

⚡
TL;DR

This guide provides a step-by-step tutorial on how to use a private SSH key within a GitLab CI/CD pipeline to access private repositories. You will learn how to generate an SSH key, securely store the base64-encoded private key as a masked CI/CD variable, and configure your .gitlab-ci.yml to use this key for authenticating and cloning private repos during your CI jobs.

In this article, we'll see how to use a private SSH key using a GitLab CI.

The need: when running my CI, the GitLab runner has to be able to connect to my self-hosted (and private) GitLab environment. This is because it'll need to pull private projects from there. Think of a PHP project f.i.: in my composer.json, I'm referring to dependencies hosted on my GitLab server. Or, same for a JavaScript project and its package.json file.

Another example: my CI will produce some files (like a .pdf one) by converting some Markdown documents to a real documentation. At the end of this conversion, the PDF has to be pushed to the repository.

My need is thus: I should share my SSH key with the GitLab runner.

Two sibling articles about the same CI: GitLab - Using Docker private images, which solves the same authentication problem for a private image rather than a private repository, and GitLab - Running Docker-out-of-Docker in your CI.

Here's the payoff once the CI variable is in place: the job clones the private repo over SSH instead of failing with a permission error.

Why It Works​

  • The private key never touches your .gitlab-ci.yml or your repo — it lives as a base64-encoded, masked CI/CD variable, decoded back into a file only inside the running job.
  • One variable can be scoped to a single repo, a whole GitLab group, or the entire instance, so you set it up once and reuse it everywhere you need the same private access.

Information

To illustrate this article, let's assume christophe is my user account on my self hosted GitLab server and that server is called my_self_hosted_gitlab.

Remember to replace these two constants with yours ;-)

First, I need to have an SSH key​

For the long story, read my SSH - Launch a terminal on your session without having to authenticate yourself article.

I'll create a private key by running ssh-keygen -t ed25519 -C "christophe@my_self_hosted_gitlab" -f ~/.ssh/id_ed25519_my_self_hosted_gitlab.

That command will create the ~/.ssh/id_ed25519_my_self_hosted_gitlab file. It's my private key and this is the one I'll need in the next step.

Then, I will encode it for better security​

By running cat ~/.ssh/id_ed25519_my_self_hosted_gitlab | base64 -w 0, I'll base64 encode my private key as a very long string with alphanumeric characters.

I have to copy that long string in the clipboard.

Third, I have to create a SSH_PRIVATE_KEY CI variable​

Here I've a lot of possibilities:

  • I'll use the variable just for one repo;
  • I'll use the variable for all repositories of a given group (a GitLab group is like a folder containing multiple repo);
  • I'll use the variable for all repositories stored on my GitLab instance.

By going to my repository, I have to click on CI/CD Settings, expand the Variables area so I can add a new variable.

The variable name should be SSH_PRIVATE_KEY and I'll select Masked (or Masked and hidden) for the visibility. For the value, I'll paste the base64 encoded string I've just copied in the clipboard.

I'll finalize the creation of the variable by clicking on the Add variable button present at the bottom of the pane.

Information

If I want to use the key for all repositories of a given group, I just need to proceed the same way but not at the repository level, rather at the group level.

Information

And, if I'm a GitLab admin, I can start the admin interface and from there, go to the CI/CD Settings page and proceed the same way.

Finally, I have to adjust my .gitlab-ci.yml file​

As an example, I'll reuse the example provided by GitLab: https://gitlab.com/gitlab-examples/ssh-private-key/-/blob/main/.gitlab-ci.yml

Note

Based on the used image, you'll need to use apt or apk.

For an ubuntu image like the example below, it'll be apt. If, f.i. you're using the docker image, then, replace apt-get update -y && apt-get install openssh-client git -y by apk update && apk add --no-cache openssh-client git.

using_ssh_key:
image: ubuntu
before_script:
- |
apt-get update -y && apt-get install openssh-client git -y
eval $(ssh-agent -s)
mkdir -p ~/.ssh && chmod 700 ~/.ssh
- |
echo "$SSH_PRIVATE_KEY" | base64 -d > ~/.ssh/id_ed25519
cat ~/.ssh/id_ed25519
chmod 400 ~/.ssh/id_ed25519
- |
ssh-keyscan my_self_hosted_gitlab >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
script:
- | # try to connect to my GitLab server
ssh -T git@my_self_hosted_gitlab
# - | # try to clone a private repo
# git clone git@my_self_hosted_gitlab:my_repo.git

This is a GitLab CI job configuration that sets up SSH authentication to connect to a self-hosted GitLab server. It installs SSH tools, decodes a stored private key, registers it with the SSH agent, and verifies the connection works by attempting an SSH login.

Note

If the CI fails with an error like load pubkey "id_ed25519": invalid format or error in libcrypto, one cause can be the key used: the variable SSH_PRIVATE_KEY should be initialized with the private key; not the public one.

The base64 string should be created like this: cat ~/.ssh/id_ed25519_my_self_hosted_gitlab | base64 -w 0 (and, thus, not using the .pub file).

Conclusion​

A key pair, a base64-encoded, masked CI/CD variable, and a few lines in .gitlab-ci.yml are all it takes to let your CI clone a private repository over SSH — no secret ever committed to your codebase. See GitLab - Using Docker private images for the same authentication problem applied to a private Docker image instead of a private repository.


🦋 Join the conversation on BlueskyRequires a free Bluesky account to like, repost or comment. This block talks to Bluesky's public API directly from your browser (no cookies, no sign-in) — that means your IP address reaches Bluesky's servers just by this section loading.

Loading comments…

Was this article helpful?
Did you try the steps in this article?