العودة إلى المدونة

خدمة systemd لا تبدأ؟ هكذا تُصلحها

14 سبتمبر 2026

خدمة systemd ترفض البدء ليست غامضة قطعًا. شغّل systemctl status <unit>، ثم اقرأ آخر 50 سطرًا من سجل تلك الوحدة بـ journalctl -u <unit> -n 50 --no-pager — وبين الاثنين، عادة يُسمّى أحد خمسة أسباب صراحة: مسار خاطئ، ثنائي مفقود، أذونات خاطئة، رفض SELinux/AppArmor، أو خطأ صياغة في ملف الوحدة. سبب الفشل في السجل؛ والإصلاحات أدناه مجرد مطابقة أنماط ضده.

كيف أرى لماذا فشلت خدمة systemd؟

الحالة أولًا ثم السجل:

systemctl status myapp.service
journalctl -u myapp.service -n 50 --no-pager

يعطيك status الحالة (inactive (dead) أو failed (exit-code) أو activating (auto-restart)) وآخر أسطر السجل. أما السجل فيعطيك الحكاية كاملة: stdout وstderr وشكايات systemd الخاصة بالوحدة.

وإذا فشلت الوحدة سابقًا وأردت سجل تلك الجولة، فأضف -b للإقلاع الحالي أو --since today:

journalctl -u myapp.service -b --no-pager

وللعدة الكاملة — الإقلاعات والأولويات ومتابعة الناتج حيًّا — انظر ورقة أوامر journalctl. إنها الذاكرة العضلية نفسها.

وزوج آخر يستحق المعرفة:

systemctl list-units --failed
systemctl reset-failed myapp.service

الأول يسرد كل وحدة حمراء على الجهاز. والثاني يمسح حالة الفشل بعد إصلاحك لها — تجميلي، لكنه يمنع صفحات الحالة من البكاء الذئب.

ما أكثر الأسباب شيوعًا؟

بعد أن يسمّي السجل العرَض، يكون السبب في الغالب أحد هذه الخمسة:

يقول السجلالسبب المرجحالإصلاح
status=203/EXECمسار ExecStart= خاطئ أو مفسّر مفقودمسار مطلق، chmod +x، تحقق من shebang
status=203/EXEC على سكربتأسطره تنتهي بـ CRLF أو shebang فاسدdos2unix script.sh، أصلح السطر الأول
status=1/FAILURE بلا سجلات تطبيقمجلد عمل أو متغير بيئة مفقوداضبط WorkingDirectory=، أضف Environment=
Permission deniedلا يقرأ المستخدم الملفات أو يربط المنفذأصلح الملكية؛ المنافذ دون 1024 تحتاج AmbientCapabilities=CAP_NET_BIND_SERVICE أو root
Unit is maskedأحدهم شغّل systemctl masksystemctl unmask myapp.service
تعديل ملف الوحدة “لا يفعل شيئًا”العفريت لم يُعاد تحميلهsystemctl daemon-reload

وعائلة 203/EXEC تستحق ذكرًا خاصًا لأنها آكلة العشاءات. لا يستخدم systemd طرفيتك لإطلاق ExecStart=. أي أن:

# خطأ — لا طرفية، الـ ~ لا يتمدد أبدًا، ولا بحث في PATH
ExecStart=~/app/run.sh

# صحيح
ExecStart=/opt/app/run.sh

ويجب أن يكون السكربت نفسه قابلًا للتنفيذ ويبدأ بـ shebang حقيقي (#!/bin/bash أو #!/usr/bin/env bash). سكربت يعمل جيدًا من طرفيتك ويفشل بـ 203 تحت systemd هو دائمًا تقريبًا أحد: غير قابل للتنفيذ، أو نهايات CRLF، أو shebang يشير إلى العدم، أو مسار نسبي.

لماذا تبدأ يدويًا ولا تبدأ مع الإقلاع؟

خطأ الترتيب الكلاسيكي. إذا أظهر السجل فشلًا بعد الإقلاع مباشرة لكن الوحدة تبدأ جيدًا حين تشغّلها بـ systemctl start بيدك، فخدمتك تخسر سباقًا — تصل إلى الشبكة أو قرص مركّب أو قاعدة بيانات قبل وجوده.

الإصلاح أن تصرّح بالاعتمادات بدل الأمل:

[Unit]
After=network-online.target postgresql.service
Wants=network-online.target

[Service]
ExecStartPre=/usr/bin/test -f /opt/app/config.toml

يرتب After= بدء التشغيل؛ ويجعل Wants= systemd يرفع الاعتماد فعلًا. ولا يعمل network-online.target إلا إذا كانت خدمة انتظار الشبكة مفعلة على توزيعتك، فتحقق من systemctl is-enabled NetworkManager-wait-online.service (أو ما يقابلها في systemd-networkd). وحرس ExecStartPre= وسيلة رخيصة صادقة لتفشل عالي الصوت برسالة مقروءة لا بآثار مكدس.

ونسخة ثانية: تبدأ الخدمة مع الإقلاع ثم تموت فورًا. ابحث عن أشياء كانت في بيئتك التفاعلية وغائبة عن الإقلاع — اختلافات PATH، أو virtualenv، أو HOME. اضبط ما تحتاجه صراحة:

[Service]
Environment="PATH=/opt/app/venv/bin:/usr/bin"
User=appuser
WorkingDirectory=/opt/app

لماذا لا تسجل خدمتي في السجل؟

إذا لم يعرض journalctl -u شيئًا، فافحص ثلاثة أشياء بالترتيب:

  1. StandardOutput= وStandardError= في الوحدة — يجب أن تكون journal (الافتراضي) أو journal+console. ربما ضبطهما أحدهم على null.
  2. التطبيق يكتب في ملف بدل stdout. لا يلتقط systemd إلا stdout/stderr؛ ومن يكتب في ملفات سجل يتجاوز السجل كليًا. إما وجّه التطبيق إلى stdout أو اقرأ الملف.
  3. حدود التخزين أسقطت أسطرًا قديمة: journalctl --disk-usage، وSystemMaxUse= في /etc/systemd/journald.conf إذا كان السجل يختنق.

ولتصحيح البدء نفسه، لا شيء يغلب إسقاط طرفية داخل سياق الإقلاع:

[Service]
ExecStart=/bin/bash -c 'exec /opt/app/bin/server 2>&1'

أو، للألغاز الصادقة، شغّل أمر ExecStart= نفسه بمستخدم الوحدة داخل طرفية — ومعظم فروق البيئة تظهر في أول عشر ثوانٍ.

كيف أجعلها تعيد التشغيل تلقائيًا بعد الانهيار؟

السياسة الافتراضية Restart=no: الخدمة المنهارة تبقى ميتة، وتعرف بذلك من مستخدم. أصلحها لكل خدمة:

[Service]
Restart=on-failure
RestartSec=5
الإعداديعيد التشغيل عند
no (الافتراضي)أبدًا
on-failureخروج غير صفري، إشارة، تجاوز زمن
alwaysأي خروج، حتى النظيف
on-watchdogتجاوز زمن المراقبة وحده

اربط Restart=on-failure بحارس معدل البدء كي لا تقصف حلقة الانهيار الجهاز: StartLimitIntervalSec= وStartLimitBurst= في قسم [Unit]. خمسة أعطاب في ستين ثانية ينبغي أن تنبه إنسانًا لا أن تلبي معالجًا.

وإذا كنت تربطها كعمل مجدول لا كعفريت، فزِن cron مقابل مؤقت systemd أولًا — المؤقتات تمنحك تسجيلًا في السجل وترتيب اعتمادات مجانًا، وهما بالضبط ما تسعى إليه هذه المقالة طوال الوقت.

قائمة الفحص في 60 ثانية

  1. systemctl status <unit> — اقرأ الحالة وآخر الأسطر.
  2. journalctl -u <unit> -n 50 --no-pager — اعثر على الخطأ الحقيقي.
  3. 203/EXEC؟ أصلح المسار وshebang والأذونات. Permission denied؟ أصلح المستخدم وملكية الملف.
  4. تفشل مع الإقلاع فقط؟ أضف After=/Wants=network-online.target وحرس ExecStartPre=.
  5. عدّلت ملف وحدة؟ systemctl daemon-reload && systemctl restart <unit>.
  6. أضف Restart=on-failure ليعلن الانهيار القادم نفسه بدل الاختباء.

معظم لحظات “systemd معقدة” تنحصر في عبارة: الخطأ كان في السجل طوال الوقت. اقرأه قبل أن تعدّل ملف الوحدة، لا بعده.

— mrsaynothing

— mrsaynothing

ملاحظات ميدانية في الذكاء الاصطناعي وLinux والاستضافة الذاتية.

ناقش هذا المقال على dev.to dev.to ↗

احصل على الشرح التطبيقي التالي بالبريد

رسالة واحدة لكل مقال. أصلح المشكلة وامضِ.

self-hosted · بلا أطراف ثالثة · إلغاء الاشتراك بنقرة واحدة

ما هذا؟

ضغط GGUF: أي مستوى تستخدم؟

أعجبتك هذه الكتابات؟ بناء مثل هذا هو عملي. وظّفني