npm install -g 권한 오류 EACCES: permission denied 해결 (우분투·맥, sudo 없이)

이 글의 핵심

npm install -g 명령이 npm error code EACCES와 함께 permission denied, mkdir '/usr/local/lib/node_modules/...'로 실패하는 것은 npm이 전역 패키지를 root 소유 폴더에 쓰려고 하기 때문입니다. sudo로 밀어붙이면 당장은 설치되지만 캐시 폴더가 root 소유로 바뀌고 설치 스크립트가 관리자 권한으로 실행되는 부작용이 남습니다. 이 글은 npm 공식 문서가 권하는 두 가지 해결책(nvm으로 Node 다시 설치, 전역 설치 경로를 홈 폴더로 변경)과, 이미 sudo를 써서 꼬인 캐시를 되돌리는 방법, 전역 설치 자체를 줄이는 npx, 윈도우의 EPERM 오류를 순서대로 설명합니다.

오류 메시지와 이 글의 기준 환경

우분투나 맥에서 npm install -g typescript처럼 전역 설치를 하면 다음과 같은 오류가 나는 경우가 있습니다. npm 9 이하에서는 각 줄이 npm ERR!로 시작하는 아래 형태입니다.

npm ERR! code EACCES
npm ERR! syscall mkdir
npm ERR! path /usr/local/lib/node_modules/typescript
npm ERR! errno -13
npm ERR! Error: EACCES: permission denied, mkdir '/usr/local/lib/node_modules/typescript'
npm ERR!  [Error: EACCES: permission denied, mkdir '/usr/local/lib/node_modules/typescript'] {
npm ERR!   errno: -13,
npm ERR!   code: 'EACCES',
npm ERR!   syscall: 'mkdir',
npm ERR!   path: '/usr/local/lib/node_modules/typescript'
npm ERR! }
npm ERR!
npm ERR! The operation was rejected by your operating system.
npm ERR! It is likely you do not have the permissions to access this file as the current user

npm 10 이후에는 접두어가 npm error로 바뀌었습니다. 아래는 Node 22.20.0(npm 10.9.3)에서 쓰기 권한이 없는 전역 설치 폴더를 지정하고 npm install -g cowsay를 실행해 실제로 재현한 출력입니다. 경로는 테스트용 폴더지만, /usr/local에서 실패할 때와 메시지 구조가 같습니다(중간의 스택 트레이스 일부는 생략했습니다).

npm error code EACCES
npm error syscall mkdir
npm error path /tmp/npmexp/prefix/lib/node_modules/cowsay
npm error errno -13
npm error Error: EACCES: permission denied, mkdir '/tmp/npmexp/prefix/lib/node_modules/cowsay'
npm error     at async mkdir (node:internal/fs/promises:860:10)
...
npm error
npm error The operation was rejected by your operating system.
npm error It is likely you do not have the permissions to access this file as the current user
npm error
npm error If you believe this might be a permissions issue, please double-check the
npm error permissions of the file and its containing directories, or try running
npm error the command again as root/Administrator.

mkdir 대신 access '/usr/local/lib/node_modules'나 symlink ... -> '/usr/local/bin/...'이 찍히는 변형도 있습니다. 어느 쪽이든 핵심은 path 줄에 나온 폴더에 현재 사용자가 쓸 수 없다는 것입니다.

기준 환경: 우분투 24.04에서 apt로 설치한 nodejs·npm, 또는 맥에서 nodejs.org의 공식 설치 프로그램(.pkg)으로 설치한 Node. 두 경우 모두 Node 자체는 시스템 영역에 설치되고, 일반 사용자 계정으로 npm을 실행하는 상황을 전제로 합니다. 윈도우는 구조가 달라 마지막 부분에서 따로 다룹니다.

왜 전역 설치만 권한 오류가 나는가

npm에는 설치 위치가 두 종류 있습니다. npm install(전역 옵션 없이)은 현재 프로젝트의 node_modules에 설치하므로, 내 홈 폴더 아래의 프로젝트라면 권한 문제가 생기지 않습니다. 반면 npm install -g는 “prefix”라고 부르는 전역 경로 아래에 설치합니다.

  • 패키지 본체: <prefix>/lib/node_modules/<패키지>
  • 실행 명령(링크): <prefix>/bin/<명령>

