Published on

rsync 권한거부·소유권 깨짐 완전 해결 가이드

Authors

운영 환경에서 rsync는 백업, 서버 간 마이그레이션, 배포 아티팩트 동기화에 거의 표준처럼 쓰입니다. 그런데 막상 돌려보면 Permission denied로 중간에 멈추거나, 복사 후에 파일 소유권이 바뀌고 실행 비트가 사라지며, 심지어 SELinux 컨텍스트/ACL/xattr가 누락되어 서비스가 장애로 이어지는 일이 흔합니다.

이 글은 “왜 이런 일이 생기는지”를 원인별로 분해하고, 환경별로 바로 적용 가능한 해결책을 명령 단위로 정리합니다. 특히 -a 하나로는 해결되지 않는 케이스(원격 rsync, rootless, NAS, 컨테이너, SELinux, ACL)를 포함해 실무에서 자주 부딪히는 함정을 다룹니다.

문제 추적 관점은 장애 원인 추적 글들과 비슷한 접근이 유효합니다. 예를 들어 서비스가 비정상 재시작되는 원인을 좁혀가는 방식은 rsync 실패 지점(파일/디렉터리/권한)을 좁혀가는 방식과 닮아 있습니다: systemd 서비스가 계속 재시작될 때 원인 추적법

1) 증상 체크리스트: 무엇이 깨졌는지 먼저 분류

아래 중 어디에 해당하는지부터 확인하면 해결이 빨라집니다.

  • 전송 도중 rsync: send_files failed to open ... Permission denied (13)
  • 전송은 완료됐는데 대상 파일의 owner:group이 바뀜(대개 실행 계정으로)
  • 권한 모드가 달라짐(예: 755644로)
  • 심볼릭 링크가 파일로 풀리거나, 링크 자체가 깨짐
  • ACL이 사라짐(getfacl 결과가 달라짐)
  • xattr가 사라짐(예: security.capability, user.*, security.selinux)
  • 대상에서 서비스가 파일을 못 읽거나 실행 못함(배포 후 장애)

문제 분류를 위해 최소한 아래는 확인하세요.

# 소유권/권한/타임스탬프/링크 확인
stat -c '%A %a %U:%G %n' /path/to/file

# ACL 확인
getfacl -p /path/to/file | sed -n '1,40p'

# xattr 확인
getfattr -d -m - /path/to/file 2>/dev/null | sed -n '1,40p'

2) -a의 착각: 아카이브가 “모든 메타데이터”는 아니다

많이들 rsync -a면 끝이라고 생각하지만, -a는 아래의 묶음입니다.

  • -r 재귀
  • -l 심볼릭 링크 유지
  • -p 퍼미션 유지
  • -t 수정 시간 유지
  • -g 그룹 유지
  • -o 소유자 유지
  • -D 디바이스/특수파일

여기서 핵심은 “유지하려고 시도한다”는 점입니다. 원격으로 보낼 때는 대상에서 소유권을 바꿀 권한이 없으면 -o가 적용되지 않고, ACL/xattr/SELinux 컨텍스트는 -a에 포함되지 않습니다.

즉 실무에서는 보통 아래 조합이 기본값에 가깝습니다.

rsync -aHAX --numeric-ids --delete --info=stats2,progress2 \
  /src/ /dst/
  • -H: 하드링크 유지
  • -A: ACL 유지
  • -X: xattr 유지
  • --numeric-ids: 이름 기반 매핑 대신 UID/GID 숫자 그대로(계정명이 다른 서버 간 필수)
  • --delete: 목적지에서 소스에 없는 파일 삭제(백업/미러링 시)

3) Permission denied(13) 원인 7가지와 처방

3.1 디렉터리 탐색 권한 부족(가장 흔함)

파일 자체 권한이 아니라 “상위 디렉터리 execute 비트”가 없어도 접근이 막힙니다.

namei -l /dst/some/deep/path

해결은 상위 디렉터리에 x 권한(탐색)을 부여하거나, rsync 실행 계정을 바꾸는 것입니다.

# 예: 특정 그룹에게만 탐색권 부여
chmod g+x /dst /dst/some /dst/some/deep

3.2 원격 대상에서 소유권 변경 불가(chown 실패)

-a에는 -o가 포함되어 원격에서 chown을 시도합니다. 대상에서 root가 아니면 실패합니다.

해결 옵션은 두 가지 방향입니다.

  1. 소유권 보존을 포기하고 전송 성공을 우선
rsync -a --no-o --no-g /src/ user@host:/dst/
  1. 소유권까지 보존이 반드시 필요하면 root 권한으로 rsync를 실행
  • SSH로 root 접속이 가능하면
rsync -aHAX --numeric-ids -e 'ssh' /src/ root@host:/dst/
  • root SSH가 막혀 있고 sudo만 허용이면 --rsync-path를 씁니다.
rsync -aHAX --numeric-ids \
  -e 'ssh' \
  --rsync-path='sudo rsync' \
  /src/ user@host:/dst/

주의: 원격에서 sudo가 비밀번호를 요구하면 실패합니다. sudoers에 NOPASSWD 설정이 필요합니다.

3.3 대상 파일시스템이 소유권/권한을 지원하지 않음

예: FAT 계열, 일부 오브젝트 스토리지 마운트, 특정 NAS 설정은 POSIX 권한을 온전히 지원하지 않습니다. 이런 경우 chown, chmod가 의미가 없거나 제한됩니다.

확인:

mount | grep ' /dst '
stat -f -c '%T' /dst

처방:

  • 가능한 경우 POSIX 지원 파일시스템으로 변경(ext4, xfs 등)
  • 불가능하면 “배포용”과 “백업용”을 분리하고, 배포용은 권한을 목적지 정책에 맞게 재설정
rsync -rltD --delete /src/ /dst/
# 전송 후 목적지에서 권한을 정책대로 강제
chmod -R u=rwX,g=rX,o= /dst/app

3.4 umask 때문에 권한이 바뀌는 것처럼 보임

rsync가 파일을 “새로 생성”하는 경로에서는 umask가 영향을 줍니다. 보통 -p로 최종 권한을 맞추지만, 권한 변경 자체가 막혀 있으면 umask 상태로 남습니다.

확인:

umask

처방:

  • 목적지에서 권한 변경이 가능하도록 권한/소유권을 먼저 맞추거나
  • rsync에 권한 강제 옵션을 사용
rsync -a --chmod=Du=rwx,Dg=rx,Do=rx,Fu=rw,Fg=r,Fo=r /src/ /dst/

3.5 SELinux 컨텍스트 누락으로 실행/접근 실패

전송은 성공했는데 서비스가 파일을 못 읽는다면 SELinux 컨텍스트가 깨졌을 수 있습니다. -X로 xattr를 옮겨도, 환경 차이로 컨텍스트가 부적절할 수 있습니다.

확인:

getenforce
ls -Z /dst/app | sed -n '1,20p'

처방:

  • 보통은 목적지 정책에 맞게 재라벨링이 정답입니다.
restorecon -RFv /dst/app
  • 아예 SELinux 컨텍스트까지 동일하게 복제해야 하는 특수 케이스에서만 -X의 의미가 큽니다.

3.6 ACL/xattr 미복제로 인한 “권한은 같은데 접근이 안 됨”

POSIX 모드(chmod)만 동일해도 ACL이 다르면 접근 권한이 달라집니다.

처방:

rsync -aAX /src/ /dst/

주의: 대상 파일시스템이 ACL/xattr를 지원해야 합니다. 지원하지 않으면 경고가 나거나 조용히 누락됩니다.

3.7 심볼릭 링크/하드링크 처리 문제

  • 심볼릭 링크를 그대로 유지하려면 -l(=-a에 포함)
  • 링크가 가리키는 실제 파일로 “풀어서” 복사하려면 -L

실수로 -L을 넣으면 링크 구조가 깨지고, 배포 시 경로가 달라져 장애가 날 수 있습니다.

# 링크를 유지(권장)
rsync -a /src/ /dst/

# 링크를 실제 파일로 복사(의도한 경우에만)
rsync -aL /src/ /dst/

4) 소유권 깨짐: UID/GID 매핑 문제를 정면으로 해결

서버 A와 B에 같은 이름의 계정이 있어도 UID/GID가 다르면 소유권이 달라집니다. 특히 NFS, NAS, 온프레미스에서 자주 발생합니다.

4.1 --numeric-ids로 숫자 ID를 그대로 전달

rsync -aHAX --numeric-ids /src/ user@host:/dst/

단, 목적지에 해당 UID/GID가 존재하지 않으면 ls -l에서 숫자로 보이거나, 서비스 계정과 매칭되지 않아 접근 문제가 생길 수 있습니다.

4.2 계정/그룹을 목적지에 동일 UID/GID로 맞추기(근본 해결)

# 예: 목적지에서 appuser를 UID 1001로 맞춘다
sudo usermod -u 1001 appuser
sudo groupmod -g 1001 appgroup

주의: 이미 파일이 존재한다면 소유권 재정리가 필요합니다.

sudo find / -xdev -uid OLD_UID -exec chown -h 1001 {} +

운영 서버에서는 영향 범위가 크니 반드시 점검 후 진행하세요.

5) 원격 동기화에서 자주 터지는 함정들

5.1 --rsync-path='sudo rsync' 사용 시 TTY/비밀번호 문제

원격 sudo가 TTY를 요구하거나 비밀번호를 요구하면 non-interactive rsync가 실패합니다.

  • sudoers에서 대상 명령만 NOPASSWD로 허용
  • Defaults requiretty 같은 옵션이 켜져 있으면 해제(배포 서버 정책에 따라 다름)

5.2 SSH 옵션과 함께 쓰는 안정적인 템플릿

rsync -aHAX --numeric-ids --delete \
  -e 'ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=3' \
  --rsync-path='sudo rsync' \
  /src/ user@host:/dst/

네트워크 이슈로 중간에 끊길 때는 --partial--partial-dir이 도움이 됩니다.

rsync -aHAX --numeric-ids --delete --partial --partial-dir='.rsync-partial' \
  /src/ user@host:/dst/

6) “안전한 삭제”와 “권한 문제”를 동시에 다루는 운영 옵션

권한 문제를 해결한다고 --delete를 무작정 켜면, 경로를 잘못 지정했을 때 대형 사고로 이어집니다. 아래 습관을 권장합니다.

6.1 항상 --dry-run으로 삭제 목록부터 확인

rsync -aHAX --numeric-ids --delete --dry-run --itemize-changes \
  /src/ /dst/

6.2 삭제 보호: --delete-delay 또는 --backup 전략

# 삭제를 전송 끝에 몰아서 수행
rsync -a --delete-delay /src/ /dst/

# 삭제 대신 백업 디렉터리로 이동
rsync -a --delete --backup --backup-dir='.rsync-deleted' /src/ /dst/

7) 문제 재현과 디버깅: rsync 로그를 “읽을 수 있게” 만들기

권한 문제는 “어느 파일에서, 어떤 syscall이 막혔는지”가 핵심입니다.

7.1 변경 사항을 라인 단위로 확인

rsync -a --itemize-changes --stats /src/ /dst/

7.2 실패 지점 확대: -vv와 로그 파일

rsync -a -vv --log-file='rsync.log' /src/ user@host:/dst/

7.3 최종 보루: strace로 권한 거부 syscall 확인

sudo strace -f -o rsync.strace rsync -a /src/ /dst/
# EACCES, EPERM 위주로 필터링
grep -E 'EACCES|EPERM' rsync.strace | head -n 50

이 방식은 “증상만 보고 추측”하는 시간을 줄여줍니다. 장애 원인 추적이 필요한 경우의 사고방식은 다음 글도 참고할 만합니다: OpenAI Responses API 504 Timeout 재현·해결

8) 실전 레시피 6종: 상황별 정답 커맨드

8.1 같은 서버 내 디렉터리 미러링(권한/ACL/xattr 포함)

rsync -aHAX --numeric-ids --delete --info=stats2,progress2 \
  /srv/app/ /backup/app/

8.2 원격으로 보내는데 root 권한 필요(SSH는 일반 계정)

rsync -aHAX --numeric-ids --delete \
  -e 'ssh' \
  --rsync-path='sudo rsync' \
  /srv/app/ deploy@prod:/srv/app/

8.3 소유권은 목적지 정책에 맡기고, 퍼미션만 통제

rsync -a --no-o --no-g --delete \
  --chmod=Du=rwx,Dg=rx,Do=,Fu=rw,Fg=r,Fo= \
  /build/out/ /var/www/app/

8.4 계정명이 다른 서버 간 소유권 보존(UID/GID 기준)

rsync -aHAX --numeric-ids /data/ user@host:/data/

8.5 심볼릭 링크는 유지하되, 링크가 깨진 항목은 제외

rsync -a --copy-links --safe-links /src/ /dst/

주의: --copy-links-L과 유사하게 링크를 풀어 복사합니다. “링크를 유지”가 목적이면 빼야 합니다. 링크가 외부를 가리키는 위험만 막고 싶다면 --safe-links만 쓰는 편이 안전합니다.

8.6 전송 중 끊김이 잦은 환경(재개 친화)

rsync -aHAX --numeric-ids --delete \
  --partial --partial-dir='.rsync-partial' \
  --timeout=60 \
  -e 'ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=3' \
  /src/ user@host:/dst/

9) 체크 포인트: “해결됐는지” 검증하는 방법

마지막으로, 전송 성공 로그만 보고 끝내면 안 됩니다. 아래처럼 “속성 동등성”을 확인해야 합니다.

# 권한/소유권/링크가 기대대로인지
find /dst/app -maxdepth 2 -type f -exec stat -c '%a %U:%G %n' {} + | head

# ACL 샘플 비교
getfacl -p /src/app/somefile
getfacl -p /dst/app/somefile

# xattr 샘플 비교
getfattr -d -m - /src/app/somefile 2>/dev/null
getfattr -d -m - /dst/app/somefile 2>/dev/null

운영에서는 “검증 자동화”까지 포함해 두는 게 좋습니다. 배포 파이프라인에서 캐시/아티팩트가 의도대로 재사용되는지 확인하는 방식과 유사하게, rsync 결과도 반복 검증이 중요합니다: GitHub Actions 캐시가 안 먹을 때 - key·restore-keys 완전 정복

10) 결론: 권한 문제는 옵션이 아니라 환경의 합성 문제

rsync 권한거부와 소유권 깨짐은 단일 원인보다 “권한 모델(POSIX 모드/ACL) + 실행 주체(root 여부) + UID/GID 매핑 + 파일시스템 기능 + 보안 레이어(SELinux) + 원격 실행 방식(sudo)”이 합쳐져 발생합니다.

정리하면 다음 우선순위로 접근하세요.

  1. 실패가 EACCES인지 EPERM인지부터 로그/strace로 확인
  2. 소유권 보존이 필요한지 정책 결정(필요하면 root 또는 sudo rsync)
  3. 서버 간이면 --numeric-ids와 UID/GID 정합성부터 점검
  4. ACL/xattr/SELinux가 필요한 워크로드면 -A -Xrestorecon을 포함
  5. --dry-run과 검증 커맨드로 결과를 확인한 뒤 운영에 반영

이 흐름대로만 정리해두면, rsync는 “가끔 운 좋게 되는 도구”가 아니라 “예측 가능한 동기화 도구”가 됩니다.