BuildWithMatija
  1. Home
  2. Blog
  3. Tools
  4. Push to Multiple Git Remotes — Mirror GitHub & Bitbucket

Push to Multiple Git Remotes — Mirror GitHub & Bitbucket

Step-by-step guide to configure multiple git remotes, route SSH keys per host, and mirror pushes reliably between…

13th July 2026·Updated on:28th July 2026··
Tools
Push to Multiple Git Remotes — Mirror GitHub & Bitbucket

📚 Get Practical Development Guides

Join developers getting comprehensive guides, code examples, optimization tips, and time-saving prompts to accelerate their development workflow.

No spam. Unsubscribe anytime.

📄View markdown version
0

Frequently Asked Questions

About the author

Matija Žiberna

Matija Žiberna

Full-stack developer, co-founder

AboutResume

Self-taught full-stack developer sharing lessons from building software and startups.

I'm Matija Žiberna, a self-taught full-stack developer and co-founder passionate about building products, writing clean code, and figuring out how to turn ideas into businesses. I write about web development with Next.js, lessons from entrepreneurship, and the journey of learning by doing. My goal is to provide value through code—whether it's through tools, content, or real-world software.

Contents

  • Why One Repo Can Talk to Several Hosts
  • Checking What You Already Have
  • Adding a Second (or Third) Remote
  • Routing SSH Keys per Host
  • Pushing to Only One Remote
  • Setting the default upstream
  • Feature branches, tags, and deletions
  • Fetching and Comparing Remotes
  • Pushing to Both Remotes at Once
  • The `main` vs `master` Trap
  • First Sync When the Remote Already Has Commits
  • My Daily Workflow for This Setup
  • A Note on CI
  • FAQ
  • Wrapping Up
On this page:
  • Why One Repo Can Talk to Several Hosts
  • Checking What You Already Have
  • Adding a Second (or Third) Remote
  • Routing SSH Keys per Host
  • Pushing to Only One Remote
Build with Matija logo

Build with Matija

Senior-led B2B websites, applications, content systems, and digital infrastructure. Business-first, full-stack, AI-assisted, no handoffs.

Services

  • B2B Website Development
  • Headless CMS
  • AI Integration & Implementation
  • Next.js + Payload Advisory
  • Digital Platform Discovery
  • Website Growth Review

Resources

  • Case Studies
  • How I Work
  • Blog
  • Topics
  • CMS Hub
  • E-commerce Hub
  • B2B Website Strategy
  • Dashboard

Headless CMS

  • CMS Architecture Review
  • Payload CMS Developer
  • CMS Migration
  • Multi-Tenant CMS
  • Payload vs Sanity
  • Payload vs WordPress
  • Payload vs Contentful

Discuss the system

Planning a rebuild, migration, application, or workflow change? Start with the business problem and the system behind it.

Book a discovery callContact me →
© 2026Build with Matija•All rights reserved•Privacy Policy•Terms of Service
BuildWithMatija
Get In Touch

If a client requires their code on Bitbucket while your team collaborates on GitHub, you can run both from a single local repository. This guide walks through adding multiple remotes, pushing to each one selectively, and keeping one host as the canonical source for pull requests while the other stays a mirror.

I set this up recently on a client project where the collaboration and review process happens on GitHub, but the client's internal tooling requires a mirror on Bitbucket. Rather than maintaining two separate clones or relying on a manual copy-paste workflow, I configured one local repo with two remotes and a couple of scripted push commands. This guide documents that exact setup, including the SSH key routing, the branch-naming quirks between hosts, and the commands I actually run day to day.

Why One Repo Can Talk to Several Hosts

Git itself has no concept of a "home" host. Your local .git directory holds the full history, and GitHub, Bitbucket, and GitLab are simply named bookmarks pointing at other copies of that same history. A remote is just a label attached to a URL.

code
Local repo (main branch)
      |
      ├── origin     → GitHub
      ├── bitbucket  → Bitbucket
      └── gitlab     → GitLab (optional)