현재 prefix는 다음 명령으로 확인합니다.

npm config get prefix
ls -ld "$(npm config get prefix)/lib/node_modules"

맥 공식 설치 프로그램으로 설치한 Node는 prefix가 /usr/local이고, 우분투에서도 /usr/local이나 /usr가 나오는 것이 보통입니다(설치 방법과 패키지 출처에 따라 다르므로 직접 확인하는 것이 정확합니다). 이 폴더들은 root 소유이고 일반 사용자에게는 읽기 권한만 있습니다. 그래서 npm이 그 안에 새 폴더를 만들려는(mkdir) 순간 운영체제가 거부하고, Node는 이를 EACCES(errno -13, 접근 권한 없음)로 보고합니다.

정리하면 이 오류는 npm의 버그가 아니라 정상적인 권한 검사입니다. 시스템 영역에 설치된 Node의 기본 전역 경로가 시스템 영역이기 때문에 생기는 구조적인 문제이고, 해결책도 “권한을 올린다”가 아니라 “전역 경로를 내 소유로 만든다” 쪽이어야 합니다.

sudo npm install -g를 피해야 하는 이유

오류 메시지 마지막 줄이 “root/Administrator로 다시 실행해 보라”고 하니 sudo npm install -g가 가장 먼저 떠오릅니다. 실제로 이렇게 하면 설치는 됩니다. 하지만 npm 공식 문서는 이 방법 대신 아래의 두 방법을 권하는데, 이유는 두 가지입니다.

첫째, 패키지의 설치 스크립트가 관리자 권한으로 실행될 수 있습니다. npm 패키지는 preinstall, install, postinstall 같은 스크립트를 가질 수 있고, 이 스크립트는 설치 과정에서 자동으로 실행됩니다. npm 버전에 따라 root로 실행할 때 스크립트의 실행 사용자를 낮추는 동작이 있거나 없었지만, 원칙적으로 sudo는 “이 패키지와 그 의존성 전체를 관리자 권한 범위에서 설치한다”는 뜻입니다. 의존성 수십 개 중 하나라도 악성으로 바뀐 패키지가 있다면 시스템 전체가 노출됩니다. npm 생태계에서 인기 패키지의 계정이 탈취돼 악성 버전이 배포된 사례는 여러 차례 보고됐습니다.

둘째, 홈 폴더의 npm 캐시가 root 소유로 바뀌어 이후 sudo 없는 작업까지 실패합니다. sudo로 실행된 npm이 ~/.npm 아래에 캐시 파일을 만들면 그 파일들의 소유자가 root가 됩니다. 그러면 나중에 프로젝트 폴더에서 평범하게 npm install을 해도 캐시에 쓰지 못해 다음과 같은 형태의 오류가 납니다(경로와 숫자는 계정마다 다릅니다).

npm ERR! code EACCES
npm ERR! syscall open
npm ERR! path /home/me/.npm/_cacache/index-v5/...
npm ERR! errno -13
npm ERR!
npm ERR! Your cache folder contains root-owned files, due to a bug in
npm ERR! previous versions of npm which has since been addressed.
npm ERR!
npm ERR! To permanently fix this problem, please run:
npm ERR!   sudo chown -R 1000:1000 "/home/me/.npm"

메시지는 “이전 버전 npm의 버그 때문”이라고 하지만, 지금 이 상태가 되는 가장 흔한 경로는 sudo로 npm을 실행한 것입니다. 그 결과 “전역 설치에서 sudo를 썼더니 이제 모든 npm 명령에 sudo가 필요해지는” 악순환이 시작됩니다.

셋째, 전역 패키지가 OS 패키지 관리자가 관리하는 영역과 섞입니다. 우분투에서 apt로 설치한 npm 자체를 sudo npm install -g npm으로 업그레이드하면, apt가 아는 파일과 npm이 덮어쓴 파일이 달라져 이후 apt 업그레이드나 제거가 꼬일 수 있습니다.

해결 1: nvm으로 Node를 사용자 영역에 설치하기

