Bryan Primus

Using SSH to Connect Local Git to Remote Repositories

8 min read | Published on 2024-11-13

TL;DR

  • SSH uses a public and private key pair instead of tokens, making it more secure and convenient for Git authentication.
  • Generate keys with ssh-keygen -t ed25519, then add the public key to our remote repository platform (GitHub, GitLab, Bitbucket).
  • Use ssh-agent and an SSH config file with AddKeysToAgent and UseKeychain so we only enter our passphrase once.
  • Test the connection with ssh -T git@github.com to verify everything is working.

GitHub, GitLab, Bitbucket and many more are platforms to host remote repositories using Git. The two most common ways to connect to a remote repository from local Git are HTTPS and SSH.

Each has its own advantages and disadvantages, but throughout this blog post we will focus on SSH since it is the most commonly used method and it offers many benefits compared to HTTPS.

Note: This post assumes git is installed on the local machine. It can also be used as a practical guide for setting up a new SSH connection to a remote repository. With existing SSH keys, jump straight to Generating SSH Keys.

SSH and Git

SSH is a protocol that allows us to securely communicate between a local machine and a remote host. Git is a distributed version control system designed to track changes in files and directories over time.

Git is a collaboration tool that allows us to work on projects collaboratively, and using SSH will help us communicate our changes to other people in a secure way.

Benefits of Using SSH over HTTPS

Security

When using HTTPS to connect to a remote repository, we typically use a Personal Access Token (PAT) for authentication. Platforms like GitHub deprecated password based HTTPS authentication back in 2021, so tokens are now the standard. The token and data are encrypted in transit, so they’re not easily readable by anyone on the network. However, we still need to enter or cache our token for each session, and if the token is leaked or stolen, anyone with it can access our repositories until we revoke it.

When using SSH, we use a private and public key pair for authentication, which works differently:

  • Private Key is stored only on our local machine. Think of it as a digital signature unique to us. It is encrypted and often secured with a passphrase.
  • Public Key is stored on the remote server. This is what our private key “unlocks” to prove it is really us.
sequenceDiagram
    participant Local as Our Machine
    participant Server as Remote Server
    Local->>Server: Connection request
    Server->>Local: Send challenge (newly generated)
    Note over Local: Solve challenge using Private Key
    Local->>Server: Send response
    Note over Server: Verify response using Public Key
    Server->>Local: Authenticated

Our local machine uses the private key to prove our identity without sending the private key itself over the network. Instead, the server sends a challenge to our local machine that only our private key can solve. This challenge is newly generated every time we try to connect to the remote server. After our machine solves the challenge, the server verifies the response using our public key. If it matches, we are authenticated. Because the challenge changes every time, intercepting the network traffic won’t help an attacker gain access.

Convenience

Using SSH is more convenient than HTTPS for connecting to a remote server because it eliminates the need to enter our token every time. Instead, we can use an SSH agent, which securely stores our passphrase in memory and handles the authentication process for subsequent connections.

Generate SSH Keys

There are several algorithms that can be used to generate SSH keys. In this blog post, we will use Ed25519 (Edwards-curve Digital Signature Algorithm) because it is modern and faster than other algorithms.

Check Existing SSH Keys

Before generating a new SSH key, we should check if we already have one because we don’t want to accidentally overwrite an existing key.

Terminal window
### input
ls -al ~/.ssh

Look for id_ed25519 and id_ed25519.pub as these are the default file names for SSH key generation.

Terminal window
### output
total 40
drwx------@ 7 username staff 224 Nov 5 15:45 .
drwxr-x---+ 42 username staff 1344 Nov 5 19:18 ..
-rw-r--r--@ 1 username staff 185 Sep 11 19:00 config
-rw-------@ 1 username staff 464 Sep 11 18:24 id_ed25519
-rw-r--r--@ 1 username staff 104 Sep 11 18:24 id_ed25519.pub
-rw-------@ 1 username staff 1842 Oct 25 16:47 known_hosts
-rw-------@ 1 username staff 1106 Oct 25 16:43 known_hosts.old

If we see id_ed25519 and id_ed25519.pub then we already have SSH keys. If we don’t want to overwrite them, we will need to specify a different path for the next key generation. If we want to use our existing SSH keys, we can skip the creation step and continue to Connecting to a remote repository.

Create a New SSH Key

Terminal window
# input
ssh-keygen -t ed25519 -C "your_email@example.com"

-t is the algorithm type, -C (optional) is custom comment.

Terminal window
# output
Generating public/private ed25519 key pair.
Enter file in which to save the key (/Users/username/.ssh/id_ed25519):

Press enter to accept the default path or provide a custom path with the file name.

Terminal window
# output
Enter passphrase (empty for no passphrase):