Fetching pulls commits from a remote into your machine. Pushing sends commits from your machine to a remote. The remotes never talk to each other directly, so if you push to GitHub and forget Bitbucket, Bitbucket simply falls behind until you push there too.

Checking What You Already Have

Before adding anything, see what's configured:

bash
git remote -v
git branch -vv
git status

A repo with two remotes looks like this:

text
origin     https://github.com/org/repo.git (fetch)
origin     https://github.com/org/repo.git (push)
bitbucket  git@bitbucket.org:workspace/repo.git (fetch)
bitbucket  git@bitbucket.org:workspace/repo.git (push)

* main  abc1234 [origin/main] latest commit message

The [origin/main] marker tells you which remote branch your local main tracks. That's the target for a plain git pull or git push with no remote name specified.

Adding a Second (or Third) Remote

Create the empty repository on each host first, then point your local project at it.

bash
# HTTPS
git remote add origin https://github.com/ORG/REPO.git
git remote add bitbucket https://bitbucket.org/WORKSPACE/REPO.git

# SSH (recommended for daily use)
git remote add origin git@github.com:ORG/REPO.git
git remote add bitbucket git@bitbucket.org:WORKSPACE/REPO.git

To change a URL later, or rename or remove a remote:

bash
git remote set-url bitbucket git@bitbucket.org:WORKSPACE/REPO.git
git remote rename origin github
git remote remove gitlab

The convention I use: GitHub stays origin because it's canonical for pull requests and daily pushes, and every other host gets named after itself (bitbucket, gitlab).

Routing SSH Keys per Host

If you use separate accounts across hosts, or you just want clean revocation, generate a dedicated key per host and route it through ~/.ssh/config:

sshconfig
Host github.com
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519_github
  IdentitiesOnly yes

Host bitbucket.org
  HostName bitbucket.org
  User git
  IdentityFile ~/.ssh/id_ed25519_bitbucket
  IdentitiesOnly yes

Generate a new key and add the public half to the host's UI:

bash
ssh-keygen -t ed25519 -C "bitbucket-work" -f ~/.ssh/id_ed25519_bitbucket

Confirm each connection works before you push anything:

bash
ssh -T git@github.com
ssh -T git@bitbucket.org

If you see a host key warning, register the host once:

bash
ssh-keyscan -t ed25519,rsa bitbucket.org >> ~/.ssh/known_hosts

Pushing to Only One Remote

This is the workflow you'll use most. Always name the remote explicitly so there's no ambiguity about where a branch, tag, or deletion is headed.

bash
git push origin main       # GitHub only
git push bitbucket main    # Bitbucket only

Setting the default upstream

git push -u origin main sets origin as the default target for a plain git push going forward. Run this once for GitHub and skip -u for the mirror, so an unqualified git push never accidentally lands on the wrong host:

bash
git push -u origin main
git push bitbucket main

If the upstream ever gets pointed at the wrong remote, restore it directly:

bash
git branch -u origin/main main

Feature branches, tags, and deletions

Each of these needs the remote spelled out too, since Bitbucket or GitLab won't see anything you don't explicitly push:

bash
git push -u origin feature/i18n     # PR branch on GitHub
git push bitbucket feature/i18n     # same branch mirrored to Bitbucket

git push origin v1.2.0              # a single tag
git push origin --delete old-branch # remove a branch on one remote only

Fetching and Comparing Remotes

bash
git fetch origin
git fetch bitbucket
git fetch --all

To see what's different between two remotes without merging anything:

bash
git fetch --all
git log --oneline origin/main..bitbucket/main
git log --oneline bitbucket/main..origin/main

Pushing to Both Remotes at Once

For a repo you mirror on every push, there are three ways to automate it.

ApproachHow it worksBest for
Dual push URL on one remotegit remote set-url --add --push origin <second-url> makes git push origin hit two hosts at onceRepos where every push should always mirror, no exceptions
Shell aliasA one-line alias runs both git push commands in sequenceDay-to-day use where you want visibility into each push
Custom pushInsteadOf rewriteGit rewrites the push URL at the config levelRare; explicit named remotes are usually clearer

