Route Git Through a Controlled External Transport with git-remote-ext
You will finish with a working pattern for sending Git's smart-transport traffic through an external command, such as ssh, socat or a site-specific wrapper. You will also know what Git substitutes into the command, which arguments are consumed by the helper, and where debugging output comes from.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about twenty minutes. You need Git 2.43.0 or a nearby release, a repository URL that you are authorised to access, and the external program named in the remote URL. The examples are unprivileged. Nothing here needs sudo, and the checks do not alter a server.
1. Confirm the installed helper
This guide follows the installed git-remote-ext(1) from the Ubuntu git-man package, version 1:2.43.0-1ubuntu7.3. Check your own installation before copying a command because the helper's contract belongs to your Git version:
$ git --version
git version 2.43.0
$ dpkg-query -W -f='${Package} ${Version}\n' git-man
git-man 1:2.43.0-1ubuntu7.3
$ git remote-ext --help
usage: git remote-ext <remote> <url>
The helper is normally reached indirectly. Git sees a URL beginning with ext::, starts the command after that prefix, and connects the command's standard input and output to the Git service.
Checkpoint
Your version is known, and the external program you intend to run is installed and trusted.
2. Start with an SSH transport
For an SSH connection with an explicit key, add a remote using the long service substitution %S. Git replaces it with the service name it needs, such as git-upload-pack for a fetch:
$ git remote add origin "ext::ssh -i /home/you/.ssh/repository_key [email protected] %S /srv/git/project.git"
$ git remote get-url origin
ext::ssh -i /home/you/.ssh/repository_key [email protected] %S /srv/git/project.git
Replace the key path, account, host and repository path with values you have verified. The SSH process receives Git's protocol stream on standard input and must return the remote service's stream on standard output. Do not add shell syntax such as |, && or command substitutions to this URL: git-remote-ext splits the command and arguments on unescaped spaces; it does not describe a shell script.
Fetch only after checking the stored URL. This contacts the remote and may update local remote-tracking refs, so treat it as a normal network operation:
$ git fetch origin
From example.org:/srv/git/project
* [new branch] main -> origin/main
Exact progress text varies. The useful result is exit status zero and a remote-tracking update. If the remote is push-only or the path is wrong, Git and SSH will report that without changing the remote configuration.
3. Use the percent substitutions deliberately
The substitutions are part of the remote URL syntax, not shell variables:
%%becomes one literal percent sign.%sbecomes the short service name, such asupload-pack,receive-packorupload-archive.%Sbecomes the long name, such asgit-upload-pack. This is usually the convenient form for SSH.%G/repo, only when it starts an argument, asks the helper to send a Git protocol service request and uses/repoas its repository field.%Vhost, only when it starts an argument, supplies a virtual host field for that Git protocol request.
A literal space inside one command argument is written as % , a percent sign followed by a space. For example, the following describes a helper argument containing spaces:
ext::git-server-alias foo %G/repo% with% spaces %Vvirtual-host
Do not put %G or %V in the middle of an argument. They are recognised only at the start of one. Also do not pass the repository path twice: with %G/repo, the path is sent in the protocol request rather than passed as an ordinary argument.
4. Connect a Git protocol service through a tunnel
Use %G when the external command is a byte-preserving tunnel to a Git protocol endpoint. The manual's representative shape uses socat and an abstract Unix socket:
$ git remote add tunnel "ext::socat -t3600 - ABSTRACT-CONNECT:/git-server %G/somerepo"
$ git remote get-url tunnel
ext::socat -t3600 - ABSTRACT-CONNECT:/git-server %G/somerepo
This is a configuration example, not a promise that your host has that socket or that socat is installed. Test a real tunnel only during an approved maintenance window. A tunnel can expose repository data to a different security boundary, and a service disruption is possible if you replace an existing remote without a rollback plan.
5. Inspect the service environment with a wrapper
If a custom helper needs to choose between fetch, push and archive operations, it can read two environment variables that Git passes to the external command:
GIT_EXT_SERVICEcontains the long name, for examplegit-upload-pack.GIT_EXT_SERVICE_NOPREFIXcontains the name without thegit-prefix, for exampleupload-pack.
Keep a wrapper small and explicit. It should validate the service name, select a fixed trusted destination, and then replace itself with the tunnel or server connector. Never concatenate an untrusted repository name into shell source.
#!/bin/sh
set -eu
case "${GIT_EXT_SERVICE_NOPREFIX-}" in
upload-pack|receive-pack|upload-archive) ;;
*) echo "unsupported Git service" >&2; exit 64 ;;
esac
exec /usr/bin/ssh [email protected] "/usr/local/bin/git-$GIT_EXT_SERVICE_NOPREFIX"
The wrapper above is illustrative: its final command must match the remote system's interface. Install it only in a location you control, make it executable, and test it with a disposable repository before assigning it to a production remote. If the wrapper rejects the service, Git will return a non-zero status and no fetch or push should be treated as successful.
6. Diagnose a failed connection
First inspect the URL and the external command without changing anything:
$ git remote get-url origin
$ command -v ssh
/usr/bin/ssh
$ ssh -G example.org | sed -n '1,12p'
The last command prints effective SSH configuration. It may include sensitive host details, so do not paste it into a public issue without review.
For transport-level reads and writes, set GIT_TRANSLOOP_DEBUG for one command:
$ GIT_TRANSLOOP_DEBUG=1 git ls-remote origin
<debug output varies by Git version>
This can expose protocol details and paths in your terminal or logs. Use it briefly and redact output before sharing it. The variable reports reads and writes; it does not repair a bad SSH key, missing socket or rejected service.
If you decide the remote entry is wrong, remove only that entry and recreate it with the corrected URL:
$ git remote remove tunnel
$ git remote -v
Removal changes local repository configuration but does not delete the remote repository. Before removing an important remote, save its URL with git remote get-url tunnel. There is no elevated-privilege step in this recovery.
Done means
- You confirmed the installed Git and
git-manversions. - Your
ext::URL names a trusted external command and has been checked withgit remote get-url. - You chose
%S,%s,%Gand%Vaccording to the remote protocol rather than guessing. - A fetch, push or archive operation has been tested against an authorised endpoint.
- Any wrapper validates
GIT_EXT_SERVICE_NOPREFIXbefore opening a tunnel. - Debugging output and SSH configuration details will be treated as sensitive operational data.