Passphrase is optional, but I highly recommend entering one to protect the use of our private SSH key. We will be using ssh-agent later so that we don’t have to enter the passphrase every time we make a connection.

Terminal window
# output
# dummy data
Your identification has been saved in /Users/username/.ssh/id_ed25519
Your public key has been saved in /Users/username/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:nds92nn/dsNDsadbjndkansdjsknadkj your_email@example.com
The key's randomart image is:
+--[ED25519 256]--+
| o.**|
| o**|
| ++**|
| . +=*|
| S. . +o*o|
| o..=.o.=|
| --o+o+o|
| .. o=*=|
| o+E=|
+----[SHA256]-----+

Locate the Newly Generated SSH Key

Verify whether the key was generated successfully by running:

For public key:

Terminal window
# input
cat ~/.ssh/id_ed25519.pub

Adjust the path if using a custom one.

Terminal window
# output
# dummy data
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB8Dkha0+XZ9sjd34fGFkeGZkHZck9TZx7Hnm0Dd9e2j bry@yourhost

For private key:

Terminal window
# input
cat ~/.ssh/id_ed25519

Adjust the path if using a custom one.

Terminal window
# output
# dummy data
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktZDI1NTE5AAAAIB8Dkha0+XZ9sjd34fGFkeGZkHZck9TZx7Hnm
0Dd9e2j+AAAAAAAAAAAAAAAAAAEAAAAIB8Dkha0+XZ9sjd34fGFkeGZkHZck9TZx7
Hnm0Dd9e2j+AAAAAlkdiJHDU6FOSZDa7yxptjMNxzHxgtid7YKm2GFjdjXaAAAAAE
2jxKVmksl3Dbjoef1H5/tf1sgYnJe+JqfgvUhrBgkgmfhdAAAAAAEAAAXLS1mhdhd
bHJdkfHZ9GFFX3NNMgVdk+KfbkhTgyg==
-----END OPENSSH PRIVATE KEY-----

If everything is done correctly, we should see our public and private key in the output as shown above.

Connect to a Remote Repository

Add Public Key to Remote Repository

We need to add our public key to the remote repository platform we are using. Visit one of these links:

Note: Links provided may become invalid in the future as I don’t have control over any changes the platforms might make.

Click New SSH Key or Add SSH Key.

Give the key a descriptive name.

Copy the public key from the local machine:

Terminal window
# input
pbcopy < ~/.ssh/id_ed25519.pub

Adjust the path if using a custom one.

Paste the public key in the Key field and save.

Add Our Private SSH Key to the ssh-agent

First, we need to start the ssh-agent running in the background.

Mac:

Terminal window
exec ssh-agent zsh

WSL using bash:

Terminal window
exec ssh-agent bash

Windows (I haven’t tested this):

CMD:

Terminal window
start-ssh-agent

PowerShell:

Terminal window
Start-Service ssh-agent

Then add the private key to the agent:

Terminal window
ssh-add C:\path\to\your\key

Set Up the SSH Config File

Check if we have an existing ssh config file.

Terminal window
cat ~/.ssh/config

If we don’t have one, create a new config file.

Terminal window
touch ~/.ssh/config
open ~/.ssh/config

Update our config file based on the remote host we are using.

For example:

Terminal window
# For GitHub
Host github.com
AddKeysToAgent yes
UseKeychain yes # macOS only
IdentityFile ~/.ssh/id_ed25519

UseKeychain will save our passphrase in the keychain, which will be used to unlock our private key when we make a connection. This way, we don’t have to enter the passphrase again every time we start a new session or restart our computer.

Test the SSH Connection

Terminal window
# input
# github
ssh -T git@github.com
# gitlab
ssh -T git@gitlab.com
# bitbucket
ssh -T git@bitbucket.org

If everything goes well, we should see the following output:

Terminal window
# output
Hi username! You've successfully authenticated, but GitHub does not provide shell access.

Now we are ready to connect, clone repositories, and push changes to our remote repository!

Wrapping Up

To recap what we covered in this post: we started by understanding why SSH is a better choice than HTTPS for connecting to remote repositories. Then we generated an Ed25519 key pair, added the public key to a remote repository platform, configured the SSH agent and SSH config file for convenience, and finally tested the connection. We should now be able to push, pull, and clone repositories over SSH without entering credentials every time.

Questions Worth Exploring

These topics go deeper into some of the concepts we touched on in this post. I will cover them in future posts.

  1. Why is a passphrase necessary? We mentioned that a passphrase protects our private key, but what exactly happens if someone gets access to our private key without a passphrase?
  2. What is ssh-agent and how does it work? We used ssh-agent to avoid entering our passphrase repeatedly, but how does it actually store and manage our keys in memory?