To stop Git tracking a file but keep it sitting on your disk, run git rm --cached <file>. The --cached flag removes the file from the index (Git's list of tracked files) without touching the copy in your working directory. Commit that change, add the file to .gitignore, and Git forgets about it while the file stays exactly where it is.
That single flag is the whole trick. Plain git rm deletes the file from both Git and your disk; git rm --cached deletes it from Git only. Almost everyone who lands here did the same thing: committed a file they did not mean to (a .env, a node_modules folder, a build artifact, an IDE config), noticed afterwards, and wants it gone from the repo without losing the actual file.
The one command
Say you accidentally committed a config.local.json that has a password in it. The file is on disk and it is tracked by Git. You want Git to forget it, but you still need the file to run your app locally.
git rm --cached config.local.jsonGit responds:
rm 'config.local.json'The word "rm" looks alarming, but the file is still right there in your folder. All that happened is Git staged a deletion of the tracked copy. Confirm it:
git statusOn branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
deleted: config.local.json
Untracked files:
(use "git add <file>..." to include in what will be committed)
config.local.jsonNotice the file shows up twice: as a staged deletion (Git is about to stop tracking it) and as an untracked file (the copy on disk that Git no longer knows about). That is exactly what you want. Commit it:
git commit -m "Stop tracking config.local.json"From this commit forward, Git ignores changes to that file. But it is not done yet, because the file is now untracked, which means the next person who runs git add . will commit it straight back in. You need .gitignore to make the removal stick.
Add it to .gitignore so it never comes back
Open (or create) the .gitignore file at the root of your repository and add the path:
echo "config.local.json" >> .gitignoreThen commit the .gitignore change:
git add .gitignore
git commit -m "Ignore config.local.json"Now the file is doubly handled: removed from history going forward, and listed in .gitignore so neither you nor anyone else can stage it again by accident. The whole mess usually starts when a repo gets going without an ignore file, so it is worth knowing how to set Git up correctly on a new project from the start and avoid the cleanup entirely. For the full set of patterns (wildcards, directories, negation), I wrote a separate walkthrough of how .gitignore patterns actually match. The short version is that .gitignore only affects untracked files, which is why you have to git rm --cached first. Adding a tracked file to .gitignore does nothing on its own; Git keeps tracking what it already tracks.
Removing a whole folder
The classic offender is node_modules or a vendor directory committed before anyone added a .gitignore, which is exactly what happens when you start adding Git to a project that already has files in it and skip the ignore file. Same idea, but you need -r for recursive:
git rm -r --cached node_modulesrm 'node_modules/.bin/acorn'
rm 'node_modules/acorn/package.json'
...It prints a line per file, which can be thousands of lines for node_modules. That is fine. The folder stays on disk untouched. Add it to .gitignore and commit:
echo "node_modules/" >> .gitignore
git add .gitignore
git commit -m "Stop tracking node_modules"rm vs rm --cached
The difference is one flag, and getting it wrong deletes the file off your disk. Here is the full picture:
| Command | Removes from Git index | Deletes file on disk | When to use it |
|---|---|---|---|
git rm --cached file | Yes | No | Stop tracking, keep the file locally |
git rm file | Yes | Yes | Delete the file from the repo and your disk |
rm file (plain shell) | No (until you commit) | Yes | Delete on disk; Git records a deletion you still commit |
If you only ever remember one thing: --cached means "keep my copy." Everything without it touches your disk.
I already deleted the file by mistake
If you ran git rm without --cached and the file vanished from your disk, do not panic as long as you have not committed yet. The file still exists in the last commit, so you can bring it back:
git restore --staged config.local.json
git restore config.local.jsonThe first line unstages the deletion; the second pulls the file back out of the last commit onto your disk. On Git before 2.23, the equivalent is git checkout HEAD -- config.local.json. If you had already committed the deletion, recover it from the previous commit:
git checkout HEAD~1 -- config.local.jsonThis is one of those moments where the reflog quietly saves your week, because nothing in Git is gone until it is garbage-collected, which takes weeks. If you want the broader picture of unstaging and rolling back, see discarding local changes and undoing the last commit.
A critical warning: the file is still in your history
git rm --cached stops tracking a file from this commit forward. It does not scrub the file out of past commits. If you committed a secret (an API key, a database password, a private key) and then ran git rm --cached, the secret is still sitting in every earlier commit, and anyone who clones the repo or reads the history can pull it out. Pushing the "removal" commit does not help; the old blob is still there.
If what you removed was sensitive, treat it as compromised: rotate the secret first, then rewrite history to purge it. I cover the actual rewrite (with git filter-repo) in how to remove a secret from Git history. And if the repo is already public, assume the secret was scraped; exposed .git directories are a real attack vector that bots scan for constantly.
For ordinary, non-sensitive files (a build artifact, an editor config, a log), the forward-only behaviour is completely fine and you can ignore this section.
Why this trips people up
The confusion comes from how Git thinks about a file. There are three places a file can live: your working directory (the actual file on disk), the index or staging area, and the committed history. git rm --cached operates only on the index. It tells Git "remove this from the list of things you track," and because it leaves the working directory alone, your file survives.
People expect git rm to behave like a Git-aware version of the shell's rm, and it mostly does: by default it removes the file everywhere. The --cached flag is the escape hatch that says "only the Git side." Once you internalise that the index is a separate layer from your disk, the whole thing stops being mysterious. The same mental model explains git stash, git reset versus git revert, and most of the rest of the Git toolbox.
New to all of this? Start with the Git for beginners guide, which walks through the working directory, the index, and commits from scratch before you hit edge cases like this one.
Sources
Authoritative references this article was fact-checked against.
- git-rm: official Git documentationgit-scm.com
- gitignore: official Git documentationgit-scm.com
- Pro Git: Recording Changes to the Repositorygit-scm.com





