SSD Nodes Learn 🎉 VPS dari $5.50/bln
Panduan Matt ConnorOleh Matt Connor · Dikemas kini 2026-08-13

Cara Selesaikan Ralat 403 Nginx Akibat SELinux

Nginx memulangkan ralat 403 walaupun keizinan fail betul. Ketahui cara menyemak log audit, membetulkan label dengan semanage dan restorecon tanpa mematikan SELinux anda.

Mengapa nginx memulangkan ralat 403 pada fail yang mempunyai keizinan yang betul

Nginx memulangkan ralat 403 pada fail yang mempunyai bit keizinan yang betul hampir selalu disebabkan oleh SELinux (security-enhanced Linux) yang menghalang bacaan tersebut. SELinux menyemak set peraturan kedua selepas keizinan biasa diluluskan, dan pelayan web hanya dibenarkan membaca fail yang membawa label kandungan web. Fail anda membawa label yang berbeza, jadi operasi buka gagal dan nginx tidak mempunyai apa-apa untuk dihantar.

Lihat label tersebut, bukan sekadar modnya:

ls -ldZ /data/www /data/www/index.html
drwxr-xr-x. root root unconfined_u:object_r:default_t:s0 /data/www
-rw-r--r--. root root unconfined_u:object_r:default_t:s0 /data/www/index.html

Titik yang dicetak selepas drwxr-xr-x bermaksud fail tersebut membawa label SELinux. default_t ialah label yang diperoleh sesuatu laluan apabila polisi tidak mengenalinya, dan tiada apa-apa dalam peraturan pelayan web yang membenarkan pembacaan jenis tersebut. Log ralat menunjukkan ralat Unix biasa, itulah sebabnya ia kelihatan seperti pepijat keizinan:

2026/08/11 09:14:02 [error] 1183#1183: *1 open() "/data/www/index.html" failed (13: Permission denied), client: 203.0.113.9, server: _, request: "GET / HTTP/1.1"

Kernel memulangkan 13: Permission denied untuk kedua-dua jenis penolakan, iaitu yang biasa dan yang disebabkan oleh SELinux. Jadi, tugas pertama adalah untuk mengetahui lapisan mana yang memberikan penolakan tersebut. Jangan mulakan dengan setenforce 0.

Bahagian model yang anda perlukan

SELinux ialah kawalan akses mandatori, biasanya ditulis sebagai MAC. Setiap proses berjalan dalam domain, seperti httpd_t untuk pelayan web. Setiap fail dan setiap port rangkaian membawa jenis, seperti httpd_sys_content_t. Polisi tersebut merupakan senarai kombinasi domain, jenis dan tindakan yang dibenarkan, dan apa-apa yang tiada dalam senarai itu akan dinafikan. Ia berjalan selepas semakan Unix klasik, jadi bit kebenaran dalam drwxr-xr-x masih perlu membenarkan akses tersebut terlebih dahulu. Kedua-dua lapisan mesti memberikan kebenaran.

Konteks penuh mempunyai empat medan yang dipisahkan oleh kolon, seperti system_u:system_r:httpd_t:s0: pengguna SELinux, peranan, jenis dan tahap. Pada pelayan, anda akan menghabiskan hampir semua masa anda pada medan ketiga, iaitu jenis. Dua arahan menunjukkan nilai semasa:

ps -eZ | grep nginx
id -Z

Pekerja nginx menunjukkan konteks yang berakhir dengan httpd_t. Shell log masuk anda menunjukkan unconfined_u:unconfined_r:unconfined_t:s0, kerana polisi targeted lalai mengehadkan servis dan membiarkan pengguna interaktif tanpa sekatan. Perkara ini penting untuk diketahui, kerana SELinux tidak menggantikan menjalankan servis di bawah pengguna dengan keistimewaan minimum. Ia mengehadkan perkara yang boleh dicapai oleh sesuatu servis selepas seseorang berjaya mencerobohnya.