npm 공식 문서가 첫 번째로 권하는 방법은 Node 버전 관리자로 Node를 다시 설치하는 것입니다. nvm은 Node를 ~/.nvm 아래에 설치하므로 prefix도 홈 폴더 안이 되고, 전역 설치가 처음부터 내 소유 폴더에서 일어납니다. 여러 Node 버전을 프로젝트마다 바꿔 쓸 수 있다는 장점도 함께 생깁니다.

# nvm 설치 (버전 번호는 nvm 저장소의 README에서 최신 것을 확인)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash

# 새 터미널을 열거나 설정 다시 읽기
source ~/.bashrc      # 맥 기본 셸(zsh)이면 source ~/.zshrc

nvm install --lts
nvm use --lts
npm config get prefix  # /home/me/.nvm/versions/node/v22.x.x 형태로 나오면 성공
npm install -g typescript

기존에 apt로 설치한 Node가 있으면 nvm 설치 후에도 PATH 순서에 따라 옛 Node가 잡힐 수 있습니다. which -a node npm으로 확인하고, ~/.nvm/... 경로가 먼저 나오지 않으면 nvm 설정 줄이 셸 설정 파일에 들어갔는지 확인합니다. 시스템 Node가 더 필요 없다면 우분투에서는 sudo apt remove nodejs npm으로 정리하는 편이 혼동이 적습니다. 맥 공식 설치 프로그램으로 설치한 Node는 설치 프로그램이 제거 기능을 제공하지 않아 파일을 직접 지워야 하므로, 지우기 전에 /usr/local/bin/node, /usr/local/lib/node_modules 등 대상 경로를 확인한 뒤 진행합니다.

nvm을 쓸 때 알아 둘 점이 하나 있습니다. 전역 패키지는 Node 버전마다 따로 설치됩니다. Node 20에서 설치한 typescript는 Node 22로 바꾸면 보이지 않으므로, 새 버전을 설치할 때 nvm install 22 --reinstall-packages-from=20처럼 옮겨 오거나 다시 설치해야 합니다. nvm의 설치, .nvmrc, 윈도우용 nvm-windows와의 차이는 nvm으로 Node.js 버전 변경하기에서 자세히 다룹니다.

해결 2: Node는 그대로 두고 전역 설치 경로만 홈 폴더로 옮기기

회사 정책이나 다른 도구와의 호환 때문에 시스템 Node를 바꿀 수 없다면, npm의 prefix만 홈 폴더 안으로 옮깁니다. npm 공식 문서의 두 번째 방법입니다.

mkdir -p "$HOME/.npm-global"
npm config set prefix "$HOME/.npm-global"

이 설정은 ~/.npmrc에 prefix=/home/me/.npm-global 한 줄로 저장됩니다. 셸이 $HOME을 절대 경로로 펼친 뒤 저장하므로 다른 셸이나 IDE에서도 같은 경로로 읽힙니다.

다음으로 새 경로의 bin 폴더를 PATH에 추가합니다. 이 단계를 빼먹으면 설치는 성공하는데 명령을 찾지 못하는 상태가 됩니다. 실제로 prefix를 바꾼 뒤 PATH를 고치기 전과 후를 비교하면 다음과 같습니다(Node 22.20.0, npm 10.9.3, 경로만 바꿈).

$ npm config get prefix
/home/me/.npm-global

$ npm install -g cowsay
added 41 packages in 625ms

$ command -v cowsay || echo "cowsay not on PATH"
cowsay not on PATH

$ export PATH="$HOME/.npm-global/bin:$PATH"
$ which cowsay
/home/me/.npm-global/bin/cowsay

$ npm ls -g --depth=0
/home/me/.npm-global/lib
└── cowsay@1.6.0

export는 그 터미널에서만 유효하므로 셸 설정 파일에 넣어 영구 적용합니다.

# 우분투 기본 bash
echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.profile
source ~/.profile

# 맥 기본 zsh
echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.zprofile
source ~/.zprofile

우분투 데스크톱에서는 ~/.profile이 로그인 시점에 읽히므로, 새로 연 터미널에서 바로 반영되지 않으면 한 번 로그아웃했다가 다시 로그인합니다. 이미 ~/.bashrc에 PATH 설정을 모아 두고 있다면 그쪽에 넣어도 됩니다. 셸별 설정 파일이 읽히는 순서는 bash·zsh·PowerShell 비교를 참고하면 됩니다.

