SSD Nodes Learn Hosting plans →
가이드 Matt Connor작성자 Matt Connor

nano 저장 권한 거부 해결 방법

nano에서 Permission denied 오류가 발생하는 4가지 원인을 정리했습니다. 파일 소유권, 상위 디렉터리 권한, 읽기 전용 파일 시스템, 컨테이너 내 UID 문제를 순서대로 점검하여 문제를 해결하십시오. 데이터 손실 없이 안전하게 파일을 저장하는 방법도 함께 안내합니다.

nano에서 파일을 저장할 수 없는 이유

nano에서 파일을 저장할 수 없는 이유는 다음 네 가지 중 하나입니다. 파일의 소유자가 아니거나, 상위 디렉터리에서 nano의 작업을 허용하지 않거나, 파일 시스템이 읽기 전용이거나 공간이 부족하거나, 혹은 다른 사용자 ID로 실행 중인 컨테이너 내부에 있기 때문입니다. 앞의 두 가지는 권한 문제이며, 뒤의 두 가지는 그렇지 않습니다. 이 순서대로 확인하십시오. 첫 번째 이유가 대부분의 사례를 차지하며, 명령 한 번으로 확인이 가능하고 해결 방법 또한 sudoedit가 sudo nano보다 적절하기 때문입니다.

편집기가 열려 있는 동안에는 데이터가 손실되지 않습니다. 텍스트는 메모리에 남아 있으므로 파일을 열어둔 채로 소유권이 있는 경로에 버퍼를 저장한 뒤, 나중에 해당 위치로 옮길 수 있습니다. 이 탈출구는 본 가이드의 마지막 부분에서 다룹니다.

권한을 변경하기 전에 다음 점검을 수행하십시오

각 명령어를 편집 중인 실제 경로에 적용하십시오. 이 명령어들은 서로 다른 정보를 제공하므로, 무언가를 수정하기 전에 모두 실행해야 합니다. 어떤 점검에서 실패가 발생하는지 모르는 상태에서 권한을 변경하면, 보통 기존 문제에 더해 새로운 문제가 추가로 발생합니다.

id
ls -l /etc/nginx/nginx.conf
ls -ld /etc/nginx
namei -l /etc/nginx/nginx.conf
findmnt -no SOURCE,FSTYPE,OPTIONS -T /etc/nginx/nginx.conf
df -h /etc/nginx
df -i /etc/nginx

id는 현재 사용자의 ID와 그룹 ID를 출력합니다. ls -l은 파일 자체의 소유자, 그룹, 권한 비트를 보여줍니다. ls -ld은 해당 파일을 포함하는 디렉터리에 대해 동일한 정보를 보여주며, 이는 파일과는 별개의 확인 사항입니다. namei -l은 경로의 모든 구성 요소를 따라가며 각 부분의 소유자와 권한을 나열하므로, 두 가지 질문에 대한 답을 한 번에 확인할 수 있습니다. findmnt는 해당 경로의 파일 시스템 이름과 마운트 옵션을 보여줍니다. df -h은 여유 공간을 보고하며, df -i은 여유 inode를 보고합니다. inode는 디스크 공간과는 별도로 고갈될 수 있습니다. 만약 권한 문자열이 익숙하지 않다면, ls -l이 출력하는 권한 문자열을 읽는 방법부터 확인하십시오.

원인 1: 파일 소유자가 root이며 사용자가 아님

읽기 권한과 쓰기 권한은 별개이며, /etc 하위의 대부분 파일은 누구나 읽을 수 있습니다. nano가 파일을 열어 내용을 보여주고 자유롭게 타이핑할 수 있게 하는 이유는 이 과정이 디스크에 아무런 영향을 주지 않기 때문입니다. 저장 시점에 거부 메시지가 나타나는 이유는 커널이 사용자의 ID 및 그룹 ID와 파일의 소유자, 그룹, 기타 권한 비트를 비교하기 때문입니다. nano는 커널이 전달한 내용을 그대로 보여주는 것뿐이므로, 어떤 nano 옵션을 사용해도 결과는 바뀌지 않습니다.

id 및 ls -l를 함께 사용하면 이 원인을 확인할 수 있습니다. 파일의 소유자는 root이고 사용자는 root가 아니며, 기타 권한 비트에도 쓰기 권한이 부여되어 있지 않습니다. Ctrl-O를 다시 누르는 것은 도움이 되지 않습니다.

