---
title: "Push to Multiple Git Remotes — Mirror GitHub & Bitbucket"
slug: "push-to-multiple-git-remotes-mirror-github-bitbucket"
published: "2026-07-13"
updated: "2026-07-28"
validated: "2026-07-28"
categories:
  - "Tools"
tags:
  - "git multiple remotes"
  - "push to multiple remotes"
  - "mirror GitHub to Bitbucket"
  - "route SSH keys per host"
  - "git remote add"
  - "dual push URL"
  - "gpush-all alias"
  - "git branch upstream"
  - "git fetch --all"
  - "force push mirror"
  - "GitHub Actions"
  - "Bitbucket Pipelines"
llm-intent: "reference"
audience-level: "intermediate"
framework-versions:
  - "git"
  - "github"
  - "bitbucket"
  - "gitlab"
  - "ssh"
status: "stable"
llm-purpose: "git multiple remotes — Configure remotes, SSH routing, and mirror pushes to GitHub and Bitbucket with concise commands and tips. Follow this practical…"
llm-prereqs:
  - "Access to Git"
  - "Access to GitHub"
  - "Access to Bitbucket"
  - "Access to GitLab"
  - "Access to SSH"
llm-outputs:
  - "SSH credentials configured for secure migrations"
---

**Summary Triples**
- (Push to Multiple Git Remotes — Mirror GitHub & Bitbucket, focuses-on, git multiple remotes — Configure remotes, SSH routing, and mirror pushes to GitHub and Bitbucket with concise commands and tips. Follow this practical…)
- (Push to Multiple Git Remotes — Mirror GitHub & Bitbucket, category, general)

### {GOAL}
git multiple remotes — Configure remotes, SSH routing, and mirror pushes to GitHub and Bitbucket with concise commands and tips. Follow this practical…

### {PREREQS}
- Access to Git
- Access to GitHub
- Access to Bitbucket
- Access to GitLab
- Access to SSH

### {STEPS}
1. Inspect current remotes and branches
2. Add the second remote
3. Route SSH keys per host
4. Set default upstream for canonical host
5. Push selectively to each remote
6. Mirror pushes automatically or by alias
7. Resolve first-sync conflicts
8. Validate daily workflow and CI behavior

<!-- llm:goal="git multiple remotes — Configure remotes, SSH routing, and mirror pushes to GitHub and Bitbucket with concise commands and tips. Follow this practical…" -->
<!-- llm:prereq="Access to Git" -->
<!-- llm:prereq="Access to GitHub" -->
<!-- llm:prereq="Access to Bitbucket" -->
<!-- llm:prereq="Access to GitLab" -->
<!-- llm:prereq="Access to SSH" -->
<!-- llm:output="SSH credentials configured for secure migrations" -->

# Push to Multiple Git Remotes — Mirror GitHub & Bitbucket
> git multiple remotes — Configure remotes, SSH routing, and mirror pushes to GitHub and Bitbucket with concise commands and tips. Follow this practical…
Matija Žiberna · 2026-07-13

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.

```
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.

| Approach | How it works | Best for |
|---|---|---|
| Dual push URL on one remote | `git remote set-url --add --push origin <second-url>` makes `git push origin` hit two hosts at once | Repos where every push should always mirror, no exceptions |
| Shell alias | A one-line alias runs both `git push` commands in sequence | Day-to-day use where you want visibility into each push |
| Custom `pushInsteadOf` rewrite | Git rewrites the push URL at the config level | Rare; 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.

| Host | Common default |
|---|---|
| GitHub | `main` |
| Bitbucket | historically `master` |
| GitLab | `main` (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
```

4. 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

## LLM Response Snippet
```json
{
  "goal": "git multiple remotes — Configure remotes, SSH routing, and mirror pushes to GitHub and Bitbucket with concise commands and tips. Follow this practical…",
  "responses": [
    {
      "question": "What does the article \"Push to Multiple Git Remotes — Mirror GitHub & Bitbucket\" cover?",
      "answer": "git multiple remotes — Configure remotes, SSH routing, and mirror pushes to GitHub and Bitbucket with concise commands and tips. Follow this practical…"
    }
  ]
}
```