วิธีตั้งค่า systemd service บน VPS
เรียนรู้วิธีสร้าง systemd service เพื่อให้โปรแกรมทำงานอัตโนมัติเมื่อ boot และ restart เมื่อ crash พร้อมวิธีใช้ timer และการจำกัดสิทธิ์เพื่อความปลอดภัยสูงสุด
systemd service คืออะไร และเหตุใดคุณจึงควรใช้งาน
systemd service คือไฟล์ข้อความขนาดเล็กที่ใช้กำหนดวิธีการทำงานของโปรแกรมบน server เช่น การสั่งให้โปรแกรมเริ่มทำงานเมื่อ boot เครื่อง, การสั่งให้ restart โปรแกรมหากเกิดการ crash และการส่ง output ของโปรแกรมไปยัง system log หน้าที่หลักมีเพียงเท่านี้ โปรแกรมที่เริ่มทำงานด้วยตนเองผ่าน SSH session จะหยุดทำงานทันทีที่คุณ log out หรือเมื่อ server reboot แต่โปรแกรมที่ถูกจัดการด้วย systemd service จะยังคงทำงานต่อไป เนื่องจากระบบ server เป็นผู้ดูแลโปรแกรมนั้นแทนที่ shell ของคุณ
systemd คือ init system บน Ubuntu, Debian, Fedora และ Linux server สมัยใหม่ส่วนใหญ่ โดยเป็น process แรกที่เริ่มทำงานและทำหน้าที่ควบคุม process อื่นๆ ทั้งหมด การเขียน service file คือการส่งโปรแกรมของคุณให้แก่ตัวควบคุมนี้ คู่มือนี้จะแสดงถึง unit ที่มีขนาดเล็กที่สุดที่สามารถใช้งานได้, ส่วนประกอบทั้ง 3 ส่วนที่มีอยู่ในทุก unit, วิธีการเปิดใช้งานและอ่าน log, วิธีการตั้งเวลาทำงานด้วย timer และวิธีการจำกัดสิทธิ์การทำงานให้มี privilege น้อยที่สุดเท่าที่เป็นไปได้
บริการที่มีขนาดเล็กที่สุดที่สามารถใช้งานได้
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 หมายถึงให้เริ่มทำงานเมื่อ server เข้าสู่โหมด multi-user operation ตามปกติ ซึ่งเป็นสาเหตุที่ทำให้โปรแกรมเริ่มทำงานตอน boot เครื่อง ส่วนรายละเอียดอื่น ๆ คือการปรับแต่งเพิ่มเติม
ส่วนประกอบทั้งสามส่วนและหน้าที่ของแต่ละส่วน
Unit file ทุกไฟล์จะถูกแบ่งออกเป็นส่วนต่างๆ ภายในเครื่องหมายวงเล็บเหลี่ยม โดย Service หนึ่งจะประกอบด้วยสามส่วนหลัก
[Unit] ใช้สำหรับอธิบายรายละเอียดของ Service และความสัมพันธ์ต่างๆ ส่วนประกอบสองบรรทัดที่คุณจะได้ใช้งานบ่อยที่สุดคือ:
[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 จะรันโปรแกรมด้วยสิทธิ์ของผู้ใช้ทั่วไปแทนที่จะเป็น root ซึ่งเป็นบรรทัดที่สำคัญที่สุดเพื่อความปลอดภัย ส่วน Restart=on-failure และ RestartSec=5 จะมีส่วนแยกเป็นของตนเองด้านล่าง เนื่องจากเป็นเหตุผลหลักที่คนส่วนใหญ่สร้าง Service ขึ้นมา
[Install] คือสิ่งที่เกิดขึ้นเมื่อคุณสั่ง enable service:
[Install]
WantedBy=multi-user.targetWantedBy=multi-user.target คือสิ่งที่เชื่อมโยง service เข้ากับการ boot ระบบเมื่อคุณรัน systemctl enable หากไม่มีส่วน [Install] Service จะสามารถสั่งรันด้วยตนเองได้ แต่จะไม่เริ่มทำงานโดยอัตโนมัติหลังจากรีบูตเครื่อง
เปิดใช้งานและตรวจสอบการทำงาน
หลังจากเขียนหรือแก้ไข unit file ใดๆ ให้ reload systemd เพื่อให้ระบบรับทราบการเปลี่ยนแปลง จากนั้นจึงสั่ง enable และ start service ในขั้นตอนเดียว:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.servicedaemon-reload คือขั้นตอนที่มักถูกละเลย เนื่องจาก systemd มีการทำ cache unit files การแก้ไขไฟล์จะไม่มีผลจนกว่าจะทำการ reload ส่วน enable --now จะทำการ enable service เพื่อให้เริ่มทำงานตอน boot และเริ่มทำงานในทันที ตรวจสอบสถานะได้ดังนี้:
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 หากต้องการอ่าน output ของโปรแกรม ให้ใช้ 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 จะเริ่มโปรแกรมใหม่เมื่อโปรแกรมจบการทำงานด้วย exit code ที่ไม่ใช่ 0 หรือหยุดทำงานเนื่องจาก crash signal เช่น SIGKILL หรือ SIGSEGV การจบการทำงานแบบปกติ หรือการหยุดโดย SIGTERM, SIGINT, SIGHUP หรือ SIGPIPE จะไม่ทำให้เกิดการเริ่มใหม่ RestartSec=5 จะรอ 5 วินาทีระหว่างการพยายามแต่ละครั้ง เพื่อป้องกันไม่ให้โปรแกรมที่ crash ทันทีทำงานวนซ้ำอย่างรวดเร็ว คุณสามารถทดสอบได้โดยการ kill process และสังเกตว่า systemd นำโปรแกรมกลับมาทำงานใหม่หรือไม่ ให้ใช้ SIGKILL เนื่องจากค่าเริ่มต้นของ SIGTERM ถือเป็นการหยุดทำงานแบบปกติ ดังนั้น on-failure จะไม่เริ่มบริการใหม่:
sudo systemctl kill -s SIGKILL myapp.service
sudo systemctl status myapp.serviceภายใน 5 วินาที สถานะจะแสดง Main PID และ active (running) ใหม่ นี่คือคุณสมบัติทั้งหมดของฟีเจอร์นี้ และเป็นเหตุผลที่การใช้ service ดีกว่าการปล่อยให้โปรแกรมทำงานใน tmux หรือ screen
รันโปรแกรมด้วยสิทธิ์ผู้ใช้ทั่วไปและเพิ่มความปลอดภัย
หากโปรแกรมถูกเจาะระบบ Service ที่รันด้วยสิทธิ์ root จะสามารถทำอันตรายใดก็ได้กับ server ของคุณ ควรเปลี่ยนไปรันด้วย user ของตัวเอง และใช้คำสั่งของ systemd เพื่อจำกัดขอบเขตการทำงาน ขั้นแรก ให้สร้าง system account ที่ไม่มีสิทธิ์ login และไม่มี home directory:
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 จะป้องกันไม่ให้ process ได้รับสิทธิ์ใหม่ แม้จะผ่าน setuid binary ก็ตาม PrivateTmp=true จะสร้าง /tmp ส่วนตัวที่ process อื่นไม่สามารถมองเห็นได้ ProtectSystem=strict จะทำให้ filesystem ทั้งหมดเป็นแบบ read-only ยกเว้น path ที่คุณกำหนดด้วย ReadWritePaths= และ ProtectHome=true จะทำการซ่อน /home จากโปรแกรมโดยสิ้นเชิง นี่คือหลักการ least-privilege เช่นเดียวกับการวาง service ไว้หลัง firewall คือการให้สิทธิ์เฉพาะสิ่งที่จำเป็นเท่านั้น หากคุณอ่านคู่มือเรื่อง การปิดช่องโหว่ IPv6 firewall บน VPS มาแล้ว นี่คือการดำเนินการในระดับ host ซึ่งใช้หลักการเดียวกัน สำหรับ service ที่ต้องเชื่อมต่อกับ internet ควรใช้การ hardening นี้ควบคู่กับ Fail2ban หน้า SSH และ firewall แบบ default-deny
แทนที่จะพิมพ์คำสั่งทั้งหมดด้วยตนเองซึ่งอาจทำให้จำคำสั่งผิดพลาด คุณสามารถสร้าง unit file ที่สมบูรณ์และปลอดภัยแล้วคัดลอกออกไปใช้งานได้ดังนี้:
Timers: the modern cron
systemd timer ทำงานตามกำหนดเวลาที่ตั้งไว้ และเป็นสิ่งที่มาแทนที่ cron job ในปัจจุบัน timer ประกอบด้วยไฟล์สองไฟล์ ได้แก่ .service ที่ทำหน้าที่ประมวลผล และ .timer ที่ระบุเวลาทำงาน สมมติว่าคุณต้องการสำรองข้อมูลตอน 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 mode เมื่อเทียบกับ cron แล้ว timer ให้ log จริงใน journal สามารถใช้คำสั่ง hardening ได้เหมือนกับ service ทั่วไป และมีระบบทำงานย้อนหลังสำหรับงานที่พลาดไปซึ่งจัดทำโดย Persistent=true การใช้ cron ยังคงใช้ได้ดีสำหรับงานทั่วไป แต่ timer เป็นเครื่องมือที่ดีกว่าเมื่อเป็นงานที่มีความสำคัญ
FAQ
ความแตกต่างระหว่าง systemd service และ cron job คืออะไร?
Service จะรักษาโปรแกรมให้ทำงานอยู่ตลอดเวลา โดยจะเริ่มทำงานเมื่อ boot ระบบ, เริ่มทำงานใหม่เมื่อเกิดข้อผิดพลาด และบันทึก log ลงใน journal ส่วน cron job จะรันคำสั่งระยะสั้นตามตารางเวลาแล้วจบการทำงาน หากต้องการการตั้งเวลาควบคู่ไปกับการใช้ journal logs, การทำ hardening และการทำงานย้อนหลังในกรณีที่พลาดรอบการทำงาน ให้ใช้ systemd timer ซึ่งเป็นการจับคู่ตารางเวลาของ .timer เข้ากับ service ของ oneshot และใช้แทน cron สำหรับงานส่วนใหญ่บน server
ฉันควรเก็บไฟล์ systemd service ไว้ที่ไหน?
เก็บ unit ที่สร้างขึ้นเองไว้ใน /etc/systemd/system/ โดยตั้งชื่อไฟล์ให้ลงท้ายด้วย .service ไดเรกทอรีนี้มีไว้สำหรับ unit ที่ผู้ดูแลระบบเพิ่มเข้ามา และจะมีลำดับความสำคัญสูงกว่า unit ที่มากับ package ใน /lib/systemd/system/ หลังจากสร้างหรือแก้ไขไฟล์แล้ว ให้รันคำสั่ง sudo systemctl daemon-reload เพื่อให้ systemd รับทราบการเปลี่ยนแปลง
ฉันจะทำให้ service เริ่มทำงานใหม่หากเกิดการ crash ได้อย่างไร?
เพิ่ม Restart=on-failure และ RestartSec=5 ลงในส่วน [Service] จากนั้นรัน sudo systemctl daemon-reload และ restart service นั้น systemd จะเริ่มโปรแกรมใหม่เมื่อโปรแกรมจบการทำงานด้วย exit code ที่ไม่ใช่ 0 หรือตายจาก crash signal โดยจะเว้นระยะห่างระหว่างการพยายามใหม่ 5 วินาที ทดสอบได้ด้วย sudo systemctl kill -s SIGKILL myapp.service ซึ่ง SIGTERM (signal เริ่มต้น) จะถือเป็นการหยุดทำงานแบบปกติและไม่กระตุ้นให้เกิด on-failure และสังเกตว่า systemctl status จะแสดง PID ใหม่ภายในไม่กี่วินาที
ฉันจะรัน systemd service ในฐานะผู้ใช้ที่ไม่ใช่ root ได้อย่างไร?
สร้าง system account ด้วย sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp จากนั้นเพิ่ม User=myapp ลงในส่วน [Service] เพิ่ม NoNewPrivileges=true, PrivateTmp=true และ ProtectSystem=strict เพื่อให้กระบวนการทำงานด้วยสิทธิ์ที่น้อยที่สุดเท่าที่จำเป็น การรันในฐานะ unprivileged user เป็นการเปลี่ยนแปลงที่สำคัญที่สุดในการเพิ่มความปลอดภัยให้กับ service
ทำไม service ของฉันถึงเริ่มทำงานไม่สำเร็จ?
รัน systemctl status myapp.service เพื่อดูสรุปผล และรัน journalctl -u myapp.service เพื่อดู output ทั้งหมด สาเหตุที่พบบ่อยที่สุดคือระบุ path ใน ExecStart ผิด, ขาด WorkingDirectory, ข้อผิดพลาดด้านสิทธิ์เนื่องจาก User= ไม่สามารถอ่านไฟล์ได้ หรือลืมรัน sudo systemctl daemon-reload หลังจากแก้ไขไฟล์ journal จะแสดงข้อความ error ของโปรแกรม ซึ่งมักจะระบุปัญหาโดยตรง