Reaching a private Git server from three machines
This is a record of one working session. It covers how git.zugriff.at is set up, how to reach it, adding a second machine, and an error that came up along the way. Addresses, ports, account names and key fingerprints are replaced with placeholders such as <private-ip>.
The setup
- Edge server: the public web server. It also hosts git.zugriff.at as plain bare repositories over SSH, with no web interface.
- Main server: a second cloud server on the same private network as the edge server.
- Home server: a machine at home that connects to the main server through a WireGuard tunnel.
- Laptop.
The edge server accepts SSH only over the private network. The firewall blocks it from the internet, and sshd only allows logins from private addresses. The Git account's keys are also tied to the main server's private address. In practice, every Git connection has to come from the main server or pass through it.
Connecting from the main server
This already worked. The SSH config there points the public name at the edge server's private address, uses a dedicated key, and pins the host key:
Host git.zugriff.at
HostName <edge-private-ip>
HostKeyAlias <edge-private-ip>
User <git-user>
IdentityFile ~/.ssh/git_zugriff_ed25519
IdentitiesOnly yes
StrictHostKeyChecking yes
Usage:
ssh <git-user>@git.zugriff.at list # list repositories
ssh <git-user>@git.zugriff.at create myrepo # create a bare repository
git clone <git-user>@git.zugriff.at:myrepo.git
git remote add origin <git-user>@git.zugriff.at:myrepo.git
The account's shell is git-shell. You can't log in interactively, and it only accepts list, create and normal Git operations. A test run of list returned three repositories.
Connecting from the laptop
The laptop has no direct route to the edge server. It goes through the main server with ProxyJump, on the main server's SSH port, using a key that the Git account already trusts.
Adding the home server
The home server already reaches the main server through a locked-down jump account. That account can't open a shell. Its key may only forward to one destination, the edge server's SSH port. Because forwarding to that port is already allowed, the jump account's settings didn't need to change. Only the Git account needed a new key.
A different session manages the home server and the jump account. On request, that rule stayed absolute: nothing was changed there from here. The steps below were written up as a handoff for that session to carry out.
- Create a dedicated key on the home server, separate from the tunnel key:
ssh-keygen -t ed25519 -f ~/.ssh/git_zugriff_ed25519 -C "home→git.zugriff.at" -N "" - Add an SSH config entry that jumps through the existing jump-host entry:
Host git.zugriff.at HostName <edge-private-ip> HostKeyAlias <edge-private-ip> User <git-user> IdentityFile ~/.ssh/git_zugriff_ed25519 IdentitiesOnly yes StrictHostKeyChecking yes ProxyJump <jump-host-alias> - Pin the edge server's ed25519 host key in
known_hosts. Compare the fingerprint over a channel you already trust. - Send the public key back so it can be added to the Git account. It gets the same restrictions as the other keys:
restrict, and logins only from the main server's private address. - Test it:
ssh <git-user>@git.zugriff.at list.
The home-server session did steps 1–3. It backed up the old SSH config first, and the host key was already pinned with a matching fingerprint. It then sent back the public key.
The jump goes over WireGuard to the main server, but the last hop from the main server to the edge server still comes from the main server's private address. The address restriction on the Git account therefore still matches.
Adding the key on the edge server needed a manual step. Claude Code's auto mode blocked the assistant from reading the Git account's authorized_keys on the production edge server. So instead it handed over a command to run by hand. That command backs up the file, appends the restricted key, and shows the result.
The error: "This account is currently not available"
The first test from the home server printed this message. It comes from nologin. It means something tried to open a login shell for an account that isn't allowed one.
- It isn't the Git account. Its shell is
git-shell, which prints a different error. - The main server's jump account uses
nologin, so that's where the message came from. - The main server's SSH log showed the home server connecting to the jump account at that moment. One attempt closed during authentication. The next one logged in with a different key from the two used earlier that day.
Likely cause: the connection to the jump host asked for a shell instead of only forwarding. That happens if you test with ssh <jump-host-alias> directly, or if the jump entry has RemoteCommand or RequestTTY set. A plain ProxyJump only forwards and never asks for a shell.
The home-server session was asked to check:
ssh -G git.zugriff.at | grep -iE '^(user|hostname|port|proxyjump|proxycommand|identityfile) '
ssh -G <jump-host-alias> | grep -iE '^(user|hostname|port|identityfile|remotecommand|requesttty|proxycommand) '
ssh -v <git-user>@git.zugriff.at list 2>&1 | grep -iE 'proxy|jump|authenticated|offering|channel|not available' | head -40
What a correct setup looks like: the jump entry has no RemoteCommand and no RequestTTY, and it uses the same key the home server already uses for the jump account, with IdentitiesOnly yes. Test with the Git command, not by logging in to the jump host.
Status
- Main server: working.
- Laptop: working through the main server.
- Home server: key and config are in place. Still open: confirm the key was added on the edge server, then fix the jump entry and test again.
Lessons
- Add machines through an existing restricted jump instead of opening new paths. Then the only change needed is one restricted key on the Git account.
- Give every machine and purpose its own key. Don't reuse the tunnel key for Git.
- "This account is currently not available" points at a
nologinaccount. Check which hop opened a shell. - Keep ownership boundaries strict when more than one agent session manages the infrastructure. Write a handoff message instead of crossing into another session's area.