خدمة systemd لتشغيل برنامجك على VPS
خدمة systemd تُبقي برنامجك يعمل: يبدأ عند الإقلاع، ويعاد تشغيله عند الانهيار، ويُسجَّل في الـ journal. كيف تكتبها وتضيف مؤقتًا وتحصّنها.
ما هي خدمة systemd، ولماذا تحتاجها
خدمة systemd هي ملف نصي صغير يخبر خادمك كيف يشغّل برنامجًا: يبدأ تشغيله عند الإقلاع، ويعيد تشغيله إذا انهار، ويرسل مخرجاته إلى سجل النظام. هذه هي المهمة كلها. البرنامج الذي تشغّله يدويًا في جلسة SSH يموت بمجرد أن تسجّل الخروج أو تُعاد إقلاع الخادم. أما البرنامج الذي يعمل ضمن خدمة systemd فيستمر في العمل، لأن الخادم نفسه هو من يملكه، لا الـ shell الخاص بك.
systemd هو نظام الإقلاع (init system) على Ubuntu وDebian وFedora ومعظم خوادم Linux الحديثة. إنه أول عملية تبدأ، وهو الذي يشرف على كل شيء آخر. حين تكتب ملف خدمة، فأنت تسلّم برنامجك إلى ذلك المشرف. يعرض هذا الدليل أصغر وحدة (unit) تعمل، والأقسام الثلاثة الموجودة في كل وحدة، وكيفية تفعيلها وقراءة سجلّاتها، وكيفية تشغيلها وفق جدول زمني باستخدام مؤقت (timer)، وكيفية تقييدها لتعمل بأقل قدر ممكن من الصلاحيات.
أصغر خدمة تعمل
يقع ملف الخدمة في /etc/systemd/system/، وينتهي بامتداد .service، ولا يحتاج إلا إلى بضعة أسطر. أنشئ واحدًا لبرنامج موجود في /usr/local/bin/myapp:
sudo nano /etc/systemd/system/myapp.service[Unit]
Description=My application
[Service]
ExecStart=/usr/local/bin/myapp
[Install]
WantedBy=multi-user.targetهذه وحدة كاملة وتعمل فعليًا. ExecStart هو الأمر الذي يُشغَّل. وWantedBy=multi-user.target يعني أن يبدأ هذا بمجرد وصول الخادم إلى وضع التشغيل الطبيعي متعدد المستخدمين، وهذا بالضبط ما يجعلها تعمل تلقائيًا عند الإقلاع. كل ما تبقى هو تنقيح.
الأقسام الثلاثة، وما وظيفة كل واحد منها
يُقسَّم كل ملف وحدة إلى أقسام محاطة بأقواس مربعة. تستخدم الخدمة ثلاثة منها.
[Unit] يصف الخدمة وعلاقاتها. السطران اللذان ستستخدمهما أكثر من غيرهما:
[Unit]
Description=My application
After=network-online.target
Wants=network-online.targetDescription هو التسمية المقروءة التي تراها في systemctl status. وAfter=network-online.target يخبر systemd ألا يبدأ برنامجك قبل أن تصبح الشبكة جاهزة، وهذا مهم لأي شيء يربط منفذًا أو يفتح اتصالًا صادرًا.
[Service] يحدد كيف يعمل البرنامج. وهنا تضع معظم إعداداتك:
[Service]
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.toml
WorkingDirectory=/opt/myapp
User=myapp
Restart=on-failure
RestartSec=5
Environment=LOG_LEVEL=infoUser=myapp يشغّل البرنامج بحساب غير مميّز الصلاحيات بدلًا من root، وهذا هو أهم سطر على الإطلاق من حيث السلامة. أما Restart=on-failure وRestartSec=5 فلهما قسمهما الخاص أدناه، لأنهما السبب الذي يدفع معظم الناس إلى كتابة خدمة أصلًا.
[Install] يحدد ما يحدث حين تفعّل الخدمة:
[Install]
WantedBy=multi-user.targetWantedBy=multi-user.target هو ما يربط الخدمة بالإقلاع حين تنفّذ systemctl enable. فبدون قسم [Install]، يمكن تشغيل الخدمة يدويًا، لكنها لن تعمل من تلقاء نفسها بعد إعادة الإقلاع.
فعّلها وراقبها
بعد كتابة أو تعديل أي ملف وحدة، أعد تحميل systemd ليقرأ التغيير، ثم فعّل الخدمة وابدأها في خطوة واحدة:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.servicedaemon-reload هي الخطوة التي ينساها الناس: يخزّن systemd ملفات الوحدات في ذاكرة مؤقتة، فلا يُحدث التعديل أي أثر حتى تعيد التحميل. أما enable --now فيفعّل الخدمة للإقلاع ويبدأها فورًا. تحقق منها:
sudo systemctl status myapp.service* myapp.service - My application
Loaded: loaded (/etc/systemd/system/myapp.service; enabled)
Active: active (running) since Wed 2026-07-15 22:40:11 UTC; 3s ago
Main PID: 4123 (myapp)Active: active (running) وكلمة enabled هما ما تريد رؤيته. ولقراءة مخرجات البرنامج، اسأل الـ journal (سجلّ systemd) عن هذه الوحدة تحديدًا:
sudo journalctl -u myapp.service -fالخيار -f يتابع الأسطر الجديدة أولًا بأول عند وصولها، تمامًا مثل tail -f. وكل ما يكتبه برنامجك إلى المخرج القياسي أو الخطأ القياسي ينتهي به المطاف هنا، دون أي إعداد للتسجيل من جانبك.
إعادة التشغيل بعد الفشل، سبب وجودك هنا
الفائدة الرئيسية لأي خدمة هي أن systemd يعيد تشغيل برنامجك حين يموت. سطران يكفيان لتحقيق ذلك:
[Service]
Restart=on-failure
RestartSec=5Restart=on-failure يعيد تشغيل البرنامج حين يخرج برمز غير صفري أو يموت بإشارة انهيار مثل SIGKILL أو SIGSEGV. أما الخروج النظيف، أو التوقف بإشارة SIGTERM أو SIGINT أو SIGHUP أو SIGPIPE، فلا يستدعي ذلك. وRestartSec=5 ينتظر خمس ثوانٍ بين المحاولات، حتى لا يدور برنامج ينهار فورًا في حلقة متلاحقة. تأكد من ذلك بقتل العملية ومراقبة systemd وهو يعيدها. استخدم SIGKILL: فإشارة SIGTERM الافتراضية تُحتسب توقفًا نظيفًا، ولذلك لن يعيد on-failure تشغيل الخدمة:
sudo systemctl kill -s SIGKILL myapp.service
sudo systemctl status myapp.serviceخلال خمس ثوانٍ تعرض الحالة Main PID جديدًا وactive (running) من جديد. هذه هي الميزة كلها، ولهذا فإن الخدمة أفضل من ترك برنامج يعمل داخل tmux أو screen.
شغّلها بمستخدم غير مميّز الصلاحيات، وحصّنها
الخدمة التي تعمل بحساب root يمكنها فعل أي شيء بخادمك إذا استُغِلّ البرنامج يومًا. شغّلها بمستخدم خاص بها، وأعطِ systemd بضعة توجيهات تحصرها ضمن حدود. أنشئ أولًا حسابًا نظاميًا بلا تسجيل دخول وبلا مجلد منزلي:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin myappثم اضبط User=myapp وأضف أسطر التحصين إلى [Service]:
[Service]
User=myapp
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=trueكل سطر يزيل شيئًا لا يحتاج إليه البرنامج. NoNewPrivileges=true يمنع العملية من اكتساب أي صلاحيات جديدة على الإطلاق، حتى عبر ملف تنفيذي بخاصية setuid. وPrivateTmp=true يمنحها مجلد /tmp خاصًا لا تراه أي عملية أخرى. وProtectSystem=strict يجعل نظام الملفات كله للقراءة فقط باستثناء بضعة مسارات تسمّيها بـ ReadWritePaths=. وProtectHome=true يخفي /home عنها كليًا. هذا هو منطق أقل قدر ممكن من الصلاحيات نفسه الذي يقوم عليه وضع خدمة خلف جدار حماية: أعطِها فقط ما تحتاج إليه. إن كنت قد قرأت الدليل حول سد ثغرة جدار حماية IPv6 على VPS، فهذا هو النصف الخاص بالمضيف من الفكرة ذاتها. أما بالنسبة إلى خدمة تواجه الإنترنت، فاقرن هذا التحصين بـ Fail2ban أمام SSH وجدار حماية يرفض كل شيء افتراضيًا.
بدلًا من أن تكتب كل هذا يدويًا وربما تنسى توجيهًا أو تخطئ فيه، ولّد وحدة كاملة ومحصّنة وانسخها مباشرة:
المؤقتات (timers): البديل الحديث لـ cron
مؤقت systemd يشغّل خدمة وفق جدول زمني، وهو البديل الحديث لمهمة cron. يتكوّن أي مؤقت من ملفين: ملف .service يقوم بالعمل، وملف .timer يحدد متى. لنفترض أنك تريد نسخة احتياطية عند الساعة 3 صباحًا كل يوم. تنفّذ الخدمة المهمة مرة واحدة ثم تخرج:
# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.shType=oneshot يخبر systemd أن البرنامج يعمل، ثم ينتهي وينقضي أمره، بدلًا من أن يبقى مقيمًا في الذاكرة. والمؤقت هو من يجدول ذلك:
# /etc/systemd/system/backup.timer
[Unit]
Description=Run the nightly backup
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.targetOnCalendar=*-*-* 03:00:00 تعني الساعة 3 صباحًا كل يوم. اختبر أي تعبير تقويمي بالأمر systemd-analyze calendar "*-*-* 03:00:00"، الذي يؤكد أن التعبير سليم ويطبع المرات القادمة التي سيُشغَّل فيها. وPersistent=true يشغّل المهمة الفائتة بمجرد أن يعود الخادم إلى العمل إن كان متوقفًا عند الساعة 3 صباحًا، وهو ما لا يستطيع cron فعله. لاحظ أن المؤقت يُفعَّل عبر timers.target، لا multi-user.target. فعّل المؤقت، لا الخدمة:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timersيعرض list-timers كل مؤقت مع آخر تشغيل له والتشغيل التالي، فترى بنظرة واحدة متى ستُشغَّل مهمتك تاليًا. والمولّد أعلاه يبني لك زوج .service و.timer معًا حين تفعّل وضع المؤقت. وبالمقارنة مع سطر cron، يمنحك المؤقت سجلّات حقيقية في الـ journal، ونفس توجيهات التحصين الموجودة في أي خدمة، وتعويض المهام الفائتة الذي يوفره Persistent=true. يبقى cron خيارًا جيدًا لمهمة بسيطة؛ أما المؤقت فهو الأداة الأفضل حين تصبح المهمة مهمة فعلًا.
FAQ
ما الفرق بين خدمة systemd ومهمة cron؟
الخدمة تُبقي برنامجًا طويل الأمد حيًّا: تبدأ عند الإقلاع، وتعيد التشغيل عند الفشل، وتسجّل في الـ journal. أما مهمة cron فتنفّذ أمرًا قصيرًا وفق جدول زمني ثم تخرج. وحين تريد الجدولة مع سجلّات journal وتحصين وتعويض للمهام الفائتة، استخدم مؤقت systemd، الذي يقرن جدول .timer بخدمة oneshot ويحل محل cron في معظم مهام الخادم.
أين أضع ملف خدمة systemd الخاص بي؟
ضع وحداتك الخاصة في /etc/systemd/system/، باسم ينتهي بـ .service. هذا المجلد مخصص للوحدات التي يضيفها المسؤول، وله أولوية على الوحدات التي توزّعها الحزم في /lib/systemd/system/. وبعد إنشاء ملف هناك أو تعديله، نفّذ sudo systemctl daemon-reload ليلتقط systemd التغيير.
كيف أجعل الخدمة تعيد التشغيل إذا انهارت؟
أضف Restart=on-failure وRestartSec=5 إلى قسم [Service]، ثم نفّذ sudo systemctl daemon-reload وأعد تشغيل الخدمة. يعيد systemd تشغيل البرنامج حين يخرج برمز غير صفري أو يموت بإشارة انهيار، منتظرًا خمس ثوانٍ بين المحاولات. اختبر ذلك بالأمر sudo systemctl kill -s SIGKILL myapp.service — فإشارة SIGTERM، وهي الإشارة الافتراضية، تُحتسب توقفًا نظيفًا ولا تستدعي on-failure — وراقب systemctl status وهو يعرض PID جديدًا خلال ثوانٍ قليلة.
كيف أشغّل خدمة systemd بمستخدم غير root؟
أنشئ حسابًا نظاميًا بالأمر sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp، ثم أضف User=myapp إلى قسم [Service]. أضف NoNewPrivileges=true وPrivateTmp=true وProtectSystem=strict حتى تعمل العملية بأقل قدر ممكن من الوصول. التشغيل بمستخدم غير مميّز الصلاحيات هو أهم تغيير على الإطلاق يمكنك إجراؤه لسلامة خدمتك.
لماذا فشلت خدمتي في البدء؟
نفّذ systemctl status myapp.service للحصول على الملخص، وjournalctl -u myapp.service للمخرجات الكاملة. أكثر الأسباب شيوعًا هي مسار خاطئ في ExecStart، أو WorkingDirectory مفقود، أو خطأ في الأذونات لأن User= لا يستطيع قراءة ملف، أو نسيان sudo systemctl daemon-reload بعد تعديل. يعرض الـ journal رسالة الخطأ الخاصة بالبرنامج نفسه، وهي غالبًا ما تسمّي المشكلة مباشرة.