Pushing Tags to Remote
One of the most common surprises for Git newcomers is that tags are not automatically pushed when you run git push. This is an intentional design choice: tags are often used as local bookmarks and temporary markers that you never intend to share. You must push tags explicitly. This page covers every push option, how to fetch tags from a remote, and what to be careful about.
Why tags are not pushed automatically
Many developers use tags as local scratchpad markers (e.g.,
git tag pre-merge) that should never reach the remote.Automatic tag pushing could accidentally expose internal milestone names or build identifiers.
Explicit control keeps the remote tag namespace clean — only intentional, well-named tags get published.
This design is consistent with Git's general philosophy: local operations are safe by default; sharing requires explicit action.
Push a single tag
Push one specific tag to the remote
# Push just one tag
git push origin v1.0.0
# Enumerating objects: 1, done.
# Counting objects: 100% (1/1), done.
# Writing objects: 100% (1/1), 175 bytes | 175.00 KiB/s, done.
# Total 1 (delta 0), reused 0 (delta 0), pack-reused 0
# To github.com:alice/myproject.git
# * [new tag] v1.0.0 -> v1.0.0
# Verify it appears on the remote
git ls-remote --tags origin
# 3f8a2c9... refs/tags/v1.0.0
# a4b9c3e... refs/tags/v1.0.0^{} (dereferenced commit, annotated only)Push all local tags at once
Push all tags
git push origin --tags # Enumerating objects: 3, done. # Counting objects: 100% (3/3), done. # Delta compression using up to 8 threads # Compressing objects: 100% (3/3), done. # Writing objects: 100% (3/3), 463 bytes | 463.00 KiB/s, done. # Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 # To github.com:alice/myproject.git # * [new tag] v0.9.0 -> v0.9.0 # * [new tag] v1.0.0 -> v1.0.0 # * [new tag] v1.1.0 -> v1.1.0
Push only annotated tags (recommended)
The --follow-tags flag is the safest choice for most workflows. It pushes commits and annotated tags that are reachable from the commits being pushed, but skips lightweight tags. This means only your "official" annotated release tags go to the remote.
Push commits and annotated tags together
# Push the current branch AND any reachable annotated tags git push --follow-tags origin main # Counting objects: 5, done. # Delta compression ... # To github.com:alice/myproject.git # abc1234..7e4f1a2 main -> main # * [new tag] v1.0.0 -> v1.0.0 # Note: v1.0.0 was annotated; lightweight tags are NOT pushed
Comparing the three push options
Option | Command | What gets pushed | When to use |
|---|---|---|---|
Single tag |
| Exactly one named tag | Publishing a specific release |
All tags |
| Every local tag the remote lacks | Bulk sync; be careful with lightweights |
Follow tags |
| Commits + reachable annotated tags only | Standard release workflow — recommended default |
Push tags to a non-origin remote
Push a tag to a specific remote (not origin)
# List remotes first git remote -v # origin https://github.com/alice/myproject.git (fetch) # upstream https://github.com/org/myproject.git (fetch) # Push a tag to upstream git push upstream v1.0.0 # Push all annotated tags to upstream git push --follow-tags upstream main
Fetch tags from a remote
Tags are fetched automatically during a normal git fetch if the tags are reachable from commits being fetched. However, if tags were added to the remote without associated commit fetching (e.g., someone re-tagged an old commit), you may need to explicitly fetch tags.
Fetch tags from remote
# Fetch all tags from the remote (plus any missing commits they point to) git fetch --tags # Output example: # From github.com:alice/myproject # * [new tag] v1.1.0 -> v1.1.0 # * [new tag] v1.2.0 -> v1.2.0 # Fetch tags from a specific remote git fetch origin --tags # Normal fetch also grabs tags reachable from fetched commits: git fetch origin # (tags pointing to fetched commits are downloaded automatically)
Checking which tags exist on a remote
Inspect remote tags without fetching
# List all tags on the remote
git ls-remote --tags origin
# From github.com:alice/myproject.git
# 3f8a2c9d... refs/tags/v1.0.0
# a4b9c3e2... refs/tags/v1.0.0^{}
# 7e4f1a2c... refs/tags/v1.1.0
# b5c0d4f3... refs/tags/v1.1.0^{}
# The ^{} lines are dereferenced (tag object → commit) for annotated tags
# Check if a specific tag exists on remote
git ls-remote --tags origin v2.0.0
# (empty output = tag does not exist on remote)Warning: pushing tags others have already fetched
Force-push a corrected tag (use only when necessary)
# First: move the local tag to the correct commit git tag -f -a v1.0.0 <correct-commit-sha> -m "Release 1.0.0 (corrected)" # Force-push (requires remote to allow force-push on tags) git push origin --force v1.0.0 # To github.com:alice/myproject.git # + 3f8a2c9...7e4f1a2 v1.0.0 -> v1.0.0 (forced update) # Communicate to team: # "Please run: git tag -d v1.0.0 && git fetch --tags"
Typical release workflow with tag pushing
End-to-end release tagging workflow
# 1. Ensure main is up to date git switch main git pull origin main # 2. Run final checks (tests, linting, build) npm test && npm run build # 3. Create the annotated release tag git tag -a v2.0.0 -m "Release 2.0.0 Changes in this release: - New GraphQL API layer - React 18 upgrade - Removed deprecated endpoints - Performance: 50% reduction in bundle size" # 4. Push the commit and the tag together git push --follow-tags origin main # (or: git push origin main && git push origin v2.0.0) # 5. Verify on remote git ls-remote --tags origin v2.0.0 # a4b9c3e2... refs/tags/v2.0.0