
깃허브에 코드를 올리려는데 ! [rejected] main -> main (non-fast-forward) 또는 failed to push some refs 메시지로 푸시가 막히는 경우가 있습니다. 이 오류는 저장소가 고장 난 것이 아니라, 원격 브랜치에 내 로컬에 없는 커밋이 있어서 그대로 덮어쓰면 히스토리가 사라지기 때문에 Git이 일부러 막은 것입니다. 바로 강제 푸시를 하면 동료의 커밋이 날아갈 수 있으므로 아래 순서대로 원인을 먼저 구분하세요. (Git 공식 문서 git-push 2.55.0 기준, 2026년 9월 18일 확인)
빠른 결론 3줄
- 대부분은 다른 사람이 먼저 푸시한 경우이므로
git fetch후git pull --rebase또는git pull --no-rebase로 합친 뒤 다시 푸시하면 해결됩니다. - 내가
commit --amend나rebase로 이미 올린 커밋을 고쳐 쓴 경우에만 강제 푸시가 필요하고, 이때도--force대신--force-with-lease를 씁니다. - 보호된 브랜치(예: GitHub의 protected branch)에서는 강제 푸시가 기본 차단되므로, 새 브랜치를 만들어 풀 리퀘스트로 올려야 합니다.
원인별 구분표
| 상황 | 화면에 보이는 신호 | 해야 할 일 |
|---|---|---|
| 동료가 같은 브랜치에 먼저 푸시 | (non-fast-forward), fetch first |
fetch 후 merge 또는 rebase로 합치고 재푸시 |
| 내가 amend·rebase로 히스토리 변경 | 로컬 커밋 수는 그대로인데 거부됨 | --force-with-lease로 교체 푸시 |
| 브랜치가 보호 설정됨 | GH006: Protected branch update failed 계열 |
별도 브랜치 + 풀 리퀘스트 |
| 업스트림 설정이 없거나 다른 브랜치를 가리킴 | 푸시 대상 브랜치 이름이 예상과 다름 | git push -u origin 브랜치명으로 재설정 |
작업 전 체크리스트
- 현재 브랜치 이름 확인:
git branch --show-current - 원격 주소 확인:
git remote -v - 커밋하지 않은 변경은 먼저 커밋하거나
git stash로 대피 - 중요한 작업이면
git branch backup-작업명으로 백업
단계별 해결 방법
- 오류 전문을 읽습니다. GitHub 공식 문서 예시처럼
! [rejected] main -> main (non-fast-forward)와 "To prevent you from losing history, non-fast-forward updates were rejected"가 함께 나오면, 원격에 내가 받지 않은 커밋이 있다는 뜻입니다. - 원격 상태를 받아옵니다. 터미널에서
git fetch origin을 실행합니다. fetch는 내 작업 트리를 바꾸지 않으므로 안전합니다. - 차이를 확인합니다.
git log --oneline --left-right HEAD...origin/main으로 내 커밋과 원격 커밋을 비교합니다. 원격 쪽에만 커밋이 있으면 1번 상황입니다. - 합치기 방식을 고릅니다. 병합 커밋을 남기려면
git pull --no-rebase origin main, 히스토리를 한 줄로 유지하려면git pull --rebase origin main을 사용합니다. Git 공식 문서 기준git pull의 기본 동작은--ff-only이며, 로컬이 갈라져 있으면 그냥 실패합니다. - 충돌이 나면 해결합니다. 충돌 파일을 고친 뒤
git add 파일명, rebase 중이면git rebase --continue, merge 중이면git commit으로 마무리합니다. - 중간에 그만두고 싶으면 되돌립니다. 공식 문서 안내대로
git merge --abort또는git rebase --abort를 실행하면 시작 전 상태로 안전하게 돌아갑니다. - 다시 푸시합니다.
git push origin main을 실행합니다. 원격 브랜치가 내 커밋의 조상이 되었으므로 이번에는 fast-forward로 통과합니다. - 내가 히스토리를 고쳐 쓴 경우에만 강제 푸시합니다.
git push --force-with-lease origin 브랜치명을 사용합니다. 이 옵션은 원격 ref가 내가 알고 있는 값과 같을 때만 갱신하므로, 그 사이 다른 사람이 올린 커밋이 있으면 푸시가 실패해 사고를 막아 줍니다. 더 엄격하게 확인하려면--force-with-lease와 함께--force-if-includes를 같이 지정합니다. - 업스트림이 없으면 지정합니다.
git push -u origin 브랜치명으로 추적 브랜치를 설정하면 이후에는git push만으로 같은 대상에 올라갑니다.
주의사항
git push --force는 공식 문서에서 "히스토리를 잃어도 된다고 확신할 때만 쓰는 방법"으로 설명됩니다. 공용 브랜치에서는 사용하지 않는 것이 원칙입니다.--force-with-lease도 만능은 아닙니다. 에디터나 다른 프로그램이 백그라운드에서git fetch를 계속 돌리고 있으면 보호 효과가 쉽게 무너진다고 공식 문서가 명시합니다.- GitHub는 보호된 브랜치에서 강제 푸시를 기본으로 차단합니다. 관리자가 허용 설정을 켜지 않았다면 강제 푸시 자체가 불가능합니다.
- rebase는 커밋 해시가 바뀌므로, 같은 브랜치로 작업 중인 동료에게 미리 알려야 합니다.
그래도 해결되지 않을 때 추가 점검
git remote -v와git branch -vv로 푸시 대상과 추적 브랜치를 확인합니다.git reflog로 로컬 브랜치가 옛날 커밋을 가리키는지 확인합니다.- 거부 메시지에 권한·토큰 문구가 있으면 non-fast-forward 문제가 아니라 인증 문제입니다.
자주 묻는 질문
Q1. pull과 fetch는 무엇이 다른가요?
fetch는 원격 내용을 받아오기만 하고 내 브랜치는 그대로 둡니다. pull은 fetch 후 merge 또는 rebase까지 자동 수행합니다.
Q2. merge와 rebase 중 무엇을 골라야 하나요?
협업 규칙에 따릅니다. 병합 이력을 남기는 팀이면 merge, 한 줄 히스토리를 선호하는 팀이면 rebase입니다. 공식 문서는 두 방법 모두 fast-forward 조건을 만족시켜 푸시를 통과시킨다고 설명합니다.
Q3. 강제 푸시로 동료 커밋을 지웠는데 복구할 수 있나요?
지워진 커밋을 가진 사람의 로컬에 git reflog 기록이 남아 있으면 해당 해시로 브랜치를 다시 만들어 복구할 수 있습니다. 다만 항상 복구되는 것은 아닙니다.
함께 보면 좋은 글
- Git merge conflict 해결 방법: <<<<<<< HEAD 표시 처리 순서
- Git push 인증 실패 해결: GitHub 비밀번호 인증 오류와 PAT 설정
- Git detected dubious ownership 오류 해결: safe.directory 확인·추가·삭제 순서
- GitHub Push Protection 푸시 차단 해결: 비밀키 제거·커밋 수정·재발급 순서
공식 출처
- Git 공식 문서 git-push(2.55.0 기준) - https://git-scm.com/docs/git-push
- Git 공식 문서 git-pull - https://git-scm.com/docs/git-pull
- GitHub Docs, 비빠른 선행 오류 처리 - https://docs.github.com/ko/get-started/using-git/dealing-with-non-fast-forward-errors
- GitHub Docs, 보호된 분기 정보 - https://docs.github.com/ko/repositories/...about-protected-branches
※ 이 글은 2026년 9월 18일 기준 공식 문서를 확인해 정리했습니다. Git 버전과 저장소 정책에 따라 메시지 문구와 동작이 달라질 수 있습니다.
댓글