설정 파일 대신 환경 변수로 지정할 수도 있습니다. export NPM_CONFIG_PREFIX="$HOME/.npm-global"은 ~/.npmrc의 prefix와 같은 효과이고, CI나 컨테이너처럼 설정 파일을 두기 번거로운 곳에서 쓰기 좋습니다.

이 방법은 nvm과 함께 쓰지 않습니다. nvm은 Node 버전마다 자기 prefix를 쓰도록 설계돼 있어서, ~/.npmrc에 prefix가 있으면 nvm use 시 경고를 내며 제대로 동작하지 않습니다. 나중에 nvm으로 옮긴다면 npm config delete prefix로 이 설정을 먼저 지웁니다.

/usr/local 소유자를 바꾸는 방법은 권하지 않음

검색하다 보면 sudo chown -R $USER /usr/local 또는 /usr/local/lib/node_modules의 소유자를 바꾸라는 답변도 보입니다. 오류는 사라지지만 /usr/local은 다른 프로그램과 공유하는 시스템 경로이고, 소유권을 통째로 바꾸면 다른 설치 프로그램의 동작이나 보안 전제가 깨질 수 있습니다. 같은 효과를 내면서 부작용이 없는 방법(prefix 변경)이 있으므로 굳이 택할 이유가 없습니다.

이미 sudo를 써서 꼬였을 때 되돌리기

이미 sudo npm을 몇 번 실행했다면, 위의 해결책을 적용하기 전에 root 소유가 된 파일부터 정리해야 합니다. 정리하지 않으면 prefix를 바꿔도 캐시 단계에서 다시 EACCES가 납니다.

먼저 캐시 폴더의 소유권을 내 계정으로 돌려놓습니다.

sudo chown -R $(id -u):$(id -g) ~/.npm

$(id -u):$(id -g)는 현재 사용자의 UID와 그룹 ID로 바뀝니다. 앞의 오류 메시지에 나온 1000:1000이나 맥에서 흔한 501:20 같은 숫자가 바로 이 값입니다. 숫자를 직접 적어도 되지만 명령 치환을 쓰면 계정마다 달라지는 값을 신경 쓰지 않아도 됩니다. 소유권을 바꾼 뒤에는 npm cache verify로 캐시가 정상인지 확인합니다.

~/.npmrc 자체가 root 소유가 된 경우도 있습니다. ls -l ~/.npmrc로 확인하고 소유자가 root면 같은 방식으로 돌려놓습니다.

ls -l ~/.npmrc
sudo chown $(id -u):$(id -g) ~/.npmrc

sudo로 /usr/local에 설치해 둔 전역 패키지는 prefix를 바꾼 뒤에도 그 자리에 남아서, 같은 이름의 명령이 두 곳에 존재하게 됩니다. 새 경로로 옮기기 전에 무엇이 있었는지 확인하고 정리합니다.

ls /usr/local/lib/node_modules
sudo npm uninstall -g --prefix /usr/local typescript   # 정리할 패키지마다

이 정리 단계는 시스템 영역을 지우는 작업이므로 여기서만 sudo를 씁니다. npm 자체(/usr/local/lib/node_modules/npm)나 corepack처럼 Node 설치 프로그램이 함께 넣어 둔 패키지는 지우지 않습니다.

전역 설치가 정말 필요한지 다시 보기: npx

권한 문제를 해결하는 것과 별개로, 애초에 전역 설치가 필요한지 따져 볼 만합니다. 많은 CLI 도구는 전역 설치 없이 npx로 실행할 수 있습니다.

npx create-vite@latest my-app
npx cowsay hello

npx(npm 7부터는 npm exec와 같은 동작)는 현재 프로젝트의 node_modules/.bin에 명령이 있으면 그것을 쓰고, 없으면 패키지를 npm 캐시에 내려받아 실행합니다. 캐시는 홈 폴더(~/.npm/_npx)에 있으므로 시스템 권한이 필요 없습니다. 처음 실행할 때는 설치 여부를 묻는 확인이 나오고, --yes를 붙이면 묻지 않습니다.