The alias approach is what I use, since it keeps each push visible in the terminal output:

bash
# ~/.zshrc
alias gpush-all='git push origin HEAD && git push bitbucket HEAD'
alias gpush-gh='git push origin HEAD'
alias gpush-bb='git push bitbucket HEAD'

The main vs master Trap

Hosts don't agree on default branch names, and this catches people off guard the first time they mirror an existing repo.

HostCommon default
GitHubmain
Bitbuckethistorically master
GitLabmain (configurable)

Your local branch might be main while an old, empty master branch is still sitting on Bitbucket from when the repo was first created. The Bitbucket UI path /src/master/ will show that stale branch, not the one you're actually pushing to.

Check what a remote actually has:

bash
git ls-remote bitbucket

To make main the canonical branch on a host that still defaults to master, either force master to match main's content (only if the old master history is disposable):

bash
git push bitbucket main:master --force

Or push main on its own and change the host's default branch setting through its UI:

  • GitHub: Settings → General → Default branch
  • Bitbucket: Repository settings → Repository details → Advanced → Main branch
  • GitLab: Settings → Repository → Default branch

Git can't change a host's default branch over the git protocol itself. That setting lives in the host's own configuration.

First Sync When the Remote Already Has Commits

If the host auto-created a README, license, or .gitignore, your local history and the remote's history may have no common ancestor. Check before pushing:

bash
git fetch bitbucket
git log --oneline --graph --all --decorate | head -40

If the remote's content is disposable, force your history onto it:

bash
git push bitbucket main --force

If you need to keep what's already there, merge the histories explicitly:

bash
git pull bitbucket main --allow-unrelated-histories
git push bitbucket main

Avoid --force on any branch other people rely on unless the team has agreed on it first.

My Daily Workflow for This Setup

  1. Develop and commit locally, same as any single-remote repo.
  2. Open pull requests and do code review on GitHub (origin), which stays canonical.
  3. After a merge, or whenever the client needs the mirror current, sync Bitbucket:
bash
git checkout main
git pull origin main
git push origin main
git push bitbucket main
  1. Confirm the upstream is still GitHub:
bash
git branch -u origin/main main

A Note on CI

Pushing the same commits to two hosts does not duplicate your CI pipelines. GitHub Actions and Bitbucket Pipelines are separate systems, and each one only runs if it's configured on that specific host. If Vercel or another deploy target is bound to GitHub, mirroring to Bitbucket won't trigger a second deployment unless you set that up independently.

FAQ

Does pushing to origin automatically update bitbucket? No. Remotes never sync with each other. Each git push only reaches the remote you name, so a mirror only stays current if you push to it as a separate step.

What happens if I run git push with no remote name? It goes to whichever remote your current branch tracks as its upstream, which you can check with git branch -vv. Set this explicitly with git push -u <remote> <branch> so there's no guessing.

Can I set up automatic mirroring so I never have to push twice? Yes, using a dual push URL on one remote name (git remote set-url --add --push origin <second-url>) or a CI job that mirrors on merge. A shell alias is the simplest version if you're doing this manually.

Why does Bitbucket show master when I've been working in main the whole time? Bitbucket creates a default branch when the repository is first made, and that's often still master unless changed. Check git ls-remote bitbucket to see exactly which branches exist there, then update the host's default branch setting in its UI.

Is it safe to force-push a mirror branch? Force-pushing is fine on a mirror that no one else pulls from directly, since you're just overwriting a copy. Treat any branch other people build on as off-limits for force-pushing regardless of which remote it lives on.

Wrapping Up

A single local repository can push to as many hosts as a project needs, and the pattern stays the same regardless of how many remotes you add: name each one explicitly, decide which host is canonical for reviews, and push to the others as deliberate mirroring steps rather than assuming they'll stay in sync on their own. Once the remotes and SSH routing are set up, the daily commands are just git push origin main followed by git push bitbucket main.

Let me know in the comments if you have questions, and subscribe for more practical development guides.

Thanks, Matija