root 소유 파일을 편집할 때 sudoedit을 사용해야 하는 이유

SUDO_EDITOR=nano sudoedit /etc/nginx/nginx.conf

sudo는 파일을 임시로 복사하여 소유자를 사용자 본인으로 변경한 뒤, 해당 복사본을 일반 사용자 권한으로 nano에서 실행합니다. 편집기가 종료되면 root 권한으로 결과물을 원본 위치에 덮어씁니다. 편집기 자체가 root 권한으로 실행되지 않는 방식입니다. sudo -e는 다른 이름으로 호출되는 동일한 명령어입니다. 편집기는 SUDO_EDITOR, VISUAL, EDITOR 순서로 확인하여 선택되므로, 셸 프로필에 export EDITOR=nano을 설정하면 어디서든 기본 편집기로 지정됩니다. 위 변수들에 지정된 편집기가 존재하지 않거나 실행할 수 없는 경우, sudoedit은 sudoers의 editor 설정으로 대체하며, 기본값은 vi입니다.

sudo nano 또한 파일을 저장하지만, 이 방식에는 문제가 있습니다. 세션이 유지되는 동안 대화형 편집기 전체에 파일 시스템에 대한 root 권한을 부여하기 때문입니다. 따라서 저장 프롬프트에서 경로를 잘못 입력하면 root 권한으로 다른 시스템 파일을 덮어쓰게 될 위험이 있습니다. 필요한 단계에서만 sudo를 호출하는 일반 사용자로 작업하는 습관을 들이는 것이 좋으며, 설정 파일을 편집할 때 이 습관을 실천하는 방법이 바로 sudoedit입니다.

sudoedit의 두 가지 규칙은 사용자들을 당황하게 만들기도 합니다. 첫째, 심볼릭 링크 편집을 거부합니다. 둘째, 사용자가 쓰기 권한을 가진 디렉터리 내의 파일은 root가 아닌 이상 편집을 거부합니다. 두 번째 규칙이 존재하는 이유는 디렉터리에 쓰기 권한이 있는 누군가가 편집기가 열려 있는 동안 파일을 교체할 수 있기 때문입니다. 두 동작 모두 sudoers의 기본 설정(sudoedit_follow off, sudoedit_checkdir on)입니다. 아직 존재하지 않는 파일은 새로 생성됩니다.

원인 2: 상위 디렉터리가 실제로 제어하는 것

다른 편집기를 위한 조언에서는 편집기가 새 파일을 작성한 뒤 기존 파일 위로 이름을 바꾸는 방식으로 저장하는 경우가 많으므로, 디렉터리에 쓰기 권한이 필요하다고 설명합니다. nano는 그런 방식으로 작동하지 않습니다. nano는 지정한 파일을 열어 그 파일에 직접 내용을 쓰므로, 이미 존재하는 파일의 경우 디렉터리의 쓰기 비트를 확인하지 않습니다.

그럼에도 디렉터리는 다른 사항들을 결정하며, 이것이 체크리스트에 ls -ld가 포함된 이유입니다.

  • 아직 존재하지 않는 파일을 생성하려면 디렉터리에 쓰기 및 실행 권한이 필요합니다. 새로운 이름을 추가해야 하기 때문입니다. umask는 새 파일이 시작될 때의 권한을 결정합니다.
  • 파일에 접근하려면 경로상의 모든 디렉터리에 실행 권한(검색 권한이라고도 함)이 있어야 합니다. 권한이 없는 디렉터리가 하나라도 있으면 그 아래의 모든 파일에 접근할 수 없으며, namei -l을 통해 어느 디렉터리가 문제인지 확인할 수 있습니다.
  • 백업 기능을 사용하거나 파일 잠금을 켠 상태로 저장하면 원본 파일 옆에 두 번째 파일이 생성되므로, 이러한 기능은 쓰기 가능한 디렉터리를 필요로 합니다. 백업은 -B 옵션 또는 nanorc의 set backup 설정이며, 잠금은 -G 또는 set locking입니다. 사용자나 배포판에서 활성화하지 않았다면 기본적으로 꺼져 있습니다.

디렉터리 권한은 시스템의 다른 곳에서도 동일한 비중을 가집니다. SSH 서버는 홈 디렉터리나 .ssh 디렉터리에 다른 사용자가 쓰기 권한을 가질 경우 키를 거부하며, 이는 로그인 시 SSH가 키를 거부하는 흔한 원인 중 하나입니다.