Tiga mod, dan imej yang mempunyai SELinux

sestatus
getenforce

Enforcing menyekat dan merekodkan aktiviti. Permissive membenarkan segala-galanya dan merekodkan apa yang sepatutnya disekat. Disabled tidak memuatkan sebarang polisi langsung. getenforce memaparkan mod semasa. sestatus juga memaparkan mod daripada /etc/selinux/config, iaitu mod yang akan kembali selepas but semula.

Rocky Linux, AlmaLinux, Fedora dan RHEL menyertakan SELinux dalam mod enforcing dengan polisi targeted. Ubuntu dan Debian sebaliknya menggunakan AppArmor, yang melakukan tugas yang sama dengan mekanisme berbeza (bahagian terakhir membincangkannya). Oleh itu, aplikasi yang sama boleh dipasang dengan lancar pada salah satu pelayan anda tetapi memberikan ralat 403 pada pelayan yang lain.

Pasang alatan sebelum anda memerlukannya

sudo dnf install -y policycoreutils-python-utils setroubleshoot-server

semanage: command not found pada imej minimum bermakna policycoreutils-python-utils tiada: pakej tersebut mengandungi semanage dan audit2allow. setroubleshoot-server menambah sealert dan menulis ringkasan bahasa mudah bagi setiap penafian ke dalam journal. Pasang kedua-duanya pada pelayan baharu, kerana saat anda memerlukannya adalah saat sesuatu perkara sudah pun rosak.

Cara membaca penafian SELinux dalam log audit

Setiap penafian direkodkan oleh daemon audit sebagai mesej AVC (access vector cache):

sudo ausearch -m AVC,USER_AVC,SELINUX_ERR -ts recent
type=AVC msg=audit(1754896442.881:412): avc:  denied  { read } for  pid=1183 comm="nginx" name="index.html" dev="vda1" ino=17203 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=file permissive=0

Empat medan membawa keseluruhan cerita. comm ialah program yang disekat. scontext ialah konteks sumber, iaitu domain tempat proses tersebut dijalankan. tcontext ialah konteks sasaran, iaitu label pada objek yang cuba dicapai. tclass ialah jenis objek, dalam kes ini adalah fail. Jika dibaca bersama: proses dalam httpd_t cuba membaca fail yang dilabelkan sebagai default_t, dan permissive=0 menyatakan permintaan tersebut benar-benar disekat dan bukannya sekadar direkodkan.

Jika ausearch tidak memaparkan apa-apa, daemon audit mungkin tidak berjalan. Penafian kemudiannya akan masuk ke dalam kernel ring buffer:

sudo journalctl -k | grep -i avc

Sekarang, tukarkan rekod tersebut menjadi ayat yang mudah difahami:

sudo ausearch -m AVC -ts recent | audit2why
sudo journalctl -t setroubleshoot --since today
sudo sealert -a /var/log/audit/audit.log

audit2why membaca rekod yang sama dan menamakan punca yang dikenal pastinya: boolean yang dimatikan, label yang tidak sepadan dengan polisi, atau ketiadaan peraturan langsung. sealert menyemak keseluruhan log dan mencetak cadangan arahan bagi setiap penafian. Anggap cadangan tersebut sebagai petunjuk sahaja. Perkataan yang digunakan berubah antara keluaran, dan sealert kadangkala mencadangkan modul polisi tersuai sedangkan pembetulan label satu baris adalah jawapan yang betul.

Satu lagi perkara yang perlu diketahui. Polisi mengandungi peraturan dontaudit yang menyembunyikan penafian yang dianggap tidak berbahaya, jadi sesuatu program boleh berkelakuan tidak sepatutnya sementara log kekal kosong. Nyahsembunyikan peraturan tersebut sepanjang tempoh satu ujian:

sudo semodule -DB
# reproduce the problem, then read the log again
sudo semodule -B

Membaiki laluan yang tersalah label dengan semanage fcontext dan restorecon

