Fix: .gitignore Not Working (Files Still Being Tracked)
Part of: Docker, DevOps & Infrastructure
Quick Answer
How to fix .gitignore not working — files still showing in git status after being added to .gitignore, caused by already-tracked files, wrong syntax, nested gitignore rules, and cache issues.
The Error
You add a file or pattern to .gitignore but Git still tracks the file. It shows up in git status as modified, or git add . still stages it. There is no error — .gitignore just silently has no effect.
Common symptoms:
node_modules/is listed in.gitignorebutgit statusstill shows changes inside it.- A
.envfile appears ingit diffeven after adding.envto.gitignore. - A build output folder keeps showing as untracked despite being ignored.
.gitignoreworks for new files but not for files already in the repository..gitignorerules work locally but not after cloning the repository on another machine.
Why This Happens
.gitignore only prevents untracked files from being added to Git. If a file is already tracked (previously committed), .gitignore has no effect on it — Git continues tracking it regardless. This is the single most common cause of “my .gitignore is broken.” A .env file committed once is permanently tracked until you explicitly remove it from the index, even if you delete the file from disk and add it to .gitignore.
The second-most-common cause is silent pattern failure. Unlike shell globs, Git’s ignore patterns have their own rules: leading slashes anchor the pattern, trailing slashes match directories only, and * does not cross directory boundaries. A pattern that looks correct can silently fail to match anything, and Git gives no warning.
The third cause is filesystem and platform differences. Git stores filenames literally, but the filesystem may normalize case (macOS, Windows) or strip metadata (WSL2 mounts). A .gitignore rule that works on Linux can quietly miss files on macOS because the case does not match what Git stored, and the reverse happens when a developer on case-insensitive macOS commits two files differing only in case.
Other causes:
- The file was committed before
.gitignorewas created — it is now tracked and.gitignorecannot retroactively untrack it. - Wrong syntax in the
.gitignorepattern — a typo, missing wildcard, or incorrect path separator. .gitignoreis in the wrong directory — patterns are relative to the directory containing the.gitignorefile.- A negation pattern (
!) re-includes a file that was excluded by a parent pattern. - Global gitignore (
~/.gitignore_global) is not set up, so OS-generated files like.DS_Storeare not ignored. - The pattern matches a directory but not its contents, or vice versa.
Platform and Environment Differences
.gitignore behavior is influenced by the operating system, filesystem, and how the repository is mounted. The same .gitignore does not always behave identically across environments.
Git for Windows and line endings. On Windows, Git’s core.autocrlf defaults to true, which converts line endings on checkout and commit. If .gitignore was authored on Linux with LF endings and edited on Windows, mismatched line endings can sometimes corrupt patterns when the file is opened in editors that misinterpret the encoding. Verify with git check-ignore -v rather than trusting that the file looks right. Also note that Git for Windows treats paths case-insensitively by default to match NTFS behavior, which can mask case-sensitivity bugs that later show up on Linux CI.
macOS APFS case-insensitivity. The default macOS filesystem (APFS, formerly HFS+) is case-insensitive but case-preserving. Git tracks Foo.txt and foo.txt as the same file on macOS but as two different files on Linux. A .gitignore rule like Build/ may or may not match build/ depending on core.ignoreCase (which Git auto-detects per filesystem). The safest practice is to standardize on lowercase paths in .gitignore and rename files to match.
Linux ext4 case sensitivity. Linux filesystems are case-sensitive, so node_modules/ and Node_Modules/ are distinct. If a teammate on macOS committed Node_Modules, your .gitignore rule node_modules/ will not match it on Linux CI. Use git ls-files | grep -i node_modules to audit case variations.
WSL2 and drvfs mounts. When working on a Windows-mounted directory inside WSL2 (under /mnt/c/), the metadata layer (drvfs) is slow and case-sensitivity is configurable per-directory. Performance is dramatically better when the repository lives inside the WSL2 filesystem (/home/user/) rather than on the Windows drive. Inside WSL2, Git treats paths as case-sensitive even if the underlying NTFS is not, which can cause “phantom” tracked files that only one side of the mount sees.
Docker bind mounts. When a project is bind-mounted into a Docker container, .gitignore from the host is visible inside the container, but the container’s user permissions may make git rm --cached fail with permission errors. Run Git commands on the host, not inside the container, unless the container’s user matches the host user’s UID/GID.
Sparse-checkout interaction. With git sparse-checkout enabled, paths excluded from the working tree are not affected by .gitignore because they do not exist locally. If you re-enable a sparse path, previously tracked files reappear, and .gitignore patterns added after the original commit still do not apply to them.
Fix 1: Remove Already-Tracked Files from the Index
If Git is already tracking a file, you must explicitly remove it from the index (the staging area / cache) to stop tracking it:
# Remove a specific file from tracking (keeps the file on disk)
git rm --cached path/to/file.env
# Remove a directory from tracking recursively
git rm --cached -r node_modules/
# Remove all files that now match .gitignore
git rm --cached -r .After removing from the cache, commit the removal:
git add .gitignore
git commit -m "Remove tracked files that should be ignored"From this point forward, Git no longer tracks those files, and .gitignore prevents them from being re-added.
Warning:
git rm --cachedremoves the file from Git’s tracking but keeps it on your disk. It does NOT delete the file. However, when your teammates pull this commit, the file will be deleted from their working directory since it is being removed from the repository. Make sure teammates know to keep a local copy if needed.
Pro Tip: Before running
git rm --cached -r ., make sure your.gitignoreis complete. After clearing the cache and recommitting, only files not matching.gitignorewill be re-added. Rungit statusbefore committing to verify exactly which files will be tracked going forward.
Fix 2: Fix .gitignore Pattern Syntax
A malformed pattern silently fails — no error, just no effect. Common syntax mistakes:
Paths must be relative to the .gitignore file location:
# Wrong — absolute path never matches
/home/user/project/build/
# Correct — relative path
build/Leading slash anchors the pattern to the repo root:
# Matches /logs/ at the root only
/logs/
# Matches logs/ anywhere in the tree
logs/Trailing slash matches directories only:
# Matches the dist directory but NOT a file named dist
dist/
# Matches both files and directories named dist
distWildcards:
# * matches anything except /
*.log # All .log files in any directory
logs/*.log # Only .log files directly in logs/
# ** matches across directories
logs/**/*.log # All .log files anywhere under logs/
**/*.env # All .env files anywhere in the repoTest a pattern before committing:
# Check if a specific file would be ignored
git check-ignore -v path/to/file.env
# Check all files that would be ignored
git check-ignore -v *
# Check why a file is NOT being ignored
git check-ignore -v --no-index .envgit check-ignore -v prints the pattern and the .gitignore file that caused the match — invaluable for debugging.
Fix 3: Verify .gitignore File Location
.gitignore rules apply to the directory it is in and all subdirectories. A .gitignore in a subdirectory only affects that subdirectory:
project/
├── .gitignore ← Rules apply to everything in project/
├── src/
│ ├── .gitignore ← Rules apply only to src/ and below
│ └── components/
└── dist/Common mistake — .gitignore not at the repo root:
# Check if .gitignore is at the root
ls -la .gitignore
# If it's missing, create it
touch .gitignoreCheck which .gitignore is affecting a file:
git check-ignore -v path/to/file
# Output: .gitignore:5:*.log path/to/file.log
# Shows: file path, line number, pattern, matched fileFix 4: Fix Negation Pattern Issues
Negation patterns (!) re-include files excluded by earlier patterns. Order matters:
# Ignore all .env files
*.env
# But keep .env.example — this works
!.env.exampleBroken — trying to negate inside an ignored directory:
# Ignore node_modules
node_modules/
# Try to keep a specific file — THIS DOES NOT WORK
!node_modules/important-file.jsOnce a directory is ignored, Git does not look inside it — negation patterns for files inside an ignored directory have no effect. The fix is to not ignore the directory wholesale:
# Instead of ignoring the whole directory, ignore specific patterns
node_modules/*
!node_modules/important-file.jsOr restructure so the file you want to keep is outside the ignored directory.
Fix 5: Set Up a Global .gitignore
OS-generated and editor-specific files (.DS_Store, Thumbs.db, .idea/, .vscode/) should be ignored globally, not per-project, so every repo on your machine ignores them automatically:
# Create a global gitignore file
touch ~/.gitignore_global
# Configure Git to use it
git config --global core.excludesFile ~/.gitignore_globalAdd OS and editor files to ~/.gitignore_global:
# macOS
.DS_Store
.AppleDouble
.LSOverride
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
# VS Code
.vscode/
*.code-workspace
# JetBrains IDEs
.idea/
*.iml
# Vim
*.swp
*.swo
*~This keeps project .gitignore files clean and focused on project-specific ignores.
Fix 6: Fix .gitignore After It Was Committed With Wrong Contents
If you accidentally committed a .gitignore that was too permissive (and files got committed that should not have been), fix it by updating .gitignore and clearing the cache:
# Step 1: Update .gitignore with the correct rules
echo "node_modules/" >> .gitignore
echo ".env" >> .gitignore
echo "dist/" >> .gitignore
# Step 2: Clear the entire Git index cache
git rm --cached -r .
# Step 3: Re-add everything (only non-ignored files will be added)
git add .
# Step 4: Commit
git commit -m "Fix: update .gitignore and remove incorrectly tracked files"Verify before committing:
git status
# Should NOT show node_modules/, .env, dist/ etc.
git ls-files | grep "node_modules"
# Should return nothinggit ls-files lists every file Git is currently tracking. Use it to audit what is in the index.
Fix 7: Handle .env and Secret Files Already Pushed
If you accidentally committed .env or secrets to the remote repository, removing them from .gitignore and the index is not enough — they exist in the Git history and can still be accessed.
Remove from the current state:
git rm --cached .env
echo ".env" >> .gitignore
git commit -m "Remove .env from tracking"
git pushRemove from history entirely (use with caution):
# Using git filter-repo (recommended over filter-branch)
pip install git-filter-repo
git filter-repo --path .env --invert-paths
# Force push (required after rewriting history)
git push --forceWarning: Rewriting history disrupts all collaborators — they must re-clone or rebase. After removing secrets from history, rotate all exposed credentials immediately. The secrets were public (or accessible to anyone with repo access) from the moment they were pushed.
For credentials accidentally pushed to GitHub, GitHub’s secret scanning may have already detected and notified you. Treat all exposed secrets as compromised regardless.
Still Not Working?
Check for a .git/info/exclude file. This is a per-repository ignore file that is not committed. It works the same as .gitignore but is local only. Check if conflicting rules exist there:
cat .git/info/excludeCheck git config core.excludesFile. If this is set to a different global ignore file, your ~/.gitignore_global may not be the active global ignore:
git config --global core.excludesFileCheck for core.ignoreCase settings. On case-insensitive filesystems (macOS, Windows), Git may or may not respect case in .gitignore patterns depending on the core.ignoreCase setting. If *.Log does not ignore file.log, check this setting:
git config core.ignoreCaseCheck that the file is not staged. If a file is already staged (in the index), .gitignore does not affect it. Run git restore --staged file.env to unstage, then .gitignore applies on the next git add.
Check submodule .gitignore files. A .gitignore inside a submodule applies to that submodule only. Patterns in the parent repo’s .gitignore do not apply to submodule contents. Add the rule inside the submodule itself and commit there, or ignore the entire submodule path from the parent.
Check .gitignore inside a Git worktree. Linked worktrees (git worktree add) share the object database but have separate working directories. A .gitignore change in one worktree affects all worktrees because the file is tracked, but per-repo settings in .git/info/exclude are per-worktree and can diverge silently.
Check that the editor did not save a hidden BOM. Some Windows editors save .gitignore with a UTF-8 byte-order mark. Git treats the BOM as part of the first pattern, so the first rule silently fails. Open the file in hexdump -C .gitignore | head -1 and verify it starts with the pattern, not EF BB BF.
For accidentally committed and pushed sensitive data, also see Fix: git push rejected (non-fast-forward) if you encounter push issues after rewriting history. For environment-variable file loading issues in apps, see Fix: .env variables not loading. For Docker bind-mount permission and ignore issues, see Fix: Docker no space left on device when audits reveal large untracked artifacts. For Linux command-line shell quirks, see Fix: Linux command not found.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Undo git reset --hard and Recover Lost Commits
How to undo git reset --hard and recover lost commits using git reflog — step-by-step recovery for accidentally reset branches, lost work, and dropped stashes.
Fix: git fatal: A branch named 'x' already exists
How to fix 'git fatal: A branch named already exists' when creating or renaming branches — including local conflicts, remote tracking branches, and worktree issues.
Fix: Git Worktree Not Working — Branch Already Checked Out, Prune, Submodules, and Locked Worktrees
How to fix git worktree errors — fatal: 'branch' is already checked out at, worktree prune removing valid trees, detached HEAD on add, submodules not initialized, moving/locking worktrees, and ignoring per-worktree files.
Fix: DVC Not Working — Remote Push Errors, Pipeline DAG Issues, and Git Integration
How to fix DVC errors — dvc push authentication failed, dvc pull file missing, pipeline stage not reproducing, cache out of disk space, dvc add vs dvc stage, conflict with git LFS, and S3/GCS remote setup.