2 giờ sáng, Nginx trả lỗi 403 dù đã chmod 777
PagerDuty rung chuông lúc 2 giờ sáng. Đội trực vừa đẩy hotfix lên cụm AlmaLinux 9 thì Nginx lăn đùng ra trả mã 403 Forbidden trên toàn bộ file tĩnh (CSS, JS, ảnh PNG). Chạy lệnh ls -l kiểm tra quyền POSIX, owner vẫn chuẩn nginx:nginx. Thử liều chmod -R 777 vào thư mục web root, trình duyệt vẫn trơ trơ trả về 403.
Nhiều anh em mới trực production sẽ tiện tay gõ ngay setenforce 0. Web chạy lại thật đấy. Nhưng làm vậy là bạn vừa mở toang cánh cửa bảo mật của kernel.
3 hướng xử lý thường gặp khi đụng độ SELinux
Khi SELinux chặn process truy cập file hoặc port mạng trên RHEL/AlmaLinux, sysadmin thường đứng trước 3 lựa chọn:
- Hướng 1: Tắt hẳn SELinux (Permissive hoặc Disabled): Gõ
setenforce 0để chuyển sangpermissive, hoặc sửa/etc/selinux/configthànhSELINUX=disabledrồi reboot. Cách này hạ toàn bộ khiên phòng thủ của máy chủ. - Hướng 2: Chuẩn hóa Security Context và cấu hình Boolean: Giữ nguyên chế độ
enforcing. Đọc đúng log AVC Denied, gắn lại nhãn (label) cho file quasemanage fcontextvà bật đúng cờ Boolean cần thiết. - Hướng 3: Sinh custom policy bằng audit2allow: Gom log bị từ chối rồi dùng
audit2allow -Mđể compile thành module nhị phân (.pp) nạp thẳng vào kernel.
So sánh rủi ro và đánh đổi
Cách 1: Disable hoặc Permissive
- Ưu điểm: Nhanh gọn. Xong việc sau đúng 5 giây, trang web sống lại ngay lập tức.
- Nhược điểm: Vô hiệu hóa hoàn toàn cơ chế Mandatory Access Control (MAC). Giả sử web app dính lỗi RCE (Remote Code Execution), kẻ tấn công chiếm quyền process
nginxcó thể đọc thẳng/etc/shadowhoặc SSH key của các user khác trên server.
Cách 2: Gắn nhãn Context và bật Boolean chuẩn chỉ
- Ưu điểm: Triệt để và an toàn. Hệ thống tuân thủ nghiêm ngặt nguyên tắc Least Privilege (đặc quyền tối thiểu), giữ nguyên khả năng cô lập tiến trình của Linux.
- Nhược điểm: Tốn thêm 3 đến 5 phút đọc log và tra cứu cờ lệnh.
Cách 3: Tự động tạo Module bằng audit2allow
- Ưu điểm: Hữu ích khi chạy các phần mềm chuyên biệt không có sẵn targeted policy từ Red Hat.
- Nhược điểm: Dễ tạo lỗ hổng bảo mật. Nếu app đang bị khai thác lúc bạn chạy lệnh, công cụ sẽ hợp thức hóa luôn hành vi tấn công vào kernel policy.
Lựa chọn khuyến nghị: Giữ vững Enforcing trên Production
Trên môi trường production, giữ Enforcing là nguyên tắc sống còn. Việc cấu hình đúng SELinux chỉ tốn vài phút nếu bạn nắm rõ quy trình đọc log và hiểu bản chất nhãn bảo mật.
Quy trình 5 bước chẩn đoán và xử lý triệt để
1. Đọc vết AVC Denied trong audit.log
Mỗi khi chặn một thao tác, kernel sẽ ghi một bản ghi AVC (Access Vector Cache) vào /var/log/audit/audit.log. Lọc nhanh các sự kiện vừa phát sinh bằng lệnh:
grep "type=AVC" /var/log/audit/audit.log | tail -n 5
Nếu máy chủ dùng systemd-journald, bạn có thể lọc trực tiếp:
journalctl -t setroubleshoot --since "10 minutes ago"
Một dòng log từ chối điển hình sẽ có dạng:
type=AVC msg=audit(1697801060.450:892): avc: denied { read } for pid=4812 comm="nginx" name="index.html" dev="sda3" ino=1358249 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0
Nhìn vào log trên, process nginx mang nhãn nguồn (scontext) là httpd_t. Nó đang cố đọc file index.html mang nhãn đích (tcontext) là user_home_t. Lỗi này thường do bạn dùng lệnh mv chuyển file từ thư mục /home/deploy/ sang /var/www/html, khiến file bị giữ nguyên nhãn cũ thay vì kế thừa nhãn của thư mục web.
2. Kiểm tra Security Context của file
Mỗi tiến trình, file và port đều có nhãn định danh dạng user:role:type:level. Trong đó, type quyết định quyền truy cập thông qua cơ chế Type Enforcement.
Thêm cờ -Z vào lệnh ls để xem nhãn:
ls -lZ /var/www/html
Kết quả hiển thị:
-rw-r--r--. 1 nginx nginx unconfined_u:object_r:user_home_t:s0 index.html
Quy tắc mặc định chỉ cho phép Nginx đọc file có type là httpd_sys_content_t hoặc ghi vào file có type httpd_sys_rw_content_t. Nhãn user_home_t ở trên bị chặn là hoàn toàn chính xác.
3. Đặt lại Security Context vĩnh viễn
Đừng dùng chcon. Lệnh này chỉ đổi tạm thời và sẽ bị xóa sạch khi hệ thống chạy relabel hoặc restore backup. Bạn hãy dùng semanage để ghi rule vào database, sau đó chạy restorecon:
# 1. Khai báo rule vào cơ sở dữ liệu SELinux
sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/html(/.*)?"
# 2. Áp nhãn đệ quy cho toàn bộ thư mục
sudo restorecon -Rv /var/www/html
Chạy lại ls -lZ /var/www/html. Type lúc này đã chuyển về httpd_sys_content_t. Tải lại trang web, lỗi 403 biến mất ngay lập tức mà không cần reload Nginx.
4. Cấu hình Boolean khi Nginx làm Reverse Proxy
Trường hợp Nginx đóng vai trò reverse proxy đến backend Node.js (cổng 3000) hoặc PHP-FPM socket, bạn có thể sẽ gặp tiếp lỗi 502 Bad Gateway. SELinux mặc định cấm web server mở kết nối mạng ra ngoài.
Kiểm tra trạng thái các cờ Boolean liên quan đến web server:
getsebool -a | grep httpd
Bật cờ cho phép kết nối mạng và thêm tham số -P để lưu cấu hình qua các lần reboot:
# Cho phép Nginx proxy request tới backend (Node.js, Tomcat, Gunicorn)
sudo setsebool -P httpd_can_network_connect 1
# Cho phép Nginx kết nối trực tiếp đến database server từ xa (nếu có)
sudo setsebool -P httpd_can_network_connect_db 1
5. Tận dụng sealert để lấy gợi ý fix lỗi tự động
Gặp những ca lỗi phức tạp khó dịch log, bạn hãy cài thêm bộ công cụ chẩn đoán của Red Hat:
sudo dnf install -y setroubleshoot-server
sudo sealert -a /var/log/audit/audit.log
Công cụ sẽ quét toàn bộ khối denied, phân tích nguyên nhân bằng tiếng Anh rõ ràng và in ra chính xác câu lệnh semanage hoặc setsebool cần thiết để bạn copy-paste xử lý sự cố.