프로젝트에서 쓰는 도구(TypeScript, ESLint, Prettier 등)는 전역보다 프로젝트의 devDependencies로 설치하는 편이 낫습니다. 전역 설치는 컴퓨터마다 버전이 달라지기 쉬운데, 프로젝트에 설치하면 package.json과 lock 파일로 팀원 모두가 같은 버전을 씁니다.

npm install --save-dev typescript
npx tsc --version       # 프로젝트의 node_modules/.bin/tsc 실행

package.json의 scripts에 적은 명령은 node_modules/.bin이 PATH에 자동으로 추가된 상태로 실행되므로, "build": "tsc"라고만 써도 프로젝트의 TypeScript가 쓰입니다. 결국 전역 설치가 꼭 필요한 것은 여러 프로젝트에 걸쳐 쓰는 개인 도구 정도로 줄어듭니다. pnpm은 전역 저장소 구조가 npm과 달라 같은 상황에서 다른 설정(pnpm setup)을 쓰는데, 이 내용은 pnpm 사용법에서 다룹니다.

윈도우에서는 EACCES 대신 EPERM

윈도우에서 nodejs.org 설치 프로그램으로 Node를 설치하면 전역 prefix가 C:\Users\me\AppData\Roaming\npm처럼 사용자 폴더 안으로 잡힙니다. 그래서 리눅스·맥과 같은 EACCES는 드물고, 대신 다음과 같은 EPERM 오류를 더 자주 봅니다.

npm error code EPERM
npm error syscall rename
npm error path C:\Users\me\proj\node_modules\.some-package-xxxxxxxx
npm error errno -4048
npm error Error: EPERM: operation not permitted, rename 'C:\Users\me\proj\node_modules\...' -> '...'

윈도우는 다른 프로세스가 열고 있는 파일을 지우거나 이름을 바꿀 수 없기 때문에, 대부분 파일이 잠겨서 생깁니다. 흔한 원인은 실행 중인 개발 서버나 node 프로세스, 해당 폴더를 연 VS Code의 파일 감시, 실시간 검사 중인 백신 프로그램, OneDrive 동기화 폴더 안의 프로젝트입니다. 개발 서버와 편집기를 닫고 다시 시도하는 것이 첫 번째 확인이고, OneDrive 아래에 프로젝트를 두고 있다면 동기화되지 않는 폴더(예: C:\dev)로 옮기는 것이 근본적인 해결입니다.

관리자 권한으로 터미널을 열어 실행하는 것은 잠긴 파일 문제를 해결하지 못하는 경우가 많고, 관리자 권한으로 만들어진 파일 때문에 나중에 일반 터미널에서 다시 권한 오류가 날 수 있습니다. 예외는 예전에 prefix를 C:\Program Files\nodejs처럼 보호된 폴더로 바꿔 둔 경우인데, 이때도 관리자 권한보다 npm config get prefix로 확인한 뒤 npm config delete prefix로 기본값(AppData\Roaming\npm)으로 되돌리는 쪽을 권합니다.

설정을 원래대로 되돌리려면

prefix 변경을 취소하고 싶다면 npm config delete prefix로 ~/.npmrc의 해당 줄을 지우고, 셸 설정 파일에 추가한 PATH 줄을 삭제한 뒤 rm -rf ~/.npm-global로 폴더를 지우면 됩니다. 그러면 npm은 Node 설치 위치에 따른 기본 prefix로 돌아갑니다. nvm을 제거할 때는 ~/.nvm 폴더와 셸 설정 파일에 nvm 설치 스크립트가 추가한 몇 줄(NVM_DIR 관련)을 지우면 됩니다.

어느 방법을 택했든 마지막에는 npm config get prefix, which -a node npm, ls -l ~/.npm(소유자가 내 계정인지) 세 가지를 확인해 두면, 같은 오류가 다시 나왔을 때 무엇이 바뀌었는지 바로 비교할 수 있습니다. 전역 설치 외의 npm install 오류(ERESOLVE, ENOSPC, node-gyp 빌드 실패 등)는 npm install 에러 해결 10가지에 정리돼 있습니다.