nano는 이미 존재하는 파일에 내용을 쓰기 때문에 파일은 inode를 유지합니다. inode는 이름 뒤에 숨겨진 디스크상의 실제 식별자입니다. 파일을 열어둔 프로세스는 계속해서 해당 파일을 추적하며, 컨테이너에 바인드 마운트된 단일 파일도 정상적으로 작동합니다. 파일을 교체하는 방식으로 저장하는 편집기는 마운트를 끊어버립니다. 마운트는 이름이 아닌 inode를 따라가기 때문입니다.

원인 3: 파일 시스템이 읽기 전용이거나 공간이 부족함

옵션에 findmnt를 지정하여 ro을 보고하는 것은 쓰기 작업이 처음부터 성공할 수 없었음을 의미합니다. 파일 시스템이 /etc/fstab나 읽기 전용 바인드 마운트를 통해 그렇게 마운트되었거나, 디스크 오류 발생 후 커널이 읽기 전용으로 다시 마운트했을 수 있습니다. 후자의 경우가 더 심각합니다. sudo dmesg -T | tail -50는 가장 최근의 커널 메시지를 보여주지만, 리마운트를 유발한 입출력 및 파일 시스템 오류가 반드시 마지막 50줄 안에 포함되지는 않습니다. tail 없이 sudo dmesg -T를 실행하여 커널 링 버퍼에 남아 있는 모든 메시지를 확인하고, 리마운트 이전에 기록된 오류를 찾으십시오. 복구 방법은 파일 시스템을 언마운트한 상태에서 파일 시스템 검사를 수행하는 것입니다. 루트가 아닌 파일 시스템은 언마운트 후 그 자리에서 검사할 수 있습니다. 루트 파일 시스템의 경우, 일반적인 부팅 과정에서 수행할 수 있습니다. systemd는 /etc/fstab의 fsck 패스 번호가 0보다 큰 모든 파일 시스템을 검사하며, initrd가 지원하는 경우 initrd 내에서 루트를 검사합니다. 또한 커널 명령줄에 fsck.mode=force을 추가하면 전체 검사를 강제할 수 있습니다. VPS가 부팅되지 않아 이 과정을 수행할 수 없는 경우, 제공업체의 구조용 콘솔(rescue console)을 대안으로 사용할 수 있습니다.

파일 시스템이 가득 차면 다른 이유로 동일한 쓰기 실패가 발생합니다. df -h은 일반적인 경우를 다룹니다. df -i는 사람들이 놓치기 쉬운 경우를 설명합니다. 기본값으로 흔히 사용되는 ext4에서는 파일 시스템 생성 시 고정된 풀에서 inode를 할당하므로, 작은 파일들이 트리 구조로 쌓이면 df -h상으로는 기가바이트 단위의 여유 공간이 남아 있더라도 inode가 모두 소진될 수 있습니다. XFS는 파일 생성 시 inode를 할당하므로 이 문제는 주로 ext4에서 발생합니다. 공간이 부족한데 원인을 알 수 없는 경우, df와 du의 디스크 사용량 불일치 문서를 참조하여 삭제되었지만 여전히 열려 있는 파일로 인한 문제를 해결하십시오.

이와 관련하여 혼란스러운 증상을 설명하는 세부 사항이 하나 있습니다. ext4는 파일 시스템 생성 시 블록의 일부를 루트 사용자를 위해 예약해 두기 때문에, 일반 사용자의 쓰기가 거부된 후에도 루트는 계속해서 데이터를 쓸 수 있습니다. 이때 sudo을 사용하여 문제를 해결한 것처럼 보일 수 있으나, 디스크가 나머지 공간까지 모두 차버리면 문제가 더 심각한 형태로 재발합니다.

nano는 파일 내용을 쓰기 전에 기존 파일을 잘라내기 때문에, 쓰기 도중 공간이 부족해지면 파일이 이전보다 짧아질 수 있습니다. 파일 시스템이 거의 찼을 때 중요한 설정 파일을 편집하려면 먼저 복사본을 만드십시오. sudo cp -a /etc/nginx/nginx.conf /root/nginx.conf.bak를 사용하면 복사본의 소유자, 그룹 및 권한을 그대로 유지할 수 있습니다.

