איך להריץ תוכנית כשירות systemd ב-VPS
מדריך מעשי להגדרת systemd service ב-Linux. למדו איך ליצור קובץ unit, להגדיר restart במקרה של crash, להשתמש ב-timer ולנהל לוגים דרך journalctl.
מהו systemd service ומדוע כדאי להשתמש בו
systemd service הוא קובץ טקסט קטן המורה לשרת כיצד להריץ תוכנית: להפעיל אותה בעת עליית המערכת, להפעיל אותה מחדש אם היא קורסת, ולשלוח את הפלט שלה ליומן המערכת (system log). זהו כל תפקידו. תוכנית שמפעילים ידנית בסשן SSH תפסיק לפעול ברגע שמתנתקים מהמערכת או כשהשרת עובר reboot. תוכנית שעטופה ב-systemd service תמשיך לפעול, מכיוון שהשרת עצמו מנהל אותה ולא ה-shell של המשתמש.
systemd היא מערכת ה-init ב-Ubuntu, Debian, Fedora וברוב שרתי ה-Linux המודרניים. זהו התהליך הראשון שמתחיל והוא זה שמפקח על כל שאר התהליכים. כשכותבים קובץ service, מעבירים את התוכנית למפקח זה. מדריך זה מציג את היחידה (unit) המינימלית שעובדת, את שלושת הסעיפים הקיימים בכל unit, כיצד להפעיל אותה ולקרוא את הלוגים שלה, כיצד להריץ אותה לפי לו"ז באמצעות timer, וכיצד להגביל אותה כך תרוץ עם מינימום הרשאות אפשרי.
השירות המינימלי שעובד
קובץ שירות (service file) נמצא ב-/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 פירושו הפעלה ברגע שהשרת מגיע למצב עבודה מרובה-משתמשים (multi-user operation) רגיל, מה שגורם לו לעלות בזמן העלייה של המערכת (boot). כל שאר ההגדרות הן לשיפור ודיוק.
שלושת הסעיפים, ותפקיד כל אחד מהם
כל קובץ unit מחולק לסעיפים בתוך סוגריים מרובעים. שירות (service) משתמש בשלושה סעיפים.
[Unit] מתאר את השירות ואת הקשרים שלו. שתי השורות שתשתמש בהן הכי הרבה הן:
[Unit]
Description=My application
After=network-online.target
Wants=network-online.targetDescription הוא התווית עבור בני אדם שמוצגת ב-systemctl status. After=network-online.target מורה ל-systemd לא להפעיל את התוכנית שלך עד שהרשת תהיה זמינה, דבר שחשוב לכל תוכנה שמקשרת ל-port או יוצרת חיבור יוצא.
[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 מריץ את התוכנית כמשתמש ללא הרשאות (unprivileged account) במקום כ-root, וזו השורה החשובה ביותר מבחינת אבטחה. Restart=on-failure ו-RestartSec=5 מופיעים בסעיף משלהם בהמשך, מכיוון שהם הסיבה שרוב האנשים כותבים שירות أصلاً.
[Install] מגדיר מה קורה כאשר מפעילים (enable) את השירות:
[Install]
WantedBy=multi-user.targetWantedBy=multi-user.target מקשר את השירות לתהליך העלייה של המערכת (boot) כאשר מריצים את systemctl enable. ללא סעיף [Install], ניתן להפעיל את השירות באופן ידני, אך הוא לא יעלה מעצמו לאחר הפעלה מחדש (reboot).
הפעל את השירות ועקוב אחר הפעילות
לאחר כתיבה או עריכה של קובץ unit כלשהו, יש לבצע reload ל-systemd כדי שיקרא את השינוי, ולאחר מכן להפעיל (enable) ולהריץ (start) את השירות בשלב אחד:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.servicedaemon-reload הוא השלב שמשתמשים נוטים לשכוח: systemd שומר cache לקובצי unit, לכן עריכה לא תתפוס עד שתבצע reload. enable --now מבצע גם enable לשירות לצורך עלייה במערכת וגם מפעיל אותו באופן מיידי. בדוק זאת:
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 את הפלט עבור unit זה בלבד:
sudo journalctl -u myapp.service -fה--f עוקב אחר שורות חדשות ברגע שהן מגיעות, בדומה ל-tail -f. כל מה שהתוכנית כותבת ל-standard output או ל-standard error יופיע כאן, ללא צורך בהגדרות logging מצדך.
הפעלה מחדש عند כשל, הסיבה שבגללה הגעת לכאן
היתרון העיקרי של service הוא ש-systemd מפעיל מחדש את התוכנית כאשר היא קורסת. שתי שורות מבצעות זאת:
[Service]
Restart=on-failure
RestartSec=5Restart=on-failure מפעיל מחדש את התוכנית כאשר היא יוצאת עם קוד שאינו 0 או קורסת עקב אות (signal) כמו SIGKILL או SIGSEGV. יציאה מסודרת, או עצירה על ידי SIGTERM, SIGINT, SIGHUP, או SIGPIPE, אינן מפעילות את המנגנון. RestartSec=5 ממתין חמש שניות בין ניסיונות, כדי שתוכנית שקורסת באופן מיידי לא תרוץ בלולאה אינסופית. ניתן לוודא זאת על ידי הרגית (killing) של התהליך ומעקב אחר ההפעלה מחדש של systemd. השתמש ב-SIGKILL: מכיוון ש-SIGTERM נחשב כעצירה מסודרת, on-failure לא יפעיל מחדש את ה-service:
sudo systemctl kill -s SIGKILL myapp.service
sudo systemctl status myapp.serviceבתוך חמש שניות, ה-status יציג Main PID ו-active (running) חדשים שוב. זהו כל המנגנון, וזו הסיבה ש-service עדיף על הרצת תוכנית בתוך 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 מונעת מהתהליך לקבל הרשאות חדשות, גם דרך binary מסוג setuid. PrivateTmp=true מעניקה לו /tmp פרטי ששום תהליך אחר לא יוכל לראות. ProtectSystem=strict הופכת את כל מערכת הקבצים לקריאה בלבד (read-only), למעט נתיבים ספציפיים שתגדיר באמצעות ReadWritePaths=. ProtectHome=true מסתירה את /home לחלוטין. זוהי גישת ה-least-privilege, בדומה להצבת שירות מאחורי firewall: ספק לו רק את מה שהוא זקוק לו. אם קראת את המדריך על סגירת פרצות ה-IPv6 firewall ב-VPS, זהו החלק המבוצע על המארח (on-host) של אותה גישה. עבור שירות החשוף לאינטרנט, שלב הגנה זו עם Fail2ban לפני SSH ו-firewall במצב default-deny.
במקום להקליד את כל התוכן ידנית ולהטעות בהנחיות, צור unit מלא ומאובטח והעתק אותו:
Timers: the modern cron
systemd timer מריץ service על פי לו"ז, והוא המחליף המודרני ל-cron job. timer מורכב משני קבצים: .service שמבצע את העבודה, ו-.timer שקובע מתי היא תתבצע. נניח שברצונכם לבצע backup בכל יום בשעה 3am. ה-service מבצע את המשימה פעם אחת ויוצא:
# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.shType=oneshot מעדכן את systemd שהתוכנית רצה, מסיימת ונסגרת, במקום להישאר פעילה בזיכרון. ה-timer קובע את לוח הזמנים:
# /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 פירושו 3am בכל יום. ניתן לבדוק כל ביטוי לוח שנה באמצעות systemd-analyze calendar "*-*-* 03:00:00", שמאשר שהביטוי נכון ומציג את המועדים הבאים שבהם המשימה תופעל. Persistent=true מריץ משימה שהוחסרה ברגע שהשרת חוזר לפעילות (אם הוא היה כבוי ב-3am), יכולת ש-cron אינו מסוגל לבצע. שימו לב ש-timer מופעל באמצעות timers.target ולא באמצעות multi-user.target. יש להפעיל את ה-timer, לא את ה-service:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timerslist-timers מציג כל timer יחד עם מועד ההפעלה הבא והאחרון, כך שניתן לראות במבט חטוף מתי המשימה תופעל בהמשך. ה-generator לעיל יוצר עבורכם את ה-.service וה-.timer המשלימים ברגע שמפעילים את מצב ה-timer. בהשוואה לשורת cron, timer מספק לכם logs אמיתיים ב-journal, את אותן הנחיות hardening כמו בכל service, ואת היכולת להשלים משימות שהוחסרו ש-Persistent=true מספק. cron עדיין מתאים למשימות פשוטות; timer הוא הכלי הטוב יותר עבור משימות קריטיות.
FAQ
מה ההבדל בין systemd service לבין cron job?
service שומר על תוכנית רצה לאורך זמן: הוא עולה בעת עליית המערכת, מופעל מחדש במקרה של כשל, ושולח לוגים ל-journal. cron job מריץ פקודה קצרה בלוח זמנים מסוים ואז יוצא. כאשר נדרש תזמון אך גם נדרשים לוגים ב-journal, הגדרות אבטחה (hardening) ויכולת השלמה של הרצות שהוחלתו, יש להשתמש ב-systemd timer. הוא מחבר לוח זמנים של .timer עם service מסוג oneshot ומחליף את cron ברוב משימות השרת.
היכן יש למקם את קובץ ה-systemd service שלי?
יש למקם unit-ים מותאנים אישית ב-/etc/systemd/system/, עם שם המסתים ב-.service. ספרייה זו מיועדת ליחידות שהמנהל מוסיף, והיא מקדימה יחידות המגיעות עם חבילות ב-/lib/systemd/system/. לאחר יצירה או עריכה של קובץ במקום זה, יש להריץ את sudo systemctl daemon-reload כדי ש-systemd יזהה את השינוי.
איך גורמים ל-service להפעיל את עצמו מחדש אם הוא קורס?
יש להוסיף את Restart=on-failure ו-RestartSec=5 תחת סעיף [Service], ולאחר מכן להריץ את sudo systemctl daemon-reload ולהפעיל מחדש את ה-service. systemd מפעיל מחדש את התוכנית כאשר היא יוצאת עם קוד שאינו 0 או קורסת עקב אות (signal) של קריסה, וממתינה חמש שניות בין ניסוי לניסוי. ניתן לבדוק זאת באמצעות sudo systemctl kill -s SIGKILL myapp.service — SIGTERM, האות ברירת המחדל, נחשב כעצירה מסודרת ואינו מפעיל את on-failure — ועקוב אחר systemctl status המציג PID חדש תוך שניות ספורות.
איך מריצים systemd service כמשתמש שאינו root?
צור חשבון מערכת באמצעות sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp, ולאחר מכן הוסף את User=myapp לסעיף [Service]. הוסף את NoNewPrivileges=true, PrivateTmp=true, ו-ProtectSystem=strict כדי שהתהליך ירוץ עם מינימום הרשאות נדרשות. הרצה כמשתמש ללא הרשאות מנהל היא השינוי החשוב ביותר שניתן לבצע לשיפור בטיחות ה-service.
מדוע ה-service שלי נכשל בעלייה?
הרץ את systemctl status myapp.service לקבלת סיכום ואת journalctl -u myapp.service לקבלת הפלט המלא. הסיבות הנפוצות ביותר הן נתיב שגוי ב-ExecStart, חסרונה של WorkingDirectory, שגיאת הרשאות מכיוון ש-User= אינו יכול לקרוא קובץ, או שכחה של sudo systemctl daemon-reload לאחר עריכה. ה-journal מציג את הודעת השגיאה של התוכנית עצמה, אשר בדרך כלל מציינת את הבעיה באופן ישיר.