Dua arahan diperlukan, dan susunannya adalah penting. semanage fcontext -a merekodkan apakah label yang sepatutnya bagi sesuatu laluan. restorecon menggunakan tetapan lalai yang direkodkan itu pada fail di dalam cakera.

sudo semanage fcontext -a -t httpd_sys_content_t "/data/www(/.*)?"
sudo restorecon -Rv /data/www
ls -ldZ /data/www/index.html

Laluan tersebut merupakan ungkapan nalar (regular expression). (/.*)? merangkumi direktori itu sendiri dan segala kandungan di bawahnya, iaitu keperluan bagi root dokumen. Lihat perubahan yang akan berlaku sebelum melaksanakannya: sudo restorecon -Rvn /data/www mencetak pelabelan semula yang dirancang, kerana -n bermaksud tiada tindakan diambil. Selepas restorecon yang sebenar, label akan terbaca sebagai httpd_sys_content_t dan ralat 403 akan hilang tanpa perlu memulakan semula servis.

Gunakan chcon hanya sebagai ujian. chcon -t httpd_sys_content_t index.html menetapkan label secara terus, dan restorecon yang seterusnya, kemas kini pakej, atau pelabelan semula penuh akan menetapkan semula label tersebut, kerana polisi masih menyatakan laluan itu sepatutnya mempunyai label yang lain. semanage fcontext ialah versi yang kekal. Senaraikan apa yang telah anda rekodkan dengan sudo semanage fcontext -l | grep '^/data'.

Kandungan yang perlu ditulis oleh servis memerlukan jenis (type) yang berbeza. Gunakan httpd_sys_rw_content_t untuk direktori muat naik atau cache, dan hadkan penggunaannya pada laluan tersebut sahaja: tapak web baca-sahaja di bawah jenis yang boleh ditulis memberikan akses yang lebih daripada yang diperlukan oleh aplikasi.

Mengapakah label tersebut salah sejak awal? Hampir selalu disebabkan oleh cara fail tersebut sampai ke destinasi. mv mengekalkan label sedia ada sesuatu fail, jadi tapak web yang dipindahkan dari /root akan sampai dengan label admin_home_t dan kekal sedemikian. cp biasa memberikan fail baharu label lalai bagi direktori destinasi, yang biasanya merupakan apa yang anda mahukan, manakala cp -a dan rsync -X menyalin label sumber bersama-sama dengan fail tersebut. git clone ke dalam direktori peringkat atas yang baharu menghasilkan default_t. Apabila sesuatu halaman dimuatkan dengan baik dari /usr/share/nginx/html tetapi gagal dari direktori anda sendiri, inilah puncanya.

Membaiki kelas kelakuan dengan boolean

Sesetengah kegagalan bukanlah masalah label. Reverse proxy pada pelayan Rocky atau AlmaLinux yang baharu akan memulangkan ralat 502, dan log ralat menyatakan:

2026/08/11 10:02:55 [crit] 1183#1183: *3 connect() to 127.0.0.1:3000 failed (13: Permission denied) while connecting to upstream

Upstream anda berfungsi dengan baik. Domain httpd_t tidak dibenarkan untuk membuka sambungan rangkaian keluar secara lalai, jadi panggilan connect() ditolak sebelum ia sampai ke antara muka loopback. Satu suis mengawal keseluruhan kelakuan tersebut:

getsebool -a | grep httpd_can_network
sudo setsebool -P httpd_can_network_connect on

-P ialah flag yang penting: ia menulis nilai tersebut ke cakera. Tanpa -P, perubahan akan hilang pada but semula seterusnya, yang menyebabkan servis berfungsi sehingga mesin dimulakan semula. Sahkan dengan semanage boolean -l | grep httpd_can_network_connect, yang mencetak nilai semasa yang sedang berjalan di sebelah nilai yang disimpan.