원인 4: 컨테이너 내부에서 바인드 마운트된 파일을 수정하는 경우

파일 소유권은 숫자로 관리됩니다. 커널은 사용자 ID를 저장하며, 사용자가 보는 이름은 조회 시점에 어떤 /etc/passwd을 사용하는지에 따라 달라집니다. 따라서 하나의 파일이 호스트에서는 특정 이름으로 보이고, 컨테이너 내부에서는 다른 이름이나 단순한 숫자로 보일 수 있습니다. 이름을 비교하지 말고 숫자를 비교하십시오. 컨테이너 내부에서 id -u를 실행하고 파일에 대해 ls -ln을 실행하여 확인합니다.

바인드 마운트된 파일은 호스트에서의 소유권을 그대로 유지합니다. 호스트 파일의 소유자가 현재 사용자이고 컨테이너 프로세스가 다른 사용자로 실행 중이라면, 컨테이너 내부에서의 쓰기 작업은 거부됩니다. 또한 컨테이너 내부에서 sudo를 실행해도 호스트의 파일 소유자는 변경되지 않습니다. 이 문제는 호스트에서 컨테이너가 실행되는 ID로 소유자를 변경하거나, 파일 소유자와 동일한 ID로 컨테이너를 실행하여 해결하십시오. linuxserver.io와 같은 프로젝트에서 제공하는 이미지는 프로세스가 실행될 사용자를 지정하는 PUID 및 PGID 변수를 제공합니다.

알아두어야 할 두 가지 컨테이너 사례가 더 있습니다. :ro 옵션으로 읽기 전용으로 마운트되었거나 --read-only 옵션으로 시작된 컨테이너는 소유권과 관계없이 쓰기를 거부하며, 컨테이너 내부에서 cat /proc/mounts을 실행하면 해당 플래그가 표시됩니다. 루트 권한이 없는 Podman의 경우, 사용자 네임스페이스가 컨테이너의 사용자 ID를 호스트의 특정 ID 범위로 매핑합니다. 따라서 컨테이너 내부에서 root 소유로 보이는 파일이 호스트 외부에서는 일반 사용자 계정의 소유가 됩니다.

수정 작업이 성공한 것처럼 보이지만 사라지는 경우도 있습니다. 마운트 지점이 아닌 경로의 파일을 컨테이너 내부에서 수정하면 해당 변경 사항은 컨테이너의 쓰기 가능 레이어에 저장되며, 컨테이너가 재생성될 때 해당 레이어는 삭제됩니다. 변경 사항을 영구적으로 유지하려면 마운트의 호스트 측에서 파일을 수정하거나 이미지 빌드 과정에 반영하십시오.

탈출구: 소유권이 있는 경로에 저장하기

편집기 내부에서 권한을 상승시키려 하지 마십시오. Ctrl-O를 누르고 프롬프트에 나타난 경로를 지운 뒤, /home/you/nginx.conf.new와 같이 홈 디렉터리 하위의 경로를 입력하고 Enter를 누르십시오. 그 후 Ctrl-X를 눌러 종료합니다. 이제 작업 내용이 디스크에 저장되었으며 소유권은 사용자에게 있습니다. 나머지는 일반적인 파일 복사 작업입니다.

sudo cp /home/you/nginx.conf.new /etc/nginx/nginx.conf
sudo nginx -t

여기서는 mv 대신 cp를 사용하십시오. cp은 기존 파일을 그대로 둔 채 내용을 덮어쓰므로, 파일의 소유자, 그룹, 권한이 유지됩니다. 동일한 파일 시스템에서 mv를 사용하면 기존 파일이 사용자의 파일로 교체됩니다. 이 경우 /etc에 있는 설정 파일의 소유자가 사용자 계정으로 바뀌게 되며, 이는 곧 해결해야 할 또 다른 권한 문제로 이어집니다.

무엇인가를 다시 불러오기 전에 해당 파일을 관리하는 도구로 결과를 확인하십시오. sudo nginx -t는 Nginx 설정을 구문 분석하며, sudo sshd -t는 SSH 서버 설정을 구문 분석합니다. 두 파일은 이 전체 과정을 대신 수행해 주는 전용 편집기를 가지고 있습니다. /etc/sudoers을 위한 sudo visudo과 사용자의 cron 작업을 위한 crontab -e이 그것입니다. 각 편집기는 임시 복사본을 수정하고 구문을 검사한 뒤, 구문 분석이 성공할 때만 파일을 설치합니다.

