Maintained layer, checked 2026-09-01. The short 2019 command note is preserved verbatim at the end. The maintained guide distinguishes alternate push URLs for one logical repository from two genuinely independent hosts, and treats a two-destination push as replication—not as one atomic transaction.
Table of Contents
Maintained Guide (2026)
Git can send one push to every pushurl configured for a remote. That makes the original recipe functional, but “simultaneously” is misleading: Git contacts destinations separately, and one can succeed before another fails. For two independent hosting providers, separate named remotes are usually the clearer and safer design.
The examples use a branch named main. Substitute the branch you actually intend to publish, and name it explicitly so repository-local push.default or upstream settings do not change the result.
Choose the Right Model
| Situation | Recommended model | Reason |
|---|---|---|
| Two authenticated endpoints that administrators guarantee expose the same logical repository | Multiple pushurl values on one remote |
One remote name can use all configured write endpoints while retaining one fetch URL. |
| Two independent repositories, providers, credentials, or policy sets | Separate remotes such as primary and mirror |
Each destination can be inspected, pushed, retried, and verified independently. |
This distinction comes directly from the official `git remote` documentation: push and fetch URLs for one remote should refer to the same place; if fetching from one place and publishing to another, use separate remotes. The `git push` documentation also confirms that a push affects every defined push URL.
Option A: Multiple Push URLs on One Remote
Assume origin already exists. Set its fetch URL, then add every intended push endpoint—including the primary endpoint:
git remote set-url origin ssh://git@read.example/team/project.git
git remote set-url --add --push origin \
ssh://git@write-a.example/team/project.git
git remote set-url --add --push origin \
ssh://git@write-b.example/team/project.git
Once any pushurl exists, Git uses the configured push-URL list instead of falling back to the fetch URL. Adding only the second endpoint would therefore omit the primary endpoint from future pushes.
Inspect fetch and push configuration separately:
git remote get-url --all origin
git remote get-url --all --push origin
To remove one push URL, remember that the final argument is a regular expression. Anchoring it avoids an accidental broad match:
git remote set-url --delete --push origin \
'^ssh://git@write-b[.]example/team/project[.]git$'
If all custom push URLs are intentionally cleared, Git again falls back to the remote’s fetch URL for pushing:
git config --unset-all remote.origin.pushurl
Do not use git remote rm origin merely to edit a URL: that removes the entire remote and its associated tracking configuration.
Option B: Separate Remotes for Separate Hosts
For independent repositories, give each destination a meaningful name:
git remote add primary ssh://git@code-a.example/team/project.git
git remote add mirror ssh://git@code-b.example/team/project.git
git remote get-url --all --push primary
git remote get-url --all --push mirror
If a named remote already exists, inspect it and use git remote set-url <name> <url> rather than adding a duplicate. Separate names make different credentials, branch rules, error logs, and retries visible instead of hiding them behind origin.
Dry-Run, Push, and Verify
Preview the exact branch against each independent destination:
git push --dry-run --porcelain primary main
git push --dry-run --porcelain mirror main
--dry-run does everything except send the ref updates, while --porcelain provides stable, machine-readable status lines. A dry run is useful but is not a reservation or a proof that every server-side rule will accept the later push.
Push in a deliberate order. With &&, the mirror is skipped if the primary fails:
git push primary main &&
git push mirror main
If the second command fails, the first push is not rolled back. Record the status of each destination and retry only the stale one.
After pushing, compare the local commit ID with the branch advertised by each server:
git rev-parse main
git ls-remote --exit-code --branches primary refs/heads/main
git ls-remote --exit-code --branches mirror refs/heads/main
The first field of both ls-remote lines should equal the value from git rev-parse main. In automation, compare these values exactly and fail the job if either ref is absent or different.
Atomicity and Partial Failure
git push --atomic asks one receiving server to update all refs in that push or none, and fails if that server lacks atomic-push support. It cannot combine two transports or hosts into one transaction. This remains true whether destinations are represented by multiple push URLs or by separate remotes.
A disposable local test with Git 2.43.0 reproduced the practical failure mode:
git push --dry-runvisited both configured local push URLs and updated neither bare repository.- A real push updated both repositories to the same commit.
- After the first URL was left valid and the second changed to a missing repository, the first updated, the second failed, and
git pushexited with status 128.
The exact error and exit status can vary by transport, but the partial-success risk is fundamental. A robust replication job must detect and reconcile divergence; adding --atomic does not create cross-host rollback.
Branches, Tags, and Mirror Mode
Be explicit about the ref set:
git push <remote> mainpublishes onlymain.git push <remote> --allpublishes all local branches, not tags.git push <remote> --tagspublishes all tags in addition to any explicitly listed refspecs.git push <remote> --mirrormirrors all refs underrefs/, force-updates changed refs, and deletes remote refs missing locally.
--mirror is destructive synchronization, not a synonym for “make a backup.” Use it only for a deliberately managed mirror whose complete ref and deletion policy has been reviewed. For normal publication, push named branches and only the tags you intend to distribute.
Credentials and Server-Side Policy
Never embed a password or access token in a remote URL. URLs appear in .git/config, command output, screenshots, and logs. Prefer SSH keys managed by an agent or HTTPS credentials supplied by a secure OS-backed Git credential helper. Authenticate each independent host and grant only the repository permissions that push requires.
The archived commands use git://. Git’s `git daemon` documentation says the protocol has no authentication and enables only the read-oriented upload-pack service by default; anonymous receive-pack is disabled by default. Do not reuse those historical URLs as an authenticated-write template.
Each server evaluates its own protected-branch rules, permissions, non-fast-forward policy, hooks, required signatures, and other hosting controls. Generic Git servers can reject non-fast-forward pushes with `receive.denyNonFastForwards`, while `pre-receive` and `update` hooks can reject proposed ref updates. A client-side force option does not override those server decisions. Align policy on both hosts or expect one destination to reject changes that the other accepts.
Practical Automation Checklist
- Use separate remotes for independent hosts and an explicit branch or refspec.
- Keep secrets out of URLs and logs; test authentication to each destination independently.
- Run per-remote dry runs as a preview, not as a guarantee.
- Push in an intentional order and retain the result for each destination.
- Compare the advertised remote branch IDs with the intended local commit.
- Retry only failed or stale destinations and alert when divergence remains.
This turns “push twice” into an observable replication process without pretending it has distributed-transaction guarantees.
Primary Documentation
- Git: `git remote` URL and push-URL behavior
- Git: `git push`, dry runs, ref selection, mirror mode, and atomic pushes
- Git: `git ls-remote` and `–exit-code`
- Git: credential helpers
- Git: receive configuration, including non-fast-forward rejection
- Git: receive-side hooks
- Git: `git daemon` services and unauthenticated push warning
—
Original 2019 Archive (Verbatim)
The following is the complete visible body from the original WordPress export, published and last modified on 2019-04-24. Only invisible trailing whitespace has been normalized for repository formatting. Its unauthenticated git:// examples and lack of failure handling are preserved as historical provenance, not as the maintained recommendation.
git remote set-url –add –push origin git://original/repo.git
git remote set-url –add –push origin git://another/repo.git
Remove one url from origin
git remote set-url –delete –push origin git://another/repo.git
Remove origin totally
git remote rm origin
git remote set-url origin git://another/repo.git