Pilih boolean berbanding peraturan yang ditulis secara manual apabila ia tersedia. Boolean disertakan bersama polisi pengedaran, jadi ia diselenggara, didokumentasikan, dan mudah dicari oleh orang seterusnya. getsebool -a menyenaraikan setiap satu daripadanya pada sistem.

Membenarkan servis mendengar pada port bukan standard

Port juga mempunyai label. Alihkan Nginx ke 8081 dan ia enggan bermula:

nginx: [emerg] bind() to 0.0.0.0:8081 failed (13: Permission denied)

httpd_t hanya boleh mengikat port yang dilabelkan sebagai http_port_t, dan 8081 bukan salah satu daripadanya. Tambahkannya:

sudo semanage port -l | grep -w http_port_t
sudo semanage port -a -t http_port_t -p tcp 8081

Semak senarai itu terlebih dahulu. Beberapa port tinggi sudah dibenarkan, termasuk 8008 dan 8443, dan menambah port yang sama dua kali akan gagal dengan ValueError: Port tcp/8081 already defined. Jika port tersebut sudah tergolong dalam jenis yang berbeza, ubah ia dengan semanage port -m -t http_port_t -p tcp 8081 dan bukannya menambahnya.

Perintah yang sama juga menjadikan port SSH yang telah dialihkan berfungsi. Bind to port 2222 on 0.0.0.0 failed: Permission denied dalam journalctl -u sshd bermaksud 2222 tiada dalam ssh_port_t, jadi jalankan sudo semanage port -a -t ssh_port_t -p tcp 2222 sebelum anda memulakan semula daemon dan menutup sesi anda. Itu adalah langkah yang sering dilangkau oleh pengguna apabila mereka mengikuti panduan umum untuk mengeraskan SSH pada VPS dalam imej keluarga Red Hat. SELinux bukanlah firewall, jadi port tersebut masih perlu dibuka: sudo firewall-cmd --permanent --add-port=8081/tcp && sudo firewall-cmd --reload di sini, atau ufw pada imej Debian atau Ubuntu.

Apabila tiada boolean dan tiada label untuk diubah

Situasi ini jarang berlaku pada pelayan biasa, dan di sinilah pengguna sering menyebabkan kerosakan. audit2allow boleh membina modul polisi daripada penafian (denials) dalam log:

sudo ausearch -m AVC -ts recent -c nginx | audit2allow -M nginx_local
cat nginx_local.te
sudo semodule -i nginx_local.pp

Baca nginx_local.te sebelum anda memasangnya. Dua tabiat memastikan proses ini selamat. Tapis input kepada satu program yang anda sedang baiki dengan -c, kerana menyalurkan (pipe) penafian yang tidak berkaitan selama seminggu ke dalam audit2allow akan memberikan kebenaran kepada kesemuanya sekali gus. Jangan sekali-kali memasang modul yang dibina daripada penafian yang anda tidak dapat jelaskan: peraturan yang membenarkan httpd_t membaca setiap fail pada mesin tersebut mudah dijana tetapi sukar dikesan selepas beberapa bulan. Buang modul dengan sudo semodule -r nginx_local.

Permissive ialah mod diagnostik, bukan penyelesaian

sudo setenforce 0
# reproduce the problem once, all the way through
sudo ausearch -m AVC -ts recent
sudo setenforce 1

Mod permissive membenarkan akses dan merekodkannya ke dalam log. Nilai sebenar mod ini ialah kelengkapan. Di bawah mod enforcing, servis akan berhenti pada penafian pertama, jadi anda membaiki satu isu, memulakan semula, dan kemudian berdepan dengan isu kedua. Di bawah mod permissive, pelaksanaan diteruskan dan log mengumpul setiap penafian dalam satu laluan, kemudian anda menukar semula mod dan membaiki kesemuanya serentak.

