ESC را فشار دهید تا بسته شود

رفع خطای status=203/EXEC در systemd؛ عیب‌یابی ExecStart، Permission و Shebang

فهرست

وقتی یک سرویس لینوکس با status=203/EXEC یا پیام Failed at step EXEC بالا نمی‌آید، مشکل معمولاً قبل از اجرای واقعی برنامه رخ داده است: systemd نتوانسته فایل معرفی‌شده در ExecStart یا مفسر آن را با execve() اجرا کند. بنابراین قبل از دستکاری Restart Policy یا افزایش Retry، باید خود مسیر اجرایی، مجوزها، Shebang، mount optionها و محدودیت‌های امنیتی را بررسی کنید.

در مستندات رسمی systemd، کد 203 با نام EXIT_EXEC تعریف شده و به شکست اجرای process مربوط است. این خطا با Crash شدن برنامه بعد از شروع فرق دارد؛ اگر executable اصلاً اجرا نشده باشد، تمرکز عیب‌یابی باید روی مرحله EXEC باشد.

تشخیص و رفع خطای Failed at step EXEC در systemd

نام سرویس نمونه در این مقاله myapp.service است. آن را با نام واقعی سرویس خودتان جایگزین کنید:

systemctl status myapp.service --no-pager -l
journalctl -u myapp.service -b -n 100 --no-pager
systemctl cat myapp.service
systemctl show myapp.service -p ExecStart -p User -p Group -p WorkingDirectory -p Result -p ExecMainStatus

اگر در خروجی عبارت‌هایی مثل status=203/EXEC، Failed at step EXEC، No such file or directory یا Permission denied می‌بینید، ادامه این راهنما دقیقاً برای همین سناریو است. اگر خطا بعداً به start request repeated too quickly تبدیل شد، مقاله رفع خطای start request repeated too quickly در systemd را هم بررسی کنید؛ آن خطا معمولاً نتیجه چند بار Failure پشت‌سرهم است، نه علت اولیه 203/EXEC.

قبل از تغییر، از Unit File نسخه پشتیبان بگیرید

اگر Unit سفارشی در /etc/systemd/system دارید، قبل از ویرایش یک نسخه پشتیبان بگیرید:

sudo cp -a /etc/systemd/system/myapp.service /root/myapp.service.before-203-exec.bak
sudo systemctl cat myapp.service

اگر سرویس از Package نصب شده و فقط Drop-in دارید، مسیر واقعی فایل‌ها را با systemctl cat مشخص کنید و همان فایل‌هایی را که قصد ویرایششان دارید Backup بگیرید.

مرحله ۱: رفع مشکل ExecStart در systemd

یکی از رایج‌ترین علت‌ها این است که مسیر داخل ExecStart اشتباه است یا فایل بعد از Deploy جابه‌جا شده است. فرض کنید Unit این خط را دارد:

ExecStart=/opt/myapp/app.sh

وجود فایل و نوع آن را بررسی کنید:

ls -l /opt/myapp/app.sh
stat /opt/myapp/app.sh
file /opt/myapp/app.sh
readlink -f /opt/myapp/app.sh

اگر مسیر وجود ندارد، Symlink شکسته است یا Deploy فایل را در مسیر دیگری قرار داده، Unit را با مسیر واقعی اصلاح کنید. برای executableها استفاده از مسیر مطلق خواناتر و قابل‌اعتمادتر است و وابستگی به محیط Interactive Shell را کمتر می‌کند.

بعد از اصلاح Unit، قبل از Restart این دستور را اجرا کنید:

sudo systemd-analyze verify /etc/systemd/system/myapp.service

این دستور خطاهای Syntax و بسیاری از مشکلات Unit را زودتر نشان می‌دهد؛ البته سالم بودن Syntax به‌تنهایی تضمین نمی‌کند executable واقعاً قابل اجراست.

مرحله ۲: رفع خطای Permission denied سرویس systemd

