وقتی یک سرویس لینوکس با 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.shWrapper را جداگانه با همان 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
systemctl statusوjournalctlرا بخوانید.systemctl catو مقدار واقعیExecStartرا بررسی کنید.- وجود executable، Symlink و interpreter را کنترل کنید.
- Permission فایل و دایرکتوریهای مسیر را با
namei -lبررسی کنید. - Shebang و CRLF را تست کنید.
- Mount Optionهای
noexecرا بررسی کنید. - SELinux/AppArmor و Hardening خود Unit را کنترل کنید.
- بعد از تغییر،
daemon-reload، Restart و Verify انجام دهید.
اگر این سرویس روی یک VPS لینوکسی Production اجرا میشود، قبل از تغییر Permission، Unit یا Policy امنیتی Snapshot/Backup و دسترسی جایگزین داشته باشید. برای استقرار سرویسهای لینوکسی روی زیرساخت مجازی میتوانید پلنهای سرور مجازی وانسرور را بررسی کنید.