setenforce tidak mengubah /etc/selinux/config, jadi but semula akan mengembalikan pelayan kepada mod enforcing. Ini merupakan jaring keselamatan, dan ia juga sebab mengapa "penyelesaian" yang terdiri daripada setenforce 0 muncul semula pada saat yang paling tidak diingini. Jika satu servis memerlukan ruang semasa anda mengusahakannya, tandakan domain tersebut sahaja dan bukannya keseluruhan mesin: sudo semanage permissive -a httpd_t membiarkan segala-galanya dalam mod enforcing, dan sudo semanage permissive -d httpd_t membatalkan tindakan tersebut.

Mengapa menyahdayakan SELinux lebih merugikan berbanding membetulkan label

Menetapkan SELINUX=disabled dalam /etc/selinux/config menukarkan pembetulan label satu baris dengan pelayan yang lemah secara kekal. Perbezaan ini akan ketara pada hari aplikasi web diceroboh. Di bawah mod enforcing, kod penyerang berjalan dalam httpd_t, jadi ia mungkin boleh membaca kandungan web, manakala membaca /etc/shadow atau menulis unit systemd akan ditolak oleh polisi tidak kira apa yang dibenarkan oleh pengguna Unix. Tanpa polisi yang dimuatkan, kod yang sama mendapat segala akses yang dimiliki oleh akaun servis tersebut.

Menyahdayakan SELinux juga membawa kos yang perlu dibayar kemudian. Apabila tiada polisi dimuatkan, fail baharu dicipta tanpa label, menyebabkan sistem fail tidak selari dengan polisi. Menghidupkan semula SELinux kemudiannya memerlukan pelabelan semula sepenuhnya, atau sekumpulan servis akan gagal serentak:

sudo fixfiles -F onboot
sudo reboot

Perintah tersebut menulis /.autorelabel dan melabel semula setiap sistem fail semasa but seterusnya. Pada cakera yang besar, proses ini mengambil masa yang lama dan konsol kelihatan terhenti, jadi mulakan proses ini apabila anda mempunyai masa. Pada Rocky Linux dan AlmaLinux 9, fail konfigurasi tidak lagi mematikan bahagian kernel secara sendirian, dan cara yang didokumentasikan untuk menyahdayakan SELinux sepenuhnya adalah melalui argumen kernel (sudo grubby --update-kernel ALL --args selinux=0). Mengetahui perintah tersebut membantu apabila anda mengambil alih pelayan orang lain. Ia bukanlah penyelesaian untuk ralat 403.

Menambah satu lagi label pada kontena

Pada hos keluarga Red Hat, proses kontena berjalan dalam container_t dan hanya boleh membaca fail yang dilabelkan sebagai container_file_t. Bind mount daripada hos akan gagal dengan Permission denied di dalam kontena, walaupun ls -l pada hos kelihatan sempurna. Suffix :Z memberitahu runtime untuk melabel semula mount tersebut:

docker run -d -v /data/appdata:/var/lib/app:Z myimage

:Z melabelkan direktori tersebut khusus untuk kontena ini sahaja. :z melabelkannya untuk perkongsian antara kontena. Halakan :Z kepada direktori yang digunakan oleh servis lain dan ia akan melabel semula direktori tersebut secara rekursif, yang akan menyebabkan servis tersebut tergendala, jadi berikan laluan (path) sendiri kepada setiap kontena. Segala aspek lain mengenai persediaan ini adalah sama dengan imej lain, yang telah dibincangkan dalam menjalankan Docker pada VPS.

Ubuntu dan Debian menyediakan AppArmor

Tugas yang sama, reka bentuk yang berbeza. AppArmor mengehadkan program berdasarkan laluan ke fail boleh laksananya, menggunakan profil di bawah /etc/apparmor.d/, bukannya melabel fail pada cakera. Tiada apa-apa yang perlu dilabel semula dan tiada restorecon. Mulakan di sini:

sudo aa-status
sudo journalctl -k | grep -i apparmor