FAQ

시스템 파일을 편집할 때 sudo nano와 sudoedit 중 무엇을 사용해야 합니까?

sudoedit을 사용하십시오. 이 명령은 파일을 사용자 소유의 임시 복사본으로 만든 뒤, 편집기를 일반 사용자 권한으로 실행하고, 편집기가 종료되면 결과를 root 권한으로 다시 씁니다. 따라서 편집기 자체가 root 권한을 가질 필요가 없습니다. nano를 선택하려면 SUDO_EDITOR, VISUAL 또는 EDITOR 환경 변수를 nano으로 설정하십시오. sudo nano도 사용할 수 있지만, 이 방식은 대화형 편집기에 시스템의 모든 경로에 대한 root 접근 권한을 세션 동안 부여합니다. 따라서 저장 프롬프트에서 파일 이름을 한 글자라도 잘못 입력하면 시스템 파일이 손상될 수 있습니다.

nano로 파일을 저장하려면 디렉터리에 쓰기 권한이 있어야 합니까?

이미 존재하는 파일이라면 그렇지 않습니다. nano는 파일 자체에 내용을 쓰므로, 커널은 파일의 쓰기 비트와 경로상 모든 디렉터리의 실행 비트를 확인합니다. 디렉터리의 쓰기 비트가 중요한 경우는 파일이 아직 존재하지 않아 새로운 이름을 생성해야 할 때, 또는 백업이나 파일 잠금 기능이 켜져 있어 원본 옆에 두 번째 파일을 생성해야 할 때입니다.

소유자 정보가 정확하고 디스크 용량도 충분합니다. 무엇이 쓰기를 방해할 수 있습니까?

네 가지 가능성이 있습니다. 첫째, 파일 시스템이 읽기 전용으로 마운트되었을 수 있으며, 이는 findmnt -no OPTIONS -T /etc/nginx/nginx.conf로 확인할 수 있습니다. 둘째, 파일에 변경 불가능(immutable) 속성이 설정되었을 수 있는데, 이는 lsattr으로 확인하고 sudo chattr -i로 제거할 수 있습니다. 이 속성이 설정되어 있으면 root 사용자조차 파일을 수정할 수 없습니다. 셋째, ext4 파일 시스템에서 여유 공간이 남아 있더라도 inode 풀이 고갈되었을 수 있으며, 이는 df -i로 확인할 수 있습니다. 넷째, 권한 비트상으로는 허용되더라도 SELinux나 AppArmor가 쓰기를 거부할 수 있습니다. 일반적으로 감사 로그에 거부 기록이 남지만, SELinux의 dontaudit 규칙이 적용되면 로그 없이 쓰기가 차단될 수 있습니다. sudo semodule -DB으로 해당 규칙을 끄고 문제를 재현한 뒤, sudo semodule -B로 다시 켤 수 있습니다.

파일이 전혀 저장되지 않을 때 변경 사항은 어디에 두어야 합니까?

Ctrl-O를 누른 뒤 홈 디렉터리나 사용자가 쓰기 권한을 가진 다른 경로를 지정하십시오. 버퍼는 메모리에 남아 있으므로 입력한 내용은 사라지지 않습니다. 이후 sudo cp을 사용하여 저장한 파일을 원래 위치로 복사하십시오. 이 명령은 원본 파일의 소유자와 권한을 유지합니다. 그 후 서비스를 다시 로드하기 전에 해당 서비스의 자체 테스트 명령으로 설정을 확인하십시오.

Docker 컨테이너 내부에서 수정한 내용이 왜 사라집니까?

경로가 마운트 지점이 아닐 경우, 수정 사항은 컨테이너의 쓰기 가능 레이어에 저장되며 컨테이너가 교체될 때 해당 레이어는 삭제됩니다. 바인드 마운트나 볼륨의 호스트 측 파일을 편집하거나, 이미지를 빌드할 때 파일을 포함시키십시오. 경로가 바인드 마운트인데도 저장이 거부된다면, 컨테이너 내부의 id -u와 ls -ln에서 확인한 숫자 형태의 소유자 정보를 비교해 보십시오. 파일은 호스트의 소유권을 유지하므로, 컨테이너 프로세스의 소유자와 일치해야 합니다.