Rewriting remote URLs with insteadOf Jump to heading
Remote URLs are written into many places: every cloneβs configuration, submodule files, dependency manifests, build scripts, documentation. When you want Git to use a different URL β SSH instead of HTTPS, an internal mirror instead of the public host, a new hostname after a migration β editing each occurrence is impractical. Gitβs url.<base>.insteadOf setting rewrites URLs as Git reads them: any URL starting with a given prefix is replaced by another prefix. pushInsteadOf does the same only for pushes. A few lines of global configuration redirect every repository, submodule and tool that goes through Git. This page covers the common rewrites, how to check which rule applies, and the pitfalls, within Git configuration management at scale.
When to use this approach Jump to heading
- You want SSH for all repositories on a host, even when URLs in submodules or tools use HTTPS.
- Clones in CI or an office should go through a local mirror.
- A Git host is being renamed or migrated, and old URLs must keep working.
- You use per-account SSH host aliases, as in managing Git credentials across hosts.
Step 1 β Understand the rule Jump to heading
url.NEW.insteadOf OLD means: when a URL starts with OLD, replace that prefix with NEW. The longest matching prefix wins if several rules match.
git config --global url."[email protected]:".insteadOf "https://github.com/"
git ls-remote https://github.com/git/git.git HEAD # actually connects over SSH Step 2 β Use SSH for pushes only Jump to heading
Fetching over HTTPS needs no credentials for public repositories, while pushing needs authentication. pushInsteadOf rewrites only push URLs, so clones work anonymously and pushes use your SSH key.
git config --global url."[email protected]:".pushInsteadOf "https://github.com/"
git remote -v
# origin https://github.com/org/service.git (fetch)
# origin [email protected]:org/service.git (push) Step 3 β Route fetches through a mirror Jump to heading
In CI or offices with slow links, point fetches at a nearby mirror while pushes still go to the real host.
# /etc/gitconfig on CI runners
[url "https://git-mirror.internal.example/github/"]
insteadOf = https://github.com/
[url "https://github.com/"]
pushInsteadOf = https://github.com/ The second rule keeps pushes on the original host: without it, pushes would also be rewritten to the mirror. Mirrors for runners are covered in reusing a Git mirror on self-hosted runners.
Step 4 β Survive a host migration Jump to heading
When repositories move to a new hostname, add a rule so old URLs β in clones, submodules and scripts β keep working while they are updated.
git config --system url."https://git.new.example/".insteadOf "https://git.old.example/"
git config --system url."[email protected]:".insteadOf "[email protected]:" Distribute the rule through managed configuration during the transition, then remove it once old URLs are gone. The full move is in moving a repository between hosting providers.
Step 5 β Apply SSH host aliases automatically Jump to heading
With per-account SSH aliases, rewrite URLs for a given organisation to the matching alias, so clone URLs copied from the web work unchanged.
git config --global url."git@github-work:work-org/".insteadOf "https://github.com/work-org/"
git config --global --add url."git@github-work:work-org/".insteadOf "[email protected]:work-org/"
git clone https://github.com/work-org/service.git # uses the work key Step 6 β Check which rule applies Jump to heading
Rewrites are invisible in normal output, which makes them confusing when they misfire. Check the effective URL and list the rules with their origins.
git config --show-origin --get-regexp '^url\.'
git ls-remote --get-url origin # URL after rewriting
GIT_TRACE=1 git ls-remote origin HEAD 2>&1 | grep -E 'run_command|ssh|https' | head -n 3 Step 7 β Avoid rewrite loops and surprises Jump to heading
A rule whose new value also matches another ruleβs old prefix can chain unexpectedly, and a broad prefix can catch URLs you did not mean. Keep prefixes specific, ending in a slash or colon, and test after adding a rule.
# Too broad: also rewrites https://github.company.example/
git config --global url."[email protected]:".insteadOf "https://github"
# Specific:
git config --global --unset url."[email protected]:".insteadOf
git config --global url."[email protected]:".insteadOf "https://github.com/" Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Do rewrites apply to submodules? Jump to heading
Yes. Submodule URLs are read through the same configuration, so a global rule rewrites them too. That is one of the main reasons to use rewrites.
Do they affect package managers that use Git? Jump to heading
Tools that invoke Git for fetches β Go modules, many language package managers with Git dependencies β go through the rewrite. Tools that talk HTTP to the host directly do not.
Can a repository ship its own rewrite rules? Jump to heading
Not automatically; configuration in a repositoryβs files is not applied for security reasons. Distribute rules through user or system configuration instead.
How do I see which rule rewrote a URL? Jump to heading
List all url.* entries with --show-origin, then compare their prefixes against the URL. The longest matching prefix is the one applied; git ls-remote --get-url confirms the result.
Can rewrites be scoped to some repositories only? Jump to heading
Put the rules in a file included with includeIf "gitdir:β¦". Repositories under that directory get the rewrite; others do not.
Related Jump to heading
- Git Configuration Management at Scale β the parent topic.
- Pinning and Updating Git Submodules Safely β submodule URLs that rewrites affect.
- Auditing Local Git Configuration Drift β finding stray rules.
- Shipping a Team gitconfig with includeIf β distributing rules.