Ви розгорнули зміни. Ви зробили reload. Ви натиснули оновити. Замість сайту бачите чітку, але марну відповіді 403 або 404. Вчора працювало. Сьогодні Nginx поводиться так, ніби ніколи не бачив ваших файлів.
На Debian 13 найшвидший шлях вирішення — не «дивитися в конфіг поки очі не запруться». Потрібно довести, чи маєте ви справу зі стіною прав, невідповідністю маршрутизації/конфігурації, чи це механізм безпеки (AppArmor, symlink-політики, chroot-подібні налаштування), який виконує свою роботу. Це практичний робочий процес, який я застосовую на продуктивних серверах: мінімум здогадок, максимум сигналу.
Швидкий план діагностики
Це порядок триажу, який працює в стресі. Він орієнтований на відповідь на єдине питання першочергово: «Чи дозволено Nginx читати файл, який він намагається віддати, і чи взагалі він намагається віддати той файл, який я думаю?»
По-перше: подивіться error log для точного запиту
Не починайте з редагування конфігурації. Почніть з того, щоб подивитися, що Nginx каже в момент відмови. Для 403/404 журнал помилок зазвичай містить правду в одному рядку: розв’язаний шлях, причину помилки і іноді який server block обробив запит.
По-друге: підтвердьте, який server block обробляє запит
Половина інцидентів «раптом 404» — це насправді «ви потрапили не на той віртуальний хост». Зміни Debian, оновлення сертифікатів, додані віртуальні хости або новий сайт за замовчуванням можуть тихо перехопити трафік.
По-третє: перевірте розв’язання шляху (root/alias/try_files)
403/404 часто зводиться до того, що URI, який ви запросили, не відповідає тому файлу, який ви вважаєте. Nginx має дуже буквальну модель відображення. Якщо ви помилилися хоча б однією косою рискою, ви помилилися.
По-четверте: протестуйте файлові права на всьому шляху
Nginx потрібен не лише доступ на читання до файлу; він потребує права виконання («traverse») на кожному каталозі в шляху. Один вузький каталог посеред шляху дасть «permission denied», навіть якщо сам файл читається для всіх.
По-п’яте: перевірте політику LSM (на Debian часто AppArmor)
На Debian профілі AppArmor можуть забороняти читання так, що це виглядає як звичайні Unix-права або «файл не знайдено». Ваші логи підкажуть, якщо ви їх читаєте.
По-шосте: переконайтеся, що ви не перезавантажили конфіг, який не відповідає процесу
Systemd може показувати «active (running)», тоді як Nginx обслуговує стару конфігурацію, бо reload не вдалося або ви перезавантажили неправильний екземпляр/контейнер/chroot. Швидко перевірте завантажену конфігурацію.
Операційне правило: якщо ви не можете відтворити проблему за допомогою curl -v, одночасно підглядаючи access+error логи, ви дебагуєте чутку.
Миттєва ментальна модель: що насправді означають 403 і 404 в Nginx
Nginx детермінований. Якщо вам здається, що він поводиться хаотично, ви просто не знайшли вхідні дані, які визначають його поведінку.
403 Forbidden: Nginx знайшов «місце», але не віддає вміст
403 зазвичай означає одне з наступного:
- Файл існує, але його не можна прочитати користувачем воркерів Nginx.
- Прохід по директоріям заблокований (немає бітa виконання на батьківському каталозі).
- Індекс директорії заборонений: ви запросили директорію, Nginx не знайшов індекс-файлу і autoindex вимкнено.
- Явні правила заборони в конфігурації (наприклад,
deny all;, allowlist за IP). - Політика щодо симліків:
disable_symlinksабо дивні опції монтування можуть спричинити відмову. - AppArmor-відмова.
404 Not Found: Nginx не зміг відобразити URI на файл (або не хоче показувати)
404 часто означає «неправильний root/alias» або «не той server block», але також може бути усвідомленим замаскуванням: деякі конфіги віддають 404 для заборонених ресурсів, щоб не афішувати їх наявність.
Чому не можна покладатися тільки на код статусу
Nginx може бути налаштований повертати 404 для заборонених ресурсів, або робити rewrite в внутрішнє місце, яке повертає інший код. Тому підхід такий: код статусу — це підказка; логи — це доказ.
Жарт №1: Коли on-call каже «це просто 404», я чую «це просто пожежа, але вона в логах».
Цікаві факти та історичний контекст (корисне, не тривіальне)
- Nginx створювався для передбачуваної конкуренції (event-driven модель), тому некоректно маршрутизований запит може регулярно відпадати в одному й тому ж місці — це зручно для відлагодження, якщо взяти один репрезентативний запит.
- 403 vs 404 має історію безпеки: багато організацій навмисно віддають 404 для захищених шляхів, щоб ускладнити енумерацію кінцевих точок.
- Unix-біт «виконання» для директорій означає «можна проходити», а не «можна виконувати». Директорія може бути читаємою, але не прохідною — це плутає навіть досвідчених розробників.
- Пакетні налаштування Debian віддають перевагу безпеці: стандартна структура сайтів під
/etc/nginx/sites-availableіsites-enabledпокликана зменшити випадкове розкриття, але це також створює інциденти «неправильний vhost» під час змін. - AppArmor став мейнстрім-контролем у багатьох дистрибутивах для забезпечення MAC без операційного навантаження SELinux, і він може тихо блокувати шляхи поза очікуваними коренями вебу.
- Alias vs root — постійна пастка:
aliasповодиться інакше ніжrootу location-блоках, і одна пропущена слеш-риска може перетворити валідний URI на гарантований 404. - «internal» локації Nginx широко використовуються для аутентифікації та обробки помилок; вони можуть змусити браузер бачити 404, тоді як Nginx насправді б’ється з правами десь ще.
- Правила вибору сервера за замовчуванням важливі: якщо жоден
server_nameне співпадає, Nginx обирає дефолт. Додавання нового server block може змінити, хто стає «дефолтом», і спричинити «раптові» 404.
Практичні завдання: команди, що означає вивід і які рішення
Це реальні завдання, які я запускаю в продакшені. Копіюйте/вставляйте. Кожне містить: команду, що шукати, і рішення, яке вона підказує.
Завдання 1: Відтворіть за допомогою curl і захопіть заголовки
cr0x@server:~$ curl -sv -o /dev/null http://example.internal/static/app.css
* Trying 127.0.0.1:80...
* Connected to example.internal (127.0.0.1) port 80 (#0)
> GET /static/app.css HTTP/1.1
> Host: example.internal
> User-Agent: curl/8.6.0
> Accept: */*
< HTTP/1.1 404 Not Found
< Server: nginx/1.26.2
< Date: Mon, 29 Dec 2025 10:12:32 GMT
< Content-Type: text/html
< Content-Length: 153
< Connection: keep-alive
Значення: ви підтвердили, що відповідає Nginx, а не CDN чи апстрім, і маєте точний URI та заголовок Host.
Рішення: збережіть точне значення Host; ви будете використовувати його, щоб ідентифікувати server block.
Завдання 2: Тайл error log під час відтворення
cr0x@server:~$ sudo tail -Fn0 /var/log/nginx/error.log
2025/12/29 10:12:32 [error] 1842#1842: *921 open() "/srv/www/example/static/app.css" failed (2: No such file or directory), client: 127.0.0.1, server: example.internal, request: "GET /static/app.css HTTP/1.1", host: "example.internal"
Значення: Nginx намагався відкрити конкретний шлях. Код помилки (2) — «No such file or directory». Це не права; це відображення шляху або відсутній файл.
Рішення: перевірте, що файл існує за цим точним шляхом, і підтвердьте логіку root/alias для /static/.
Завдання 3: Тайл access log, щоб підтвердити статус, який записав Nginx
cr0x@server:~$ sudo tail -n 3 /var/log/nginx/access.log
127.0.0.1 - - [29/Dec/2025:10:12:32 +0000] "GET /static/app.css HTTP/1.1" 404 153 "-" "curl/8.6.0"
Значення: підтверджує, що це не кеш браузера або змінені коди статусів. Один запит — один код.
Рішення: продовжуйте шукати проблему на рівні Nginx+файлової системи, а не апстріму.
Завдання 4: Перевірте синтаксис конфігурації перед глибшою інспекцією
cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Значення: ви не ганяєтеся за неуспішним reload, який не застосувався.
Рішення: переходьте до «який server block і який root», а не «чи Nginx взагалі парсить».
Завдання 5: Виведіть повну завантажену конфігурацію (що насправді запущено)
cr0x@server:~$ sudo nginx -T 2>/dev/null | sed -n '1,120p'
# configuration file /etc/nginx/nginx.conf:
user www-data;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
events {
worker_connections 768;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Значення: ви бачите include-ди та ключові дефолти, особливо користувача воркерів (www-data за замовчуванням на Debian).
Рішення: підтвердіть, що файл сайту, який ви правили, є в sites-enabled і не заслонений іншим include.
Завдання 6: Ідентифікуйте, який server block відповідає вашому Host
cr0x@server:~$ sudo nginx -T 2>/dev/null | awk '
$1=="server" && $2=="{" {inserver=1; sn=""; ls=""; fn=FILENAME}
inserver && $1=="server_name" {sn=$0}
inserver && $1=="listen" {ls=$0}
inserver && $1=="}" {print ls " | " sn; inserver=0}
'
listen 80; | server_name example.internal;
listen 80 default_server; | server_name _;
Значення: ви бачите, чи ваш хост явно збігається, чи потрапляєте в сервер за замовчуванням.
Рішення: якщо ви потрапляєте на server_name _; або на дефолт несподівано, виправте порядок vhost / default_server і перестаньте тикати в права.
Завдання 7: Підтвердіть, що розв’язаний шлях файлу існує
cr0x@server:~$ sudo ls -la /srv/www/example/static/app.css
ls: cannot access '/srv/www/example/static/app.css': No such file or directory
Значення: файл справді не існує за шляхом, який спробував Nginx.
Рішення: знайдіть правильний web root, виправте root/alias/try_files або виправте деплой, який не доставив ресурс.
Завдання 8: Якщо файл існує, протестуйте доступ на читання як користувач Nginx
cr0x@server:~$ sudo -u www-data head -c 64 /srv/www/example/static/app.css
head: cannot open '/srv/www/example/static/app.css' for reading: Permission denied
Значення: класична проблема з правами. Тепер у вас є доказ, що тест виконано під тим самим користувачем, що і Nginx.
Рішення: виправте власність/права/ACL на ланцюжку директорій. Не робіть «chmod 777» і не чекайте повідомлення про інцидент з безпеки.
Завдання 9: Перевірте біти проходження через директорії по всьому шляху
cr0x@server:~$ namei -l /srv/www/example/static/app.css
f: /srv/www/example/static/app.css
drwxr-xr-x root root /
drwxr-xr-x root root srv
drwx------ root root www
drwxr-xr-x root root example
drwxr-xr-x root root static
-rw-r--r-- root root app.css
Значення: /srv/www має drwx------. Навіть якщо файл читається, www-data не може пройти через цей каталог, тому Nginx упаде.
Рішення: відрегулюйте права каталогів (біт виконання для користувача/групи Nginx) або перемістіть контент у root, призначений для обслуговування.
Завдання 10: Швидко знайдіть AppArmor-відмови
cr0x@server:~$ sudo journalctl -k -g apparmor --since "10 minutes ago"
Dec 29 10:11:58 server kernel: audit: type=1400 audit(1767003118.123:91): apparmor="DENIED" operation="open" profile="nginx" name="/srv/www/example/static/app.css" pid=1842 comm="nginx" requested_mask="r" denied_mask="r" fsuid=33 ouid=0
Значення: політика обов’язкового доступу заблокувала читання. Unix-права можуть бути в порядку, але політика каже «ні».
Рішення: або відкоригуйте профіль, щоб дозволити цей шлях, або розташовуйте файли у дозволених локаціях. Зміна chmod тут не допоможе.
Завдання 11: Підтвердіть користувача майстра/воркерів і процеси
cr0x@server:~$ ps -o user,pid,cmd -C nginx
USER PID CMD
root 1721 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
www-data 1842 nginx: worker process
www-data 1843 nginx: worker process
Значення: воркери працюють як www-data. Це та особа, яка має читати ваш контент.
Рішення: припиніть гадати про «який користувач». Виправте доступ для www-data (або для налаштованого користувача, якщо ви його змінювали).
Завдання 12: Визначте, чи запит переписується кудись ще
cr0x@server:~$ sudo nginx -T 2>/dev/null | grep -R --line-number -E 'try_files|rewrite|return 404|error_page 404|internal' /etc/nginx/sites-enabled
/etc/nginx/sites-enabled/example.conf:27: try_files $uri $uri/ /index.html;
/etc/nginx/sites-enabled/example.conf:41: error_page 403 404 = /errors/notfound.html;
/etc/nginx/sites-enabled/example.conf:42: location = /errors/notfound.html { internal; }
Значення: навіть 403 може бути замапленим на внутрішню 404-сторінку, а try_files може перенаправляти відсутні статичні файли на SPA entrypoint. Ваш браузер може бачити 404, тоді як Nginx робить саме те, що ви йому наказали.
Рішення: вирішіть, чи ця поведінка переписів є навмисною; якщо ні, відкоригуйте try_files або error_page мапінг.
Завдання 13: Перевірте, чи ви потрапляєте на «неправильний» default site
cr0x@server:~$ ls -l /etc/nginx/sites-enabled
total 0
lrwxrwxrwx 1 root root 34 Dec 29 09:48 default -> /etc/nginx/sites-available/default
lrwxrwxrwx 1 root root 34 Dec 29 09:49 example.conf -> /etc/nginx/sites-available/example.conf
Значення: дефолтний сайт Debian увімкнено. Якщо у нього є default_server у директиві listen, він може перехоплювати невідповідні хости і віддавати неправильний контент (або 404).
Рішення: вимкніть дефолтний сайт на проді або зробіть потрібний vhost дефолтом явно.
Завдання 14: Перевірте опції монтування файлової системи
cr0x@server:~$ findmnt -no SOURCE,TARGET,FSTYPE,OPTIONS /srv
/dev/mapper/vg0-srv /srv ext4 rw,relatime
Значення: опції монтування зазвичай не є причиною 403/404, але можуть впливати (наприклад, дивний read-only стан, bind mounts або overlay у контейнерах).
Рішення: якщо бачите несподіваний ro або bind/overlay монтування, підтвердіть шляхи деплою і відповідність mounts у контейнерах.
Завдання 15: Переконайтеся в обробці index при запиті директорії
cr0x@server:~$ curl -svo /dev/null http://example.internal/static/
* Trying 127.0.0.1:80...
> GET /static/ HTTP/1.1
> Host: example.internal
< HTTP/1.1 403 Forbidden
< Server: nginx/1.26.2
Значення: запит директорії може повернути 403, коли індекс-файлу немає й autoindex вимкнено.
Рішення: додайте індекс (наприклад, index.html), увімкніть autoindex (рідко правильно для продакшену) або змініть маршрутизацію, щоб уникнути URI директорій.
Завдання 16: Перевірте, чи деплой не змінив власника файлів несподівано
cr0x@server:~$ sudo stat -c '%U %G %a %n' /srv/www/example /srv/www/example/static /srv/www/example/static/app.css
root root 755 /srv/www/example
root root 750 /srv/www/example/static
deploy deploy 640 /srv/www/example/static/app.css
Значення: файл належить deploy з mode 640. Якщо www-data не в групі deploy, він не зможе його прочитати.
Рішення: випрацюйте стратегію власності/групи (поширено: група www-data і права 644), або встановіть setgid для каталогів, або застосуйте ACL для www-data.
Завдання 17: Перевірте відмови, пов’язані з симлінками
cr0x@server:~$ sudo nginx -T 2>/dev/null | grep -R --line-number 'disable_symlinks' /etc/nginx
/etc/nginx/nginx.conf:63: disable_symlinks if_not_owner from=$document_root;
Значення: якщо ваш контент використовує симлінки (часто при деплоях), Nginx може відмовити в обслуговуванні в залежності від власності.
Рішення: або узгодьте власність, видаліть симлінки для обслуговуваного контенту, або свідомо змініть політику.
Завдання 18: Підтвердіть, що reload дійсно відбувся і не впав тихо
cr0x@server:~$ sudo systemctl reload nginx; sudo systemctl status nginx --no-pager -l
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Mon 2025-12-29 09:40:10 UTC; 33min ago
Docs: man:nginx(8)
Main PID: 1721 (nginx)
Tasks: 5 (limit: 18754)
Memory: 8.4M
CPU: 1.142s
CGroup: /system.slice/nginx.service
├─1721 "nginx: master process /usr/sbin/nginx -g daemon on; master_process on;"
├─1842 "nginx: worker process"
└─1843 "nginx: worker process"
Значення: статус показує, що Nginx запущено, але це не доводить, що reload застосовано. Комбінуйте з nginx -T і таймштампами у логах при внесенні змін.
Рішення: якщо підозрюєте провал reload, перевірте journald на помилки reload і повторіть nginx -t.
Помилки прав, що виглядають як баги конфігурації
Пастка проходження директорій (та, що кусає дорослих)
Ви можете ставити chmod 644 на файл хоч весь день. Якщо будь-який батьківський каталог не має біта виконання для користувача Nginx (або його групи), Nginx не зможе туди дістатися. Результат — 403, і в error log часто буде (13: Permission denied).
На Debian Nginx зазвичай працює як www-data. Отже канонічний тест: чи може www-data прочитати файл? Не «чи може root», бо root може читати все; Nginx — ні.
Зсув власності під час деплоїв
CI/CD, що rsync файли, розпаковує tarball-и або міняє симлінки, може змінити власність і права. Деплой-користувач кладе файли як deploy:deploy з 640, і раптом статичні файли мертві.
Виправляйте це в джерелі: встановіть узгоджену групу для сервованого контенту, забезпечте umask, або застосуйте ACL. «Виправляти права після кожного деплою» — це не стратегія, а повторюваний інцидент.
AppArmor: права, про які ви не знали
Debian часто використовує AppArmor. Якщо ваш профіль Nginx дозволяє /var/www/**, а ви серверите з /srv/www/**, Nginx може логувати «permission denied», навіть якщо Unix-модулі і права в порядку.
Kernel audit логи назвуть профіль і шлях. Це ваш палячий доказ. Якщо ви цього не перевірите, витратите години на «правку chmod» без результату.
Симлінки, власність та «disable_symlinks»
Симлінки популярні для атомарних деплоїв: current -> releases/2025-12-29. Nginx може бути налаштований обмежувати сервінг симлінків, щоб запобігти певним шляховим атакам і несподіванкам із власністю. Це хороший контроль — поки хтось не забуде про нього.
Якщо бачите помилки, пов’язані з симлінками, вирішіть, чи хочете, щоб Nginx взагалі сервив симлінки. Якщо так — зробіть це узгоджено; якщо ні — приберіть симлінки з document root.
Помилки конфігурації, що виглядають як проблеми прав
Неправильний server block (рулетка дефолтного сервера)
Якщо Host не збігається з жодним server_name, Nginx обирає дефолт. Цей дефолт може мати інший root, забороняти все або вказувати порожній каталог. Результат: раптові 404 або 403.
Виправлення нудне: зробіть server_name відповідним реальності і керуйте default_server навмисно. Не дозволяйте «який файл першим сортується» визначати продакшен-поведінку.
Alias vs root: схожі слова, різні фізики
Це заслуговує на різкість: якщо ви використовуєте alias без розуміння, ви рано чи пізно відправите 404 у продакшен.
root додає URI (або його частину залежно від location) до базового шляху. alias замінює префікс location на шлях alias. Отже наявність або відсутність ведучої/завершальної косої риски змінює результуючий шлях. Nginx не «розумний» тут; він послідовний.
try_files: тихий перенаправник
try_files чудовий: він дозволяє сервити статичні файли, якщо вони є, і відпадати на маршрут аплікації, якщо їх немає. Він також може створити заплутані 404, коли ви перенаправляєте відсутні файли на fallback, який сам відсутній або заборонений.
При відлагодженні знайдіть точну послідовність шляхів у try_files і переконайтеся, що fallback існує і доступний для читання.
Обробка index: 403, що не є «правами»
Запит директорії, наприклад /static/, може повернути 403, бо Nginx забороняє листинг директорій, якщо autoindex вимкнено, і якщо індекс-файлу немає. Це не відмова ОС у доступі; це відмова Nginx надавати листинг директорії.
Маскування через error_page
Корпоративні команди безпеки люблять мапити 403 у 404. Іноді це виправдано. Але це ускладнює роботу on-call. Якщо ви успадкували конфіг, пошукайте error_page і внутрішні локації, перш ніж робити висновки про значення коду статусу.
Жарт №2: «Ми замапили 403 у 404 з міркувань безпеки» — це як перефарбувати індикатор Check Engine: технічно ефективно, поки мотор не загориться.
Поширені помилки: симптоми → корінь проблеми → виправлення
1) Симптом: 404 на всі шляхи для відомого домену
Корінь: неправильний server block ловить запити (несумісність Host; default_server змінився; додано новий vhost).
Виправлення: перевірте Host за допомогою curl, потім підтвердіть співпадіння server_name. Зробіть бажаний vhost явним; вимкніть дефолтний сайт Debian, якщо він не потрібен.
2) Симптом: 403 на директоріях, 200 на відомих файлах
Корінь: запитана директорія без індекс-файлу; autoindex вимкнено.
Виправлення: додайте index index.html; і переконайтеся, що файл існує, або уникайте посилань на URI директорій, або свідомо увімкніть autoindex on; (рідко правильно).
3) Симптом: 403 на конкретних статичних файлах після деплою
Корінь: дрейф власності/режимів: файли створені як 640 юзером deploy; група не включає www-data.
Виправлення: забезпечте узгоджену власність, наприклад root:www-data з 644, або setgid каталоги з групочитанням файлів, або ACL для www-data.
4) Симптом: 404, але в error log показано «permission denied»
Корінь: конфіг маскує заборону як «не знайдено» через error_page або внутрішні rewrite-и.
Виправлення: зніміть маскування під час діагностики; перевірте error_page і rewrite-правила; виправте підлягаючу проблему з правами/LSM.
5) Симптом: 404 для ресурсів під /static, але файл існує в іншому місці
Корінь: використано alias з неправильною завершаючою слешкою, або root оголошено на неправильному рівні (server vs location).
Виправлення: порахуйте розв’язаний шлях. Віддавайте перевагу послідовним шаблонам; підтверджуйте через рядок open() в error log.
6) Симптом: все працює як root при тесті, але Nginx все ще 403
Корінь: ви тестували як root, а не як www-data, і пропустили біти проходження або ACL.
Виправлення: завжди тестуйте читання за допомогою sudo -u www-data і використовуйте namei -l.
7) Симптом: права виглядають коректно, але все одно 403/404
Корінь: AppArmor забороняє доступ до шляху (часто контент переміщено в /srv, /data або використано bind mount).
Виправлення: підтвердіть відмову в kernel логах; оновіть профіль AppArmor або перемістіть контент у дозволені шляхи.
8) Симптом: випадкові 404 після додавання нового сайту
Корінь: новий server block став дефолтом для слушателя; або перекриття server_name і listen-блоків спричинило неоднозначність у матчінгу.
Виправлення: встановіть рівно один default на кожен listen socket; перевірте за допомогою nginx -T і цілеспрямованих curl з правильним Host.
Чеклісти / покроковий план
Покроково: діагностика раптового 403
- Відтворіть за допомогою curl з правильним заголовком Host. Захопіть статус і Server header.
- Тайл error.log під час відтворення; шукайте
(13: Permission denied),directory index of ... is forbiddenабо явнеaccess forbidden by rule. - Підтвердіть server block (співпадіння server_name, поведінка default_server).
- Обчисліть цільовий шлях файлу з рядка логу. Не здогадуйтеся.
- Протестуйте читання як www-data і запустіть
namei -l, щоб виявити заблокований каталог. - Перевірте відмови AppArmor в kernel логах.
- Виправте найменше (один режим каталогу, одна ACL, один root рядок), перезапустіть, повторіть тест.
Покроково: діагностика раптового 404
- Підтвердіть, що відповідає Nginx, а не апстрім, перевіряючи заголовки і access log.
- Знайдіть шлях, який open() намагався відкрити в error.log. Якщо там
(2: No such file or directory), ви в зоні мапінгу/деплою. - Переконайтеся, що файл існує за тим точним шляхом.
- Якщо файл існує в іншому місці, перегляньте
root/aliasі правилаlocation; перевіртеtry_files. - Підтвердіть, що ви в очікуваному vhost, а не в дефолтному catchall.
- Пошукайте правила маскування, які перетворюють заборону в «не знайдено».
- Перезавантажте з валідацією (
nginx -t, потім reload) і повторіть тест.
Операційний чекліст: підвищення надійності
- Вимкніть дефолтний сайт Debian на продакшені, якщо він не потрібен.
- Логувати розв’язаний шлях (error.log вже це робить; тримайте на розумному рівні).
- Робіть власність і права частиною артефакту деплою, а не пост-кроком.
- Тримайте сервований контент в невеликій кількості відомих root-ів і узгодьте AppArmor-політику з ними.
- Використовуйте
nginx -tв CI та перед reload; фейліть швидко. - Коли використовуєте симлінкові деплoї, свідомо визначайте політику
disable_symlinksі документуйте її.
Три короткі історії з корпоративного життя (усі анонімізовані, всі правдоподібні)
Коротка історія №1 (невірне припущення): «403 означає, що WAF це блокує»
Симптом виглядав переконливо: сплеск 403 на шляху статичних ресурсів одразу після невеликого релізу. Інженер на чергуванні припустив, що це шар edge/WAF, бо сторінка 403 не виглядала як типовий Nginx. Всі кинулися до панелі безпеки.
Тим часом error log на origin показував іншу картину: open() ".../app.css" failed (13: Permission denied). Джоба деплою змінила упаковку з root:www-data на deploy:deploy з суворим umask. Контент з’явився з правами 640, недоступними для www-data.
Теорія «WAF» протрималася, бо люди більше вірили HTML у відповіді, ніж логам сервера. Але HTML-сторінка прийшла від кастомного error_page, що віддавав брендовану сторінку як для 403, так і для 404. Статус був реальний; сторінка — лише декорація.
Виправлення було нудним і стійким: зробити так, щоб збірка виробляла правильну власність і права у артефакті, забезпечити це в кроці деплою і додати smoke-тест, який читає відомий файл як www-data перед поміткою деплою як здорового.
Урок не в тому, щоб «не звинувачувати WAF». Він у тому, щоб не брати статусні сторінки за доказ. Логи — це докази.
Коротка історія №2 (оптимізація, що відпалила): «Понатужимось загартувати симлінки для безпеки»
Команда платформи посилила Nginx з disable_symlinks if_not_owner from=$document_root;. Ідея була розумна: запобігти класу симлінкових трюків і зменшити радіус ураження, якщо розробник випадково вкаже щось чутливе.
Потім продуктова команда запровадила атомарний деплой через симлінки під документ-рутом: /srv/www/app/current вказував на новий релізний каталог, створений деплой-користувачем. Власність між цільовим каталогом симлінку і document_root відрізнялася, бо релізні каталоги створювалися CI з іншим UID.
Результат: переривчасті 403 залежно від того, які активи розв’язувалися через симлінк, і від того, яким був власник файлу після білду. Помилки були коректні. Конфігурація була коректна. Дизайн системи не відповідав політиці.
Відкат вирішив проблему швидко, але це був дзвінок: «перемикачі загартування» не безкоштовні. Якщо ви вмикаєте контроль безпеки, що змінює семантику файлової системи, ви маєте перевірити його на вашу модель деплою. Безпека і надійність не вороги, але потребують координації.
Коротка історія №3 (нудна, але правильна практика, що врятувала): «Ми завжди таїли логи під час репро»
Одна команда мала просте правило: коли можна відтворити веб-помилку, відтворюй її з сервера через curl, та підглядати логи. Без винятків, без дебатів. Це не була героїчна культура; це була культура економії часу.
Одного ранку Debian 13 хост почав повертати 404 для одного домену, тоді як інші домени на тому самому екземплярі Nginx були в порядку. Швидка думка була «хтось видалив файли» або «rsync провалився». На чергуванні все одно зробили ритуал: curl + tail access/error logs.
Error log показав, що Nginx шукає під іншим root-ом, і поле server в рядку логу не збігалося з очікуваним доменом. Це привело безпосередньо до справжньої проблеми: новий vhost мав listen 80 default_server; і тихо перехоплював невідповідні хости.
Виправлення зайняло кілька хвилин: прибрати default_server з небажаного vhost, перезавантажити, перевірити маршрутизацію Host через curl. Жодних відновлень файлів. Жодних змін прав. Жодної драматичної роботи.
«Нудна практика» — це не просто tail логів. Це погодження, що докази важливіші за теорії, і приведення цього в операційний звичай.
Як визначити миттєво: ієрархія доказів
Якщо потрібен один вивід, то ось ця ієрархія — використовуйте її, щоб не обманювати себе:
- Рядок error log для запиту (містить розв’язаний шлях і код помилки ядра).
- Запис access log (підтверджує host, URI, статус, час).
- Відтворення з хоста через curl (усуває DNS і зовнішню варіабельність).
- Файлова перевірка як www-data (доводить реальні права).
- LSM audit логи (доводять відмови політики обов’язкового доступу).
- Лише потім: огляд конфігурації та рефакторинг.
Одна цитата, яку я пам’ятаю під час шумних інцидентів:
перефразована ідея — W. Edwards Deming: Без даних ви просто ще одна людина з думкою.
Ось так виглядає on-call в одному реченні.
FAQ
1) «Якщо це 404, то це не може бути правами, правда?»
Ні. Часто це відображення, але конфіги можуть навмисно перетворювати 403 у 404 (error_page або внутрішні rewrite-и). Завжди перевіряйте error.log на (13) проти (2).
2) «Де знаходиться error log Nginx на Debian 13?»
Зазвичай /var/log/nginx/error.log, якщо не перевизначено error_log в /etc/nginx/nginx.conf або в файлі сайту. Підтвердіть за допомогою nginx -T.
3) «Чому я отримую 403 для директорії, але 200 для файлів всередині?»
Тому що запит директорії тригерить обробку індексу. Якщо немає index файлу і autoindex вимкнено, Nginx повертає 403 («directory index … is forbidden»). Це не права ОС.
4) «Який найшвидший спосіб підтвердити невідповідність vhost?»
Використайте curl -sv з потрібним заголовком Host, потім подивіться на поле server: у рядку error log (воно часто виводить server_name, який збігся). Також виведіть конфіг за допомогою nginx -T і пошукайте server blocks.
5) «Я змінив права, але все одно падає. Що далі?»
Перевірте AppArmor-відмови в kernel логах. Якщо бачите apparmor="DENIED" для nginx, chmod не допоможе. Або відкоригуйте профіль, або сервируйте з дозволених шляхів.
6) «Чи слід запускати воркери Nginx від іншого користувача, ніж www-data?»
Тільки якщо маєте чітку мету ізоляції і операційну дисципліну. Зміна користувача воркерів без випрацювання стратегії власності і ACL — чудовий спосіб народити 403.
7) «Чому namei важливий, якщо я вже перевірив права файлу?»
Бо директоріям потрібен біт виконання для проходження. namei -l показує права на кожному сегменті шляху, тож ви помітите ту єдину «закриту» директорію, яка ламає все.
8) «Як уникнути помилок alias/root для статичних файлів?»
Обирайте конвенцію і дотримуйтеся її. Якщо використовуєте alias, будьте скрупульозні щодо слешів і підтверджуйте розв’язаний шлях через error log. Якщо можете чисто використати root, це часто простіше.
9) «Чи може невдалий reload залишити Nginx з старою конфігурацією?»
Так. Reload може провалитися через синтаксис або проблеми з правами. Завжди запускайте nginx -t перед reload і перевіряйте за допомогою nginx -T, коли ставки високі.
10) «Чому деплой міг спричинити 404 замість 403?»
Якщо активи не потрапили в очікуваний шлях (змінився build step, rsync виключає файли, неправильний артефакт), Nginx просто не знайде їх: (2: No such file or directory). Це 404, і це розмова з вашим pipeline-ом деплою.
Висновок: практичні наступні кроки
Якщо на Debian 13 Nginx раптово почав повертати 403/404, не сперечайтеся з припущеннями. Виконайте швидкий план:
- Відтворіть за допомогою
curl -svз реальним заголовком Host. - Тайлніть
/var/log/nginx/error.logі знайдіть точний рядокopen()(шлях + errno). - Підтвердіть вибір vhost (
nginx -Tі server_name/default_server). - Тестуйте файловий доступ як
www-dataі перевіряйте проходження директорій за допомогоюnamei -l. - Якщо ще не сходиться — перевірте AppArmor-відмови в kernel логах.
Потім виправте одну річ, перезавантажте і повторіть тест. Найкраща реакція на інцидент — та, що лишає після себе запобіжник: політика прав у деплої, перевірка vhost-ів або правило AppArmor, що відповідає реальному розташуванню файлів.