فقط داشتن بیت اجرای فایل کافی نیست. کاربری که سرویس با آن اجرا می‌شود باید بتواند همه دایرکتوری‌های مسیر را Traverse کند. ابتدا User/Group سرویس را ببینید:

systemctl show myapp.service -p User -p Group

سپس Permission مسیر را مرحله‌به‌مرحله بررسی کنید:

namei -l /opt/myapp/app.sh
stat -c '%A %a %U:%G %n' /opt/myapp/app.sh

اگر سرویس با کاربر myapp اجرا می‌شود، دسترسی اجرایی را با همان کاربر تست کنید:

sudo -u myapp test -x /opt/myapp/app.sh && echo EXEC_OK || echo EXEC_FAIL

به‌جای myapp نام کاربر واقعی Unit را قرار دهید. از chmod 777 به‌عنوان راه‌حل سریع استفاده نکنید. اگر مالکیت درست است ولی فقط بیت اجرا کم است، یک تنظیم محدودتر مثل نمونه زیر منطقی‌تر است:

sudo chmod 750 /opt/myapp/app.sh

عدد مناسب به ساختار مالکیت و Group سرویس شما بستگی دارد. اگر فایل را مستقیماً با interpreter اجرا می‌کنید، مثلاً ExecStart=/usr/bin/python3 /opt/myapp/app.py، خود فایل Python الزاماً به بیت execute نیاز ندارد، اما interpreter باید executable باشد و فایل برنامه باید برای کاربر سرویس قابل خواندن باشد.

مرحله ۳: رفع خطای Shebang در systemd

برای اسکریپتی که مستقیماً در ExecStart اجرا می‌شود، خط اول باید به یک interpreter معتبر اشاره کند. مثلاً:

#!/bin/bash

یا در محیط‌هایی که مسیر Bash متفاوت است:

#!/usr/bin/env bash

اول ببینید interpreter واقعاً وجود دارد:

head -n 1 /opt/myapp/app.sh
command -v bash
ls -l /bin/bash /usr/bin/env

اگر Shebang به مسیری مثل /usr/local/bin/bash اشاره می‌کند ولی فایل وجود ندارد، systemd ممکن است 203/EXEC بدهد حتی وقتی خود app.sh کاملاً وجود دارد. این همان حالتی است که پیام «No such file or directory» می‌تواند گمراه‌کننده باشد: فایل اصلی هست، اما interpreter معرفی‌شده در Shebang پیدا نمی‌شود.

مرحله ۴: CRLF را در فایل‌هایی که از Windows آمده‌اند پیدا کنید

اگر اسکریپت در Windows ویرایش شده باشد، ممکن است خط اول به شکل پنهان #!/bin/bash^M ذخیره شده باشد و kernel مسیر interpreter را اشتباه تفسیر کند. بررسی کنید:

head -n 1 /opt/myapp/app.sh | cat -A
file /opt/myapp/app.sh

اگر انتهای خط ^M می‌بینید، ابتدا Backup بگیرید و سپس Line Ending را به LF تبدیل کنید. اگر dos2unix نصب است:

sudo cp -a /opt/myapp/app.sh /opt/myapp/app.sh.bak
sudo dos2unix /opt/myapp/app.sh

اگر dos2unix نصب نیست، بهتر است به‌جای اجرای یک دستور مبهم روی فایل Production، خود ابزار را از مخزن رسمی توزیع نصب کنید و سپس تبدیل را انجام دهید:

# Debian / Ubuntu
sudo apt update && sudo apt install dos2unix

# RHEL / AlmaLinux / Rocky Linux
sudo dnf install dos2unix

سپس دوباره dos2unix /opt/myapp/app.sh را اجرا کنید و Shebang را با cat -A کنترل کنید.

مرحله ۵: بررسی کنید فایل‌سیستم با noexec Mount نشده باشد

حتی اگر Permission فایل درست باشد، Mount Option noexec می‌تواند اجرای مستقیم executable را مسدود کند. برای مسیر فایل بررسی کنید:

findmnt -T /opt/myapp/app.sh -o TARGET,SOURCE,FSTYPE,OPTIONS

اگر در OPTIONS مقدار noexec وجود دارد، ابتدا مشخص کنید چرا این محدودیت اعمال شده است. حذف noexec از یک Mount امنیتی تصمیم کوچکی نیست. معمولاً بهتر است executable را به فایل‌سیستمی که برای اجرای برنامه طراحی شده منتقل کنید یا طراحی سرویس را اصلاح کنید.

مرحله ۶: SELinux و AppArmor را نادیده نگیرید

روی توزیع‌هایی با SELinux، Permissionهای Unix ممکن است درست باشند ولی Policy اجرای فایل را مسدود کند. وضعیت را ببینید:

getenforce
ls -Z /opt/myapp/app.sh
sudo ausearch -m AVC -ts recent | tail -n 30

اگر Context فایل بعد از Copy یا Deploy اشتباه شده، به‌جای خاموش‌کردن SELinux ابتدا Context پیش‌فرض را بازیابی کنید:

sudo restorecon -v /opt/myapp/app.sh

روی Ubuntu و سیستم‌های دارای AppArmor نیز لاگ Deny را بررسی کنید:

sudo aa-status
journalctl -k -b | grep -i apparmor | tail -n 50

برای عیب‌یابی، SELinux یا AppArmor را کورکورانه Disable نکنید؛ دلیل Deny را پیدا کنید و Policy یا محل فایل را اصولی اصلاح کنید.

مرحله ۷: محدودیت‌های خود systemd را بررسی کنید

گاهی مشکل از Hardening خود Unit می‌آید. گزینه‌هایی مانند ProtectHome=، InaccessiblePaths=، NoExecPaths= یا ExecPaths= می‌توانند باعث شوند فایلی که روی Shell عادی قابل اجراست، داخل محیط سرویس قابل اجرا نباشد.

systemctl cat myapp.service
systemctl show myapp.service | grep -E 'ProtectHome|InaccessiblePaths|NoExecPaths|ExecPaths'

اگر یکی از این محدودیت‌ها فعال است، آن را صرفاً برای «رفع خطا» حذف نکنید. ابتدا بررسی کنید executable باید در کدام مسیر قرار بگیرد و آیا می‌توان سرویس را بدون کاهش Hardening اصلاح کرد.

مرحله ۸: ExecStart را مثل Shell معمولی ننویسید

systemd به‌طور پیش‌فرض یک Shell Interactive دور فرمان شما قرار نمی‌دهد. بنابراین Pipe، Redirection و بعضی Expansionهای Shell را نباید بدون interpreter صریح در ExecStart بنویسید. این نمونه طراحی خوبی نیست:

ExecStart=/usr/bin/myapp | tee /var/log/myapp.log

اگر واقعاً به Shell نیاز دارید، رفتار را آگاهانه تعریف کنید؛ با این حال برای سرویس‌های Production معمولاً Wrapper Script روشن و قابل تست از یک خط Shell پیچیده بهتر است:

ExecStart=/usr/local/bin/start-myapp.sh

Wrapper را جداگانه با همان User سرویس تست کنید و Permission و Shebang آن را هم بررسی کنید.

مرحله ۹: WorkingDirectory و مسیرهای نسبی را بررسی کنید

WorkingDirectory= معمولاً علت مستقیم 203/EXEC برای یک مسیر مطلق سالم نیست، اما می‌تواند اجرای Script یا فایل‌های وابسته را خراب کند. مقدار را ببینید:

systemctl show myapp.service -p WorkingDirectory
systemctl cat myapp.service

اگر برنامه به فایل‌های نسبی وابسته است، مطمئن شوید WorkingDirectory وجود دارد و کاربر سرویس به آن دسترسی دارد.

بعد از اصلاح، daemon-reload و تست واقعی انجام دهید

اگر Unit File یا Drop-in را تغییر داده‌اید:

sudo systemctl daemon-reload
sudo systemctl restart myapp.service
systemctl status myapp.service --no-pager -l
journalctl -u myapp.service -b -n 100 --no-pager
systemctl show myapp.service -p ActiveState -p SubState -p Result -p ExecMainStatus

در حالت سالم، سرویس باید به وضعیت مورد انتظار مثل active (running) برسد و دیگر 203/EXEC در آخرین Attempt دیده نشود. اگر executable حالا اجرا می‌شود ولی برنامه بعداً با خطای دیگری می‌میرد، علت جدید را جداگانه بررسی کنید. مثلاً برای محدودیت File Descriptor راهنمای رفع خطای Too many open files و LimitNOFILE در systemd مرتبط است.

نمونه Unit سالم برای یک Shell Script

این نمونه فقط الگوی ساختار است؛ مسیرها، User و Group را متناسب با سرویس خود تغییر دهید:

[Unit]
Description=My Application
After=network.target

[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/app.sh
Restart=on-failure
RestartSec=3

[Install]
WantedBy=multi-user.target

برای آشنایی بیشتر با ساختار Unitها و مدیریت سرویس‌ها، مقاله آشنایی با systemd و مدیریت سرویس‌ها در لینوکس را ببینید.

Rollback امن اگر تغییر جواب نداد

اگر قبل از ویرایش فایل Backup گرفته‌اید، همان نسخه را برگردانید:

sudo cp -a /root/myapp.service.before-203-exec.bak /etc/systemd/system/myapp.service
sudo systemctl daemon-reload
sudo systemctl restart myapp.service
systemctl status myapp.service --no-pager -l

اگر سرویس قبلاً وضعیت سالمی نداشته، هدف Rollback فقط بازگرداندن Configuration قبلی است، نه تضمین Up شدن سرویس. بعد از Rollback دوباره Journal را بررسی کنید.

اشتباه‌هایی که معمولاً وقت عیب‌یابی را تلف می‌کنند

  • chmod 777 روی فایل و پوشه‌ها: مشکل امنیتی ایجاد می‌کند و علت اصلی را پنهان می‌کند.
  • Restart کردن مکرر بدون خواندن Journal: فقط Start Limit را فعال می‌کند و تشخیص را سخت‌تر می‌کند.
  • نادیده گرفتن Shebang: ممکن است فایل وجود داشته باشد اما interpreter وجود نداشته باشد.
  • نادیده گرفتن CRLF: اسکریپت روی Shell ظاهراً درست دیده می‌شود ولی خط اول شامل کاراکتر مخفی است.
  • خاموش‌کردن SELinux/AppArmor: به‌جای رفع Root Cause، لایه امنیتی را حذف می‌کند.
  • تغییر noexec بدون تحلیل: ممکن است یک تصمیم امنیتی مهم روی Mount را بی‌اثر کند.

ترتیب پیشنهادی عیب‌یابی 203/EXEC

  1. systemctl status و journalctl را بخوانید.
  2. systemctl cat و مقدار واقعی ExecStart را بررسی کنید.
  3. وجود executable، Symlink و interpreter را کنترل کنید.
  4. Permission فایل و دایرکتوری‌های مسیر را با namei -l بررسی کنید.
  5. Shebang و CRLF را تست کنید.
  6. Mount Optionهای noexec را بررسی کنید.
  7. SELinux/AppArmor و Hardening خود Unit را کنترل کنید.
  8. بعد از تغییر، daemon-reload، Restart و Verify انجام دهید.

اگر این سرویس روی یک VPS لینوکسی Production اجرا می‌شود، قبل از تغییر Permission، Unit یا Policy امنیتی Snapshot/Backup و دسترسی جایگزین داشته باشید. برای استقرار سرویس‌های لینوکسی روی زیرساخت مجازی می‌توانید پلن‌های سرور مجازی وان‌سرور را بررسی کنید.

منابع فنی

Rate this post
اشتراک گذاری نوشته در:

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *