---
id: case-permissions
title: "App lỗi vì quyền file"
shortTitle: "Quyền file"
track: cases
trackTitle: "Tình huống vận hành"
order: 18
level: core
status: published
estimatedMinutes: 35
prerequisites:
  - linux-filesystem-permissions
  - case-outage
next: ""
reviewedAgainst: 2026-10-12
sources:
  - title: "chmod"
    url: https://man7.org/linux/man-pages/man1/chmod.1.html
  - title: "Dockerfiles USER"
    url: https://docs.docker.com/reference/dockerfile/#user
---

# App lỗi vì quyền file

`EACCES` hay `permission denied` nghĩa là kernel đang từ chối đúng user mà process đang chạy. `chmod 777` làm lỗi biến mất, nhưng đồng thời mở file cho mọi account trên máy. Bài này đi theo hướng khác: tìm xem user nào đang chạy process, rồi cấp đúng quyền trên đúng path.

## Để làm gì

Bạn sẽ đọc được lỗi quyền, đối chiếu UID của process trong container với owner của file, và sửa lỗi mà không phải đưa app về chạy bằng root.

## Gồm những gì

- Bit đọc và ghi trên file. Bit thực thi trên thư mục là quyền đi vào thư mục đó.
- Volume mount và user được khai báo trong image.
- Service systemd ghi file bằng user trong cấu hình unit, không phải user bạn đang SSH vào.

Bài không đi sâu vào SELinux. Nếu `ls -l` đã đúng mà vẫn bị từ chối, hãy ghi nhận khả năng do SELinux hoặc AppArmor, rồi đọc log của cơ chế đó. Đừng tắt cả hai trên production chỉ để thử.

## Dùng khi nào

App không đọc được config, không ghi được log, upload bị fail, hoặc SQLite báo không ghi được file. Lỗi này hay xuất hiện ngay sau khi thêm `USER node` vào image, hoặc sau khi mount một volume mới thuộc root.

Đừng dùng bài này khi lỗi là 403 do ứng dụng trả về sau khi user đã đăng nhập. Đó là quyền nghiệp vụ, nằm trong database. Còn `permission denied` từ hệ điều hành là chuyện của UID và mode.

## Kiến thức cốt lõi

Mỗi process mang một UID. Kernel so UID đó với owner, group và mode của file. `chmod` chỉ đổi mode, không đổi người đang chạy process. Man page `chmod` mô tả ba nhóm quyền: user, group, other.

Quyền trên thư mục khác với quyền trên file:

| Cần | Trên file | Trên thư mục |
| --- | --- | --- |
| Đọc nội dung | `r` | `r` để liệt kê tên |
| Ghi hoặc tạo file con | `w` trên file | `w` và `x` |
| Đi vào path | không đủ nếu thư mục cha thiếu `x` | `x` trên mọi thư mục cha |

Một file `644` thuộc root thì user khác đọc được nhưng không ghi được. Một thư mục `700` thuộc root thì user của app không vào được, dù file bên trong là `666`.

Container làm mọi thứ lệch thêm một bậc. `USER node` trong Dockerfile là user bên trong image, thường có UID 1000. Volume trên host thì thuộc root, UID 0. Trong container, UID 1000 không phải owner của volume đó. Docs Dockerfile nói `USER` đặt account cho các lệnh phía sau và cho process lúc chạy. Image chạy bằng root thì ghi được vào volume, nhưng đổi lại, một lỗ hổng trong app sẽ có quyền root trên filesystem đó. Cách sửa đúng là `chown` theo UID sẽ chạy process, hoặc dùng một group chung với mode `640`, hay `660` nếu cần ghi. Đừng thêm `user: root` vào Compose chỉ để hết lỗi.

Systemd cũng có cái bẫy tương tự. Bạn SSH bằng user của mình, tạo file trong `/var/lib/app`, rồi service chạy dưới `User=app` và không ghi được vào file bạn vừa tạo. Hãy nhìn `User=` trong unit, đừng nhìn `whoami` của phiên SSH.

Với secret và SSH key thì ngược lại. `sshd` bỏ qua key nếu mode quá lỏng, nên `.ssh` cần `700` và private key cần `600`, như bài SSH đã nói. Nhưng đừng siết `600` lên thư mục log rồi ngạc nhiên khi app không ghi thêm được dòng nào.

Thứ tự điều tra, và lý do của từng bước:

1. **Xác định UID của process.** Đây là bước đầu vì kernel chỉ quan tâm tới UID này. Nếu bạn đoán sai user, mọi lần `chmod` sau đó đều sửa sai chỗ.
2. **Kiểm tra `x` trên mọi thư mục cha** bằng `namei -l` hoặc `ls -ld` từng bậc. Một thư mục cha thiếu `x` là đủ để chặn, dù file bên trong mở rộng tới đâu.
3. **Kiểm tra owner và mode của file hoặc thư mục đích.** Chỉ khi hai bước trên đã ổn, bạn mới sửa ở đây.
4. **Sửa bằng `chown` hoặc group, không bằng `777`.**

Bạn xác nhận đã sửa xong khi app ghi hoặc đọc được bằng đúng UID và đúng image sẽ deploy, không phải bằng shell root. Nếu bản sửa mở quyền quá rộng hoặc làm hỏng service khác, hãy trả owner và mode về giá trị bạn đã ghi lại trước khi sửa.

## Lệnh và code mẫu

Lab trong thư mục tạm, không đụng vào `/etc`:

```bash
mkdir -p ~/devops-lab/perm && cd ~/devops-lab/perm
printf 'ok\n' > note.txt
chmod 600 note.txt
ls -l note.txt
sudo -u nobody cat note.txt
```

`sudo -u nobody` báo permission denied là đúng, vì nhóm other không có bit đọc. Chạy `chmod 644 note.txt` rồi `sudo -u nobody cat note.txt` lần nữa, lệnh sẽ in ra `ok`. Xóa thư mục lab khi làm xong.

Xem user thật sự đang chạy trong container:

```bash
docker run --rm node:24 id
```

Ghi lại UID. Nếu process không chạy bằng root, volume cần có owner là đúng UID đó. Máy không có Docker thì bỏ qua lệnh này, chỉ cần nhớ ý nghĩa của `id`.

## Thực hành

1. Chạy lab `note.txt`. Xác nhận `nobody` bị từ chối khi mode là `600` và đọc được khi mode là `644`.
2. Thư mục upload có mode `755`, thuộc root, trong khi app chạy với UID 1000 cần tạo file. Viết ra quyền còn thiếu: owner hoặc group phải cho UID đó quyền `w` và `x` trên thư mục.
3. Viết một câu từ chối: không dùng `chmod 777` và không đổi service về chạy bằng root.

## Kết quả mong đợi

Bạn chỉ ra được UID nào bị từ chối và bit nào đang thiếu. Thư mục tạm của lab đã được xóa.

## Checklist

- [ ] Đọc UID của process, không chỉ UID của phiên SSH.
- [ ] Kiểm tra `x` trên các thư mục cha trước khi nới quyền file bên trong.
- [ ] Volume có owner khớp với `USER` trong image.
- [ ] Key và file secret không mở rộng hơn `600`.
- [ ] Không `chmod 777` trên máy có nhiều user hoặc trên volume chứa dữ liệu.

## Lỗi thường gặp

**`chmod -R 777 /var/lib/app`.** Lỗi ghi biến mất, nhưng mọi account trên máy đọc được file database hoặc file upload. Hãy đổi owner sang user của service, và đặt `750`, hoặc `770` nếu một group cần ghi.

**Sửa quyền trên host nhưng process trong container vẫn lỗi.** Image cũ chạy bằng root nên không lộ lỗi; bản mới có `USER node` thì vỡ. Hãy thử bằng đúng image sẽ deploy, đừng thử bằng shell root.

**Chỉ sửa file, quên thư mục.** File `666` vẫn không mở được nếu không đi qua được path dẫn tới nó. Trên Linux, `namei -l /đường/dẫn` in ra mode của từng bậc.

## Nguồn

1. [chmod(1)](https://man7.org/linux/man-pages/man1/chmod.1.html) — mode user, group, other.
2. [Dockerfile USER](https://docs.docker.com/reference/dockerfile/#user) — account lúc build và lúc chạy container.

## Tiếp theo

Hết nhóm tình huống hiện tại.
