Linux의 계정신분과 권한

서버 운영 · 리눅스 권한 구조

워드프레스 업데이트가 안 되던 이유 — bitnami와 daemon, 그리고 리눅스 권한 이야기

얼마 전 Lightsail에 올려둔 워드프레스 사이트 두 개를 업데이트하려다가 이상한 걸 발견했습니다. 관리자 대시보드에서 업데이트 버튼을 눌러도 조용히 아무 일도 일어나지 않는 겁니다. 오류 메시지도 없고, 그냥 버전 숫자가 그대로였습니다. 반면 같은 워드프레스라도 NAS에 올려둔 개인 사이트는 별다른 조작 없이도 최신 버전으로 저절로 올라가 있었습니다.

같은 워드프레스, 같은 PHP인데 왜 한쪽은 되고 한쪽은 안 될까요. 원인을 따라가다 보니 리눅스 서버를 만질 때 거의 항상 마주치게 되는 개념 하나로 귀결됐습니다. 바로 “이 파일을 지금 누가 만지려고 하는가”를 리눅스가 어떻게 판단하는가 하는 문제입니다. 이번 글에서는 그 문제를 파고들면서 알게 된 bitnami, daemon 같은 계정 구조와 리눅스 권한 모델을 정리해 봤습니다.

1. 모든 건 “신분 확인”에서 시작한다

리눅스는 사람이 로그인해서 직접 타이핑하는 명령이든, 웹서버가 자동으로 돌리는 프로그램이든 구분하지 않습니다. 파일을 읽거나 쓰려는 시도가 들어오면 리눅스 커널은 딱 한 가지만 확인합니다.

“지금 이 요청을 보낸 프로세스는 누구의 신분으로 움직이고 있는가?”

여기서 “신분”이라는 게 중요합니다. 사람이든 프로그램이든 리눅스 위에서 뭔가를 실행하는 순간, 반드시 어떤 사용자 계정의 자격을 빌려서 움직이게 됩니다. 브라우저로 사이트에 접속했을 때 서버 내부에서 벌어지는 일을 순서대로 풀어보면 이렇습니다.

사용자 브라우저 │ https://example.com 요청 ▼ Apache (웹서버) │ 요청을 PHP로 넘김 ▼ PHP 프로세스 실행 ← 이 프로세스도 “누군가의 신분”이 필요하다 │ ▼ 워드프레스 파일 읽기 / 쓰기 / DB 접속

여기서 PHP 프로세스가 걸치고 있는 그 “신분”이 바로 이번 사건의 핵심이었던 daemon 계정입니다.

2. bitnami와 daemon, 이름은 비슷해 보여도 역할이 다르다

Bitnami가 세팅해 둔 Lightsail 서버에는 성격이 전혀 다른 두 계정이 함께 존재합니다.

계정정체쓰는 시점
bitnami사람이 SSH로 로그인해서 쓰는 관리자용 계정서버에 직접 접속해서 파일을 고치거나 명령을 실행할 때
daemonApache/PHP 같은 백그라운드 서비스가 파일에 접근할 때 쓰는 시스템 계정브라우저 요청이 들어와 PHP가 자동으로 실행될 때

bitnami는 “나”이고, daemon은 “내 서버 안에서 24시간 돌아가는 프로그램들의 신분증”이라고 보면 됩니다. ssh bitnami@서버주소로 접속해서 뭔가를 만들면 그 파일의 주인은 보통 bitnami가 되지만, 브라우저 요청을 받아 워드프레스 코드를 실제로 실행하는 PHP 프로세스는 daemon이라는 별도의 신분으로 움직입니다.

daemon이라는 이름의 유래
daemon은 특정 회사나 개발자가 관용적으로 붙인 이름이 아니라, 유닉스 계열 운영체제에서 오래전부터 쓰여 온 전통적인 시스템 계정 이름입니다. “daemon”이라는 단어 자체가 사용자 개입 없이 백그라운드에서 계속 돌아가는 프로그램(웹서버, 메일서버, 스케줄러 등)을 가리키는 일반 용어입니다. 그래서 이런 백그라운드 서비스들을 일반 로그인 계정과 분리해서 실행하려는 관행에서 daemon 계정이 자리를 잡았습니다. 다만 요즘 배포판이나 스택에서는 www-data, nginx, apache, mysql처럼 서비스별로 더 세분화한 전용 계정을 쓰는 경우가 많고, Bitnami는 그중 일부 웹/PHP 프로세스를 daemon 계정(또는 daemon 그룹 권한)으로 실행하는 구성을 택하고 있는 것입니다.

왜 굳이 둘을 나눠 놓았을까

PHP를 그냥 bitnami 신분으로 실행하면 훨씬 편할 텐데, 왜 daemon이라는 별도 계정을 끼워 넣을까요. 답은 보안입니다. 워드프레스 플러그인 하나가 취약점으로 뚫렸다고 가정해 보면 이 구조의 의미가 분명해집니다.

만약 PHP가 bitnami 신분으로 돈다면 ───────────────────────────── 공격자 → 플러그인 취약점 → PHP 코드 실행 → PHP는 bitnami 신분 → bitnami가 접근 가능한 “서버의 거의 모든 것”에 접근 가능 실제로는 PHP가 daemon 신분으로 돈다 ───────────────────────────── 공격자 → 플러그인 취약점 → PHP 코드 실행 → PHP는 daemon 신분 → daemon에게 허용된 범위(워드프레스 폴더 등)로 피해 제한

이렇게 프로그램이 실제로 필요한 만큼의 권한만 갖도록 설계하는 원칙을 최소 권한 원칙(principle of least privilege)이라고 부릅니다. PHP가 뚫리는 사고는 워드프레스 생태계에서 드문 일이 아니기 때문에, “뚫혔을 때 피해 범위를 얼마나 좁혀둘 것인가”가 서버 권한 설계의 핵심 고민이 됩니다.

3. rwx와 소유자·그룹·기타, 리눅스 권한의 기본 문법

이 둘의 관계를 이해하려면 리눅스 파일 권한 표기를 먼저 알아야 합니다. ls -l로 파일을 보면 이런 식으로 나옵니다.

-rw-rw-r--  1 bitnami daemon  3205  9월 22 19:00  index.php

이 한 줄에 생각보다 많은 정보가 압축돼 있습니다.

rw-rw-r--를 셋으로 잘라보면 이렇게 읽힙니다.

rw- rw- r– │ │ │ │ │ └─ others(그 외 모든 사용자): 읽기만 │ └────── group(daemon): 읽기 + 쓰기 └─────────── owner(bitnami): 읽기 + 쓰기

흔히 chmod 664처럼 숫자로도 표현하는데, 숫자 하나하나가 r=4, w=2, x=1의 합입니다. rw-는 4+2=6, r--는 4. 그래서 664는 “소유자 6(rw), 그룹 6(rw), 기타 4(r)”를 압축해서 쓴 것뿐입니다.

숫자권한의미
7rwx읽기+쓰기+실행(폴더는 “진입”)
6rw-읽기+쓰기
5r-x읽기+실행(폴더는 진입)
4r–읽기만
0접근 불가
폴더의 x는 “실행”이 아니라 “진입”
파일에서 x는 그 파일을 실행 파일로 돌릴 수 있다는 뜻이지만, 폴더에서 x는 의미가 다릅니다. 그 폴더 “안으로 들어갈 수 있는가”를 뜻합니다. 그래서 워드프레스가 새 파일을 만들거나 지우려면 폴더 쪽에는 쓰기(w)뿐 아니라 진입(x) 권한까지 함께 필요합니다. 업데이트 관련 폴더 권한이 흔히 775(rwxrwxr-x)로 설정되는 이유입니다.

664, 775는 계정에 붙는 값이 아니라 “그 파일·그 폴더” 하나하나에 붙는 값

여기서 헷갈리기 쉬운 지점이 하나 있습니다. 664775를 보고 있으면 “그러면 bitnami 신분은 664, daemon 신분은 775 같은 식으로 계정마다 정해진 권한이 있는 건가” 하는 생각이 들 수 있는데, 그렇지 않습니다.

664, 775는 계정에 매겨진 값이 아니라, 파일이나 폴더 하나하나가 각자 가지고 있는 값입니다. 그 값을 owner / group / others라는 세 자리로 쪼개서 읽을 뿐이고, owner와 group이 우연히 누구인지(bitnami, daemon)는 파일마다 다를 수 있습니다. 즉 “숫자가 곧 신분”이 아니라 “숫자는 그 파일에 대해 세 신분 각각에게 무엇을 허용할지 적어놓은 표”입니다.

예를 들어 같은 워드프레스 안에서도 파일·폴더마다 권한 숫자는 제각각입니다.

index.php → 664 wp-config.php → 640 wp-content/ → 775 uploads/ → 775

그리고 이 각각의 파일·폴더는 동시에 아래 세 가지 정보를 자기 것으로 따로 들고 있습니다. index.php를 예로 들면 이렇습니다.

소유자(owner) : bitnami 그룹(group) : daemon 권한(mode) : 664

이 664를 한 자리씩 세 개로 쪼개면 다음과 같이 해석됩니다.

6 → owner(bitnami)의 권한 6 → group(daemon)의 권한 4 → others의 권한

즉 구조를 나무 그림으로 그려보면 이렇습니다. 파일 하나는

파일 하나 ├─ 주인 : bitnami ├─ 그룹 : daemon └─ 권한 : 664

이렇게 생겼고, 폴더도 형태는 똑같습니다. 다만 권한 숫자만 다를 뿐입니다.

폴더 하나 ├─ 주인 : bitnami ├─ 그룹 : daemon └─ 권한 : 775

여기서 wp-config.php, wp-content/, uploads/도 전부 각자 이런 나무 그림을 하나씩 따로 가지고 있는 셈입니다. 서버 안의 모든 파일과 폴더는 이렇게 독립적으로 소유자·그룹·권한 세 가지를 들고 있기 때문에, 같은 워드프레스 안에서도 index.php는 664, wp-content/는 775, wp-config.php는 640처럼 파일·폴더별로 서로 다른 권한을 얼마든지 줄 수 있는 것이고, 실제로도 그렇게 줍니다.

그래서 정확한 표현은 “bitnami 신분에는 664라는 권한이 붙어 있다”가 아니라 “index.php라는 파일에 664라는 권한표가 붙어 있고, 그 표의 owner 칸에 마침 bitnami가 들어가 있다”입니다. bitnami는 서버 어디를 가든 항상 664만큼의 권한을 들고 다니는 게 아니라, 어떤 파일을 만나느냐에 따라 그 파일이 정해둔 권한표 안에서 자신의 자리(owner일 수도, group일 수도, 심지어 others일 수도 있습니다)에 적힌 만큼만 대접받습니다. 파일·폴더 하나하나가 “누가 오면 무엇을 허용할지” 자기 권한표를 스스로 들고 있고, bitnami나 daemon 같은 계정은 그때그때 그 표를 마주쳐서 자기 몫을 확인하는 쪽에 가깝습니다.

4. 실제로 무슨 일이 있었나 — 644에서 664로

이제 처음 문제로 돌아가 보면 원인이 명확해집니다. 문제가 있던 시점의 워드프레스 파일 권한은 대략 이랬습니다.

변경 전 (644 / 755) ───────────────────────── bitnami(owner) : 읽기 + 쓰기 daemon(group) : 읽기만 ← 여기가 문제 기타 : 읽기만 → PHP(daemon 신분)는 코어 파일을 “읽을 수는” 있지만 “덮어쓸 수는” 없다 → 관리자 대시보드에서 업데이트 버튼을 눌러도 조용히 건너뛰어진다 (에러 로그도 안 남음)

즉 PHP 입장에서는 새 버전 파일을 받아와도 그걸 실제로 써넣을 권한 자체가 없었던 겁니다. 그래서 그룹(daemon)에 쓰기 권한을 얹어주는 조치를 했습니다.

변경 후 (664 / 775) ───────────────────────── bitnami(owner) : 읽기 + 쓰기 daemon(group) : 읽기 + 쓰기 ← 추가된 부분 기타 : 읽기만 → PHP(daemon)도 이제 파일을 덮어쓸 수 있음 → 대시보드 업데이트, 자동 업데이트 모두 정상 작동

여기서 중요한 건 PHP를 bitnami로 바꾼 게 아니라는 점입니다. PHP는 여전히 daemon 신분 그대로이고, 파일 소유자도 여전히 bitnami입니다. 바뀐 건 딱 하나, “daemon 그룹에게 쓰기 권한을 추가로 내줬다”는 것뿐입니다. 집주인(bitnami)은 그대로 두고, 관리업체 직원(daemon)에게 예전엔 열쇠만 줬다면 이번엔 가구를 옮길 권한까지 준 셈입니다.

단, wp-config.php는 예외로 뒀다

DB 접속 정보와 보안 키가 들어 있는 wp-config.php만큼은 그룹 쓰기 권한을 주지 않고 640(rw-r—–)으로 남겨뒀습니다.

wp-config.php : 640 ───────────────────────── bitnami(owner) : 읽기 + 쓰기 → 내용을 고칠 수 있는 건 나뿐 daemon(group) : 읽기만 → PHP는 실행을 위해 읽기만 가능 기타 : 접근 불가

PHP는 사이트를 돌리기 위해 이 파일을 반드시 읽어야 하지만, 이 파일을 고칠 수는 없도록 막아둔 겁니다. 만약 PHP 코드가 취약점으로 뚫리더라도 공격자가 이 파일을 직접 변조해서 DB 접속 정보를 바꿔치기하는 경로는 차단되는 셈입니다.

5. 그럼 코어 파일을 다시 잠가야 할까?

이 지점에서 자연스럽게 드는 의문이 “그럼 업데이트 끝났으니 다시 쓰기 권한을 잠가야 하는 거 아닌가”입니다. 실제로 Bitnami가 기본값으로 코어 파일 쓰기를 막아둔 이유도 “PHP가 뚫렸을 때 코어 파일까지 고쳐지는 걸 막기 위해서”입니다.

그런데 따져보면 이 잠금이 막아주는 범위는 생각보다 좁습니다.

반대로 코어를 잠가서 잃는 건 훨씬 치명적입니다. 워드프레스에는 WP_AUTO_UPDATE_CORE = 'minor'라는 설정으로 보안·버그 수정 버전을 자동으로 설치하는 기능이 있는데, 파일 쓰기 권한이 없으면 워드프레스는 그 사실을 에러로 남기지도 않고 그냥 조용히 건너뜁니다. 즉 “자동 업데이트가 켜져 있는 것처럼 보이지만 실제로는 몇 달째 동작하지 않고 있는” 상태가 만들어질 수 있습니다. 그리고 실제로 워드프레스 사이트가 침해당하는 사고의 절대다수는 이렇게 밀린 코어·플러그인 업데이트에서 시작됩니다.

결론: 다시 잠그지 않는 쪽이 낫다.
코어 잠금으로 얻는 방어 효과는 제한적인 반면, 자동 보안 업데이트가 조용히 멈춰버리는 리스크는 훨씬 큽니다. 대신 wp-config.phpDISALLOW_FILE_EDIT를 넣어 관리자 화면의 테마·플러그인 코드 편집기를 꺼두는 쪽이 실질적입니다. 관리자 계정이 털렸을 때 그 화면에서 바로 PHP 코드를 심는 경로를 막아주면서도, 정상적인 자동 업데이트에는 영향을 주지 않기 때문입니다.

6. 그런데 NAS는 왜 애초에 문제가 없었을까

같은 워드프레스인데 NAS 쪽 사이트는 왜 처음부터 자동 업데이트가 됐을까요. 답은 파일 소유 구조 자체가 다르기 때문입니다.

NAS (Synology)Lightsail (Bitnami)
파일 소유자PHP를 실행하는 계정 자신(예: http)bitnami (사람 계정)
PHP 실행 신분파일 소유자와 동일daemon (그룹으로만 연결)
기본 쓰기 가능 여부가능 (소유자이므로)불가능 → 그룹 권한 추가 필요

Synology의 Web Station은 PHP를 실행하는 계정이 워드프레스 파일의 소유자와 같습니다. 그래서 처음부터 아무 조정 없이도 자기 파일을 자기가 고칠 수 있었던 것뿐입니다. 반면 Bitnami 구조는 “사람 계정(bitnami)이 파일 주인, 서비스 계정(daemon)은 그룹으로만 연결”되는 모델이라, 그룹 쓰기 권한을 별도로 열어줘야 같은 결과가 나옵니다. 결과(자동 업데이트가 된다)는 같아도 그 결과에 도달하는 권한 구조는 서로 다른 셈입니다.

참고로 minor 버전(보안·버그 수정) 자동 업데이트는 워드프레스 자체의 기본값입니다. NAS 쪽에 따로 설정한 적이 없어도 자동으로 올라간 이유가 이것이고, Lightsail 쪽 설정에 있던 WP_AUTO_UPDATE_CORE = 'minor'도 사실 이 기본값을 한 번 더 명시적으로 적어둔 것에 가깝습니다.

7. root와 sudo — 이 모든 것 위에 있는 계정

bitnami와 daemon이 서버 안에서 역할을 나눠 갖고 있다면, 그 위에는 사실상 모든 파일에 접근 가능한 최고 관리자 계정 root가 있습니다. bitnami 신분으로는 권한이 부족해서 못 하는 작업(권한 변경 등)을 할 때 쓰는 게 바로 sudo입니다.

sudo chmod 664 index.php

sudo는 이 명령 한 줄이 실행되는 짧은 순간만 bitnami를 root로 “격상”시켜주는 장치라고 이해하면 됩니다. 항상 root로 로그인해 다니지 않고 필요한 순간에만 잠깐 권한을 빌려 쓰는 것도 같은 최소 권한 원칙의 연장선입니다.

8. 전체 구조를 한 장으로 정리하면

root (최고 관리자 권한) │ ┌────────────┴────────────┐ │ │ bitnami daemon 사람이 SSH로 로그인 웹서버/PHP 실행 신분 (배포, 서버 관리) (사이트 요청 처리) │ │ └────────── WordPress ────┘ owner = bitnami group = daemon 파일 : 664 (rw-rw-r–) 폴더 : 775 (rwxrwxr-x) wp-config.php : 640 (rw-r—–)

같은 워드프레스 파일을 두 개의 서로 다른 신분이 “그룹”이라는 연결고리를 통해 함께 관리하는 구조입니다. bitnami는 배포·유지보수를 담당하고, daemon은 실제 웹 요청을 처리하며 사이트를 돌립니다. 이 둘을 애초에 분리해 둔 이유, 그리고 그 분리 안에서도 wp-config.php만큼은 한 단계 더 엄격하게 막아둔 이유 모두 결국 하나로 수렴합니다. 뚫렸을 때 피해 범위를 최대한 좁게 설계해 두는 것.

결국 이번에 정리하면서 가장 남는 문장은 이겁니다. “PHP가 무엇인가”보다 “PHP가 지금 누구의 신분으로 이 파일에 접근하고 있는가”가 리눅스에서는 훨씬 중요한 질문입니다. 이 감각 하나만 붙잡고 있으면 chmod, chown, sudo, Permission denied 같은 메시지들이 더 이상 따로따로 외워야 하는 규칙이 아니라, 하나의 그림 안에서 자연스럽게 이해되기 시작합니다.