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
How insteadOf rewrites a URLGit reads a remote URL from configuration, a submodule file or the command line. It finds the longest configured prefix that matches the start of the URL, replaces it with the new base, and connects to the rewritten URL. The stored URL is unchanged.URL readhttps://github.com/org/xMatch prefixlongest winsReplace[email protected]:org/xConnectover SSHthe rewrite happens at use time β€” the configured remote URL never changes

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.

Which rewrite do you need?To push with SSH but fetch anonymously, use pushInsteadOf. To fetch from a mirror but push to the real host, combine insteadOf for the mirror with a pushInsteadOf back to the original. To keep old URLs working after a migration, map the old host to the new one with insteadOf.What do you want to change?push over SSHpushInsteadOffetch stays HTTPSfetch from a mirrorinsteadOf + push backpushes to originhost renamedinsteadOf old β†’ newtemporary rulerewrites apply to every repository using that configuration file

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
insteadOf versus editing remote URLsEditing each remote changes stored configuration in every clone and misses submodules and tools. An insteadOf rule changes behaviour everywhere from one place, including submodules and dependency fetches, but is invisible in remote listings, so it needs documenting.Edit each remoteinsteadOf ruleplaces to changeevery cloneone config filecovers submodules, toolsnoyesvisible in git remote -vyesfetch: no, push: yeseasy to undoper cloneremove one linedocument global rules β€” they surprise people who do not know they exist

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.