Your SSH config file might be misconfigured
Abstract
Setting the Host option in SSH’s configuration file the correct way.
I was getting a Permission denied (publickey) error after I migrated from GitHub to Codeberg, and could not push to my new git hosting service. Even though I configured my SSH setup for Codeberg exactly like I configured it for GitHub, which (still) worked. It turned out that my understanding of the SSH config file was wrong the entire time.
My SSH configuration had been wrong for years. I never noticed it because it nonetheless worked. I want to help you avoid making the same mistake I made.
In this post I’ll show you:
- how I had SSH configured
- what was wrong about it and why
- why it worked nonetheless
- how SSH is configured correctly
How my SSH configuration file looked like#
My ~/.ssh/config file looked something like this:
Host codeberg
HostName codeberg.org
User git
IdentityFile ~/.ssh/id_ed25519_codeberg
Host github
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
Host gitlab
HostName gitlab.com
User git
IdentityFile ~/.ssh/id_ed25519_gitlab
Host *
AddKeysToAgent yes
IdentitiesOnly yes
As you can see, I have accounts with GitHub, GitLab, and Codeberg. And I want to use a different SSH keypair for each service as is best practice. Focus on the first three Host blocks. The Host * is just shown for the sake of completeness, and is irrelevant.
Perhaps you already spotted my mistake. If not, here’s the spoiler: In the Host part, it says github but not github.com. The domain of the hosting service’s URL isn’t contained in the Host part.
My mistake#
At some point in time, I had learned that you can use aliases for your hosts. I had learned that with aliases you can shorten the SSH command when you connect to your servers.
Example#
Instead of configuring SSH like so:
Host 192.168.1.10
User david
IdentityFile ~/.ssh/id_ed25519_raspberry_pi
… and instead of then connecting to your server like so:
ssh 192.168.1.10
… you could set the original address/name of the host with the optional HostName option:
Host raspberry-pi
HostName 192.168.1.10
User david
IdentityFile ~/.ssh/id_ed25519_raspberry_pi
… and then connect to your server much more conveniently like so, using an arbitrary alias that you stated in the Host part:
ssh raspberry-pi
That’s what I thought, at least. Because it worked. But that’s not how it works, more on that in a moment.
How your SSH configuration file needs to look like#
The name/address of your host stated in HostName absolutely needs to be included in the Host part too. In other words: You can’t write Host github as I did, you need to write Host github.com. Similarly, you need to write Host codeberg.org, not Host codeberg.
I had thought that, since I had already specified the hostname in HostName, I wouldn’t need to state it a second time, in Host. But you do need to.
You may nonetheless use shorter names as aliases (for example, github and codeberg as in my case), since Host allows multiple (space-separated) names. But the complete domain (that is, github.com or codeberg.org in my case) needs to be included under all circumstances in the Host part.
Therefore, my ~/.ssh/config needed to look like this if I wanted to use codeberg, github and gitlab as aliases for usage in SSH commands:
Host codeberg.org codeberg
HostName codeberg.org
...
Host github.com github
HostName github.com
...
Host gitlab.com gitlab
HostName gitlab.com
...
...
And after I applied this fix, my SSH connection to Codeberg worked.
How aliases work#
Imagine you use GitLab at work, self-hosted by your employer. But you also want to use gitlab.com on your work machine (for example, because you host your dotfiles there). So you could configure your SSH something like this:
Host gitlab.mycompany.com gitlab-work
HostName gitlab.mycompany.com
User git
IdentityFile ~/.ssh/id_ed25519_gitlab_work
Host gitlab.com gitlab-personal
HostName gitlab.com
User git
IdentityFile ~/.ssh/id_ed25519_gitlab_personal
...
Since the option Host allows multiple (space-separated) names (to be used as aliases), the normally optional option HostName becomes necessary to tell SSH which of those several names/addresses is "the actual" name/address.
Now, as an example, whenever you have SSH problems, you can debug your permission issues using these commands:
ssh -T git@gitlab-work
ssh -T git@gitlab-personal
… instead of using these commands:
ssh -T git@gitlab.mycompany.com
ssh -T git@gitlab.com
But in order for the other options, like IdentityFile, to apply, the actual addresses gitlab.mycompany.com and gitlab.com, respectively, need to be included in the Host part, as I already said. Perhaps you can already sense where I’m getting at and why my wrong configuration worked (but unlike I expected it to work).
Why did SSH work for GitHub but not for Codeberg?#
Since I had wrongly written Host github instead of the correct Host github.com or the alternative Host github.com github with an optional alias, this block Host github never even matched. The key ~/.ssh/id_ed25519_github I had specified in that block was never even used for my SSH connection.
Neither did the block Host github ever match (because it was wrongly configured) nor did any other block, thus the fallback Host * was used.
And since I didn’t explicitly specify a key for the option IdentityFile in that block Host *, SSH assumed the default name for the SSH keypair.
For RSA keypairs, that default name is ~/.ssh/id_rsa. For Ed25519 keypairs, the default name is ~/.ssh/id_ed25519. Since I did indeed have a key named id_rsa in my directory ~/.ssh/ and because I had someday uploaded that public key ~/.ssh/id_rsa.pub to GitHub and never removed it after I started using Ed25519 keypairs, I was actually using ~/.ssh/id_rsa the entire time, for all my services and servers … instead of the specifically created ~/.ssh/id_ed25519_github, ~/.ssh/id_ed25519_gitlab, et cetera. So much for best practices and using a different SSH keypair for each service.
I only noticed my mistake when I started out fresh with a new account. I had uploaded my public Ed25519 key to Codeberg but not my public RSA key, and that newly created account obviously didn’t already have my public RSA key. Consequently, I got the error Permission denied (publickey) when I tried to connect to Codeberg because SSH was using ~/.ssh/id_rsa and, unlike with GitHub or GitLab, I hadn’t added that key to my Codeberg settings.
Summary#
So that explained the mystery: I was unknowingly using ~/.ssh/id_rsa the entire time, and since I had at some point uploaded my public RSA key to both GitHub and GitLab before I started using Ed25519 keypairs, these two git hosting services continued to work when Codeberg wouldn’t work. Only when I started using a new service (Codeberg) which didn’t have a copy of my public RSA key, and after some time of figuratively banging my head against a wall, did I notice my configuration error.
Result#
After I fixed the Host entries in my ~/.ssh/config from Host codeberg to Host codeberg.org and from Host github to Host github.com as shown above, I could finally connect to Codeberg (using my Ed25519 key ~/.ssh/id_ed25519_codeberg.pub, the only SSH key I ever uploaded to Codeberg). Moreover, SSH finally used my Ed25519 key ~/.ssh/id_ed25519_github for GitHub instead of the default key ~/.ssh/id_rsa.
Final words#
Today I learned not only a bit more about how SSH works but also how to fix my broken configuration. My SSH now works as I expect it to work. Hopefully, you could — after reading thus far — also learn something and fix your SSH configuration with my help. In any case, be well.