Create a Private Fork of a Public Repository
The Problem
GitHub doesn't allow you to fork a public repository as private. This is by design, but sometimes you need a private copy of a public repo where you can:
- Make experimental changes without cluttering the public fork network
- Keep work-in-progress code private
- Maintain your own version while still pulling upstream updates
- Preserve access even if the original repo changes visibility
This guide shows you how to create a proper private fork that maintains an upstream connection.
Method 1: Bare Clone (Recommended for Upstream Tracking)
This method creates a true mirror of the repository and lets you pull changes from the original repo later.
Step 1: Create a Bare Clone
Clone the repository without a working directory (temporary, will be removed):
git clone --bare git@github.com:original-owner/repo-name.git
Step 2: Create Your Private Repository
Go to GitHub and create a new private repository with the same name. Don't initialise it with any files.
Student tip: If you can't create private repos, get the GitHub Student Developer Pack for unlimited private repositories.
Step 3: Mirror Push to Your Private Repo
Replace <your_username> with your actual GitHub username:
cd repo-name.git
git push --mirror git@github.com:<your_username>/repo-name.git
Step 4: Clean Up the Bare Clone
Remove the temporary repository:
cd ..
rm -rf repo-name.git
Step 5: Clone Your Private Repository
Now clone your private repo to work with it:
cd ~/code
git clone git@github.com:<your_username>/repo-name.git
Step 6: Add Upstream Remote (Optional but Recommended)
This lets you pull future changes from the original repository:
cd repo-name
git remote add upstream git@github.com:original-owner/repo-name.git
git remote set-url --push upstream DISABLE
The DISABLE prevents accidental pushes to the upstream repo.
Step 7: Verify Your Remotes
Check your remote configuration:
git remote -v
You should see:
origin git@github.com:<your_username>/repo-name.git (fetch)
origin git@github.com:<your_username>/repo-name.git (push)
upstream git@github.com:original-owner/repo-name.git (fetch)
upstream DISABLE (push)
Pulling Upstream Changes
When you want to pull changes from the original repository:
git fetch upstream
git rebase upstream/main
Or if the default branch is master:
git fetch upstream
git rebase upstream/master
Resolve any conflicts if they arise, then push to your private repo:
git push origin main
Method 2: GitHub Import (Simpler but No Upstream Tracking)
If you don't need to pull upstream changes, use GitHub's import feature:
- Go to https://github.com/new/import
- Enter the URL of the public repository
- Choose a name for your repository
- Select Private visibility
- Click Begin import
Pros: Simple, one-step process
Cons: No automatic upstream tracking (you'll need to manually add it later if needed)
To add upstream tracking after import:
git remote add upstream git@github.com:original-owner/repo-name.git
git remote set-url --push upstream DISABLE
Method 3: Leave Fork Network (Web Interface)
If you've already forked the repository publicly, you can convert it to private by leaving the fork network:
- Go to your fork's ⚙️ Settings
- Scroll to Danger Zone
- Click Leave Fork Network (confirm)
- Refresh the page
- Go back to Danger Zone
- Click Change visibility > Change to Private
⚠️ Important: This method works, but you lose the official fork relationship. You can still add the original repo as an upstream remote manually (see Method 1, Step 6).
Which Method Should You Use?
| Method | Best For | Upstream Tracking | Difficulty |
|---|---|---|---|
| Bare Clone | Full control, upstream tracking | ✅ Yes | Medium |
| GitHub Import | Quick private copy | ⚠️ Manual setup | Easy |
| Leave Fork Network | Existing public fork | ⚠️ Manual setup | Easy |
Common Issues
Default Branch Not Set
Sometimes after a bare clone, GitHub loses track of the default branch. Fix this in your repository settings:
- Go to ⚠️ Settings > Branches
- Set the default branch (usually
mainormaster)
Push Conflicts
If you get push conflicts when pulling upstream changes:
git fetch upstream
git rebase upstream/main
# Resolve conflicts in your editor
git add .
git rebase --continue
git push origin main --force-with-lease
The --force-with-lease is safer than --force as it won't overwrite changes pushed by others.
Important Notes
-
Fork visibility: According to GitHub's documentation, if a public repository becomes private, its public forks remain public in their own network. Your private clone created with these methods is independent.
-
Not a "fork" in GitHub's eyes: These methods create a new repository that's not officially linked as a fork. This is intentional and gives you more control.
-
Backup your work: If you're working on something important, push to your private repo frequently. Don't rely on the upstream repository staying accessible.
Related Resources
Credit: Based on this GitHub Gist by @0xjac, with additional methods and clarifications from community contributors.