Satu penolakan akan muncul sebagai apparmor="DENIED" operation="open" profile="/usr/sbin/nginx" name="/data/www/index.html" requested_mask="r". Aliran kerja adalah sama: baca penolakan tersebut, cari profilnya, kemudian ubah peraturan. sudo apt install apparmor-utils memberikan anda aa-complain (mod permisif untuk satu profil) dan aa-enforce untuk mengembalikannya semula. Ubuntu mengehadkan set perkhidmatan pakej yang terpilih dan membiarkan yang lain tidak dihadkan, jadi baca aa-status untuk melihat perkara yang benar-benar aktif dan bukannya membuat andaian.

Satu tabiat yang boleh diguna pakai pada kedua-dua sistem. Apabila sesuatu perkhidmatan melaporkan Permission denied pada sesuatu yang kelihatan betul, baca log keselamatan sebelum anda menyentuh kebenaran (permissions). Masalah jarang berpunca daripada bit kebenaran sebanyak dua kali.

FAQ

Mengapakah nginx memulangkan ralat 403 walaupun keizinan fail adalah betul?

Ini kerana SELinux menafikan akses baca, bukan kerana bit keizinan. Pelayan web berjalan dalam domain httpd_t dan hanya dibenarkan membaca fail yang dilabel untuk kandungan web, jadi fail yang dilabel sebagai default_t atau admin_home_t akan ditolak dan nginx tidak mempunyai kandungan untuk dihidangkan. Sahkan perkara ini dengan sudo ausearch -m AVC -ts recent, yang menunjukkan scontext berakhir dengan httpd_t dan tcontext memegang jenis yang salah. Kemudian, rekod label yang betul dan gunakannya: sudo semanage fcontext -a -t httpd_sys_content_t "/data/www(/.*)?" diikuti dengan sudo restorecon -Rv /data/www.

Adakah selamat untuk menjalankan setenforce 0 bagi membolehkan sesuatu servis berfungsi?

setenforce 0 ialah langkah diagnostik, bukan penyelesaian. Gunakannya untuk menghasilkan semula masalah sekali sahaja supaya log merekodkan setiap penafian dalam satu laluan, baca log tersebut dengan sudo ausearch -m AVC -ts recent, kemudian jalankan sudo setenforce 1 dan baiki punca masalahnya. Pelayan yang dibiarkan dalam mod permissive akan merekodkan setiap penafian tetapi tidak menyekat apa-apa, jadi anda akan mendapat banyak gangguan log dan kehilangan perlindungan. Jika satu servis memerlukan ruang semasa anda bekerja, jalankan sudo semanage permissive -a httpd_t supaya bahagian lain mesin kekal dalam mod enforcing.

Bagaimanakah cara menjalankan servis pada port bukan standard dengan SELinux dalam mod enforcing?

Tambahkan port tersebut kepada jenis yang dibenarkan untuk diikat oleh servis berkenaan. Untuk pelayan web pada 8081: sudo semanage port -a -t http_port_t -p tcp 8081. Untuk SSH pada 2222: sudo semanage port -a -t ssh_port_t -p tcp 2222. Semak senarai semasa terlebih dahulu dengan sudo semanage port -l | grep -w http_port_t, kerana port yang sudah tersenarai akan gagal dengan ralat ValueError: Port tcp/8081 already defined. Tanpa langkah ini, daemon akan keluar semasa permulaan dengan ralat bind() ... Permission denied walaupun tiada proses lain yang menggunakan port tersebut.

Adakah Ubuntu mempunyai SELinux?

Tidak. Ubuntu dan Debian menggunakan AppArmor, yang menguatkuasakan profil berdasarkan laluan boleh laksana (executable) dan bukannya label pada fail. Semak perkara ini dengan sudo aa-status dan cari baris apparmor="DENIED" dalam sudo journalctl -k. Ubuntu mengehadkan set servis pakej yang terpilih, jadi banyak program berjalan tanpa sekatan secara lalai. Rocky Linux dan AlmaLinux adalah sistem yang menggunakan SELinux secara lalai, bersama-sama dengan Fedora dan RHEL.