Hướng Dẫn Tạo Systemd Service Trên VPS Ubuntu Cho Ứng Dụng Chạy 24/7

Hướng dẫn tạo systemd service trên VPS Ubuntu để ứng dụng tự chạy khi boot, tự restart khi lỗi, quản lý log bằng journalctl và chạy bằng user riêng thay vì root.

Khi chạy Node.js, Python, Go hoặc một ứng dụng tự viết trên VPS Ubuntu, mở chương trình bằng terminal rồi để đó không phải cách vận hành ổn định. Nếu phiên SSH bị đóng, tiến trình crash hoặc VPS reboot, ứng dụng có thể dừng hoàn toàn. Systemd cho phép biến ứng dụng thành một service được hệ điều hành quản lý: tự khởi động khi boot, tự restart theo chính sách, chạy bằng user riêng, nhận biến môi trường và ghi log vào journal. Bài viết này hướng dẫn từ cấu trúc unit file, `ExecStart`, `WorkingDirectory`, `User`, `Restart`, `EnvironmentFile`, `daemon-reload`, `enable`, `journalctl` đến cách xử lý lỗi `status=203/EXEC`, restart loop và quyền file.

Điểm quan trọng Hãy lựa chọn dịch vụ dựa trên nhu cầu sử dụng thực tế và theo dõi tài nguyên thường xuyên để có hiệu quả tốt nhất.

Nếu bạn đang chạy một ứng dụng bằng lệnh như:

node app.js

hoặc:

python3 app.py

thì tiến trình đó thường phụ thuộc vào phiên terminal hiện tại. Khi SSH bị đóng, VPS reboot hoặc ứng dụng crash, chương trình có thể ngừng chạy.

Trên Ubuntu Server hiện đại, cách tiêu chuẩn để quản lý daemon/service là systemd. Bạn có thể tạo một file .service để systemd chịu trách nhiệm start, stop, restart, tự chạy sau boot, quản lý user và ghi log.

Nếu bạn mới làm quen terminal, hãy đọc trước bài Các lệnh Linux cơ bản trên VPS Ubuntu. Nếu ứng dụng chạy trong container, bài Cài Docker và Docker Compose trên VPS Ubuntu có thể phù hợp hơn.

1. Systemd là gì?

Systemd là service manager và init system được Ubuntu Server hiện đại sử dụng để quản lý:

  • service;
  • timer;
  • mount;
  • socket;
  • target;
  • dependency lúc boot.

Với ứng dụng tự viết, bạn thường tạo service unit có đuôi .service.

2. Vì sao nên dùng systemd thay vì chạy app thủ công?

Systemd giúp:

  • tự chạy app sau reboot;
  • tự restart khi process lỗi;
  • chạy bằng user không phải root;
  • kiểm soát working directory;
  • truyền biến môi trường;
  • xem log bằng journalctl;
  • giới hạn tài nguyên nếu cần;
  • quản lý dependency với network hoặc service khác.

3. Ví dụ ứng dụng trong bài

Giả sử ứng dụng nằm ở:

/opt/myapp

và file chạy chính là:

/opt/myapp/app.py

User chạy ứng dụng:

appuser

Bạn có thể thay bằng Node.js, Go binary hoặc lệnh thật của ứng dụng.

4. Tạo user riêng cho service

Không nên chạy mọi ứng dụng bằng root.

Tạo system user:

sudo useradd --system --home /opt/myapp --shell /usr/sbin/nologin appuser

Đặt ownership:

sudo chown -R appuser:appuser /opt/myapp

Nếu ứng dụng cần truy cập file ở nơi khác, hãy cấp đúng quyền cần thiết thay vì đưa user vào sudo.

5. Kiểm tra ứng dụng chạy thủ công trước

Trước khi tạo service, chạy đúng lệnh bằng user mục tiêu:

sudo -u appuser /usr/bin/python3 /opt/myapp/app.py

Nếu lệnh này không chạy được, systemd cũng sẽ không sửa được lỗi của ứng dụng.

6. Xác định đường dẫn executable tuyệt đối

Dùng:

which python3

Ví dụ:

/usr/bin/python3

Với Node.js:

which node

Systemd nên dùng đường dẫn rõ ràng trong ExecStart.

7. Tạo file service

Tạo:

sudo nano /etc/systemd/system/myapp.service

Ví dụ cơ bản:

[Unit]
Description=My Application
After=network.target

[Service]
Type=exec
User=appuser
Group=appuser
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/python3 /opt/myapp/app.py
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

8. Giải thích phần [Unit]

[Unit]
Description=My Application
After=network.target

Description giúp hiển thị tên dễ hiểu.

After=network.target chỉ xác định thứ tự tương đối với network target; nó không đảm bảo Internet bên ngoài đã sẵn sàng hoàn toàn.

Nếu ứng dụng cần network-online, có thể cân nhắc dependency phù hợp hơn nhưng cần hiểu network stack của VPS.

9. Type=exec là gì?

Systemd hỗ trợ nhiều Type như:

  • simple;
  • exec;
  • forking;
  • oneshot;
  • notify;
  • dbus.

Với ứng dụng foreground hiện đại, Type=exec hoặc simple thường phù hợp.

Tài liệu systemd hiện khuyến nghị Type=exec trong nhiều trường hợp vì service manager chỉ coi bước exec thành công khi executable thật sự được gọi.

10. User và Group

User=appuser
Group=appuser

Service sẽ chạy bằng quyền của user này thay vì root.

Đây là một lớp bảo mật rất quan trọng.

11. WorkingDirectory

WorkingDirectory=/opt/myapp

Nếu ứng dụng dùng path tương đối như:

./config.json

thì working directory phải đúng.

Nếu thiếu, ứng dụng có thể chạy nhưng không tìm thấy file config.

12. ExecStart

ExecStart=/usr/bin/python3 /opt/myapp/app.py

Đây là lệnh systemd chạy khi service start.

Với Node.js:

ExecStart=/usr/bin/node /opt/myapp/server.js

Với binary Go:

ExecStart=/opt/myapp/myserver

13. Restart=on-failure

Restart=on-failure
RestartSec=5

on-failure giúp restart khi process exit lỗi hoặc bị kết thúc bất thường theo các điều kiện systemd hỗ trợ.

Không nên dùng Restart=always máy móc cho mọi service vì có thể tạo restart loop vô hạn ngay cả khi ứng dụng tự exit hợp lệ.

14. Bật service sau boot

Sau khi tạo file:

sudo systemctl daemon-reload

Start:

sudo systemctl start myapp

Kiểm tra:

sudo systemctl status myapp

Nếu ổn:

sudo systemctl enable myapp

Hoặc gộp:

sudo systemctl enable --now myapp

15. Vì sao phải chạy daemon-reload?

Systemd giữ cache unit file.

Sau khi tạo hoặc sửa file .service, chạy:

sudo systemctl daemon-reload

để systemd đọc lại unit definition.

16. Xem log bằng journalctl

sudo journalctl -u myapp

100 dòng cuối:

sudo journalctl -u myapp -n 100

Theo dõi realtime:

sudo journalctl -u myapp -f

Log từ boot hiện tại:

sudo journalctl -u myapp -b

17. stdout và stderr đi đâu?

Mặc định trong nhiều cấu hình systemd, stdout/stderr của service được gửi vào journal.

Bạn không cần redirect > app.log 2>&1 trong ExecStart nếu muốn dùng journal.

18. Biến môi trường trực tiếp trong service

Ví dụ:

Environment="APP_ENV=production"
Environment="PORT=8080"

Nhưng không nên đặt secret nhạy cảm trực tiếp trong unit file nếu file dễ được nhiều user đọc.

19. Dùng EnvironmentFile

Tạo:

sudo nano /etc/myapp.env

Ví dụ:

APP_ENV=production
PORT=8080
DATABASE_URL=mysql://...

Giới hạn quyền:

sudo chown root:appuser /etc/myapp.env
sudo chmod 640 /etc/myapp.env

Trong service:

EnvironmentFile=/etc/myapp.env

Sau đó:

sudo systemctl daemon-reload
sudo systemctl restart myapp

20. Không dùng shell syntax trực tiếp trong ExecStart

Systemd không chạy ExecStart qua shell theo cách bạn gõ trong terminal.

Ví dụ này không nên dùng trực tiếp:

ExecStart=cd /opt/myapp && python3 app.py

Hãy dùng:

WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/python3 /opt/myapp/app.py

Nếu thật sự cần shell, phải gọi rõ:

ExecStart=/bin/bash -lc '...'

nhưng nên tránh nếu không cần.

21. Lỗi status=203/EXEC

Đây là lỗi phổ biến khi executable không chạy được.

Kiểm tra:

  • đường dẫn trong ExecStart;
  • file có tồn tại không;
  • permission execute;
  • shebang;
  • architecture binary.

Ví dụ:

ls -l /opt/myapp/myserver
file /opt/myapp/myserver

22. Service restart liên tục

Kiểm tra:

sudo systemctl status myapp
sudo journalctl -u myapp -n 200

Có thể app crash ngay khi start và Restart=on-failure làm nó lặp lại.

Sửa lỗi app trước thay vì chỉ tăng RestartSec.

23. Start limit

Systemd có cơ chế rate limit restart để tránh vòng lặp quá nhanh.

Nếu service báo start request repeated too quickly, hãy sửa lỗi gốc rồi chạy:

sudo systemctl reset-failed myapp
sudo systemctl start myapp

24. Service cần database khởi động trước

Bạn có thể thêm:

After=mysql.service

và tùy trường hợp:

Requires=mysql.service

Nhưng dependency chỉ đảm bảo quan hệ service, không đảm bảo database đã sẵn sàng nhận query.

Ứng dụng vẫn nên có retry connection.

25. Restart service sau khi deploy

Sau khi update source:

sudo systemctl restart myapp

Kiểm tra:

sudo systemctl status myapp

Không cần daemon-reload nếu chỉ thay source, không thay unit file.

26. Reload thay restart được không?

Chỉ khi ứng dụng có cơ chế reload config riêng và unit có ExecReload phù hợp.

Nếu không, systemctl reload có thể không hoạt động.

27. Graceful stop

Khi systemctl stop myapp, systemd gửi signal theo cấu hình service.

Ứng dụng nên bắt SIGTERM và shutdown sạch:

  • đóng connection;
  • flush queue;
  • đóng file;
  • hoàn tất request đang xử lý.

28. TimeoutStopSec

Nếu app cần thời gian shutdown:

TimeoutStopSec=30

Sau timeout, systemd có thể buộc kết thúc process theo policy.

29. Chạy service Node.js

Ví dụ:

[Unit]
Description=Node API
After=network.target

[Service]
Type=exec
User=nodeapp
Group=nodeapp
WorkingDirectory=/srv/node-api
Environment=NODE_ENV=production
ExecStart=/usr/bin/node /srv/node-api/server.js
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

30. Chạy Python virtualenv

Nếu virtualenv ở:

/opt/myapp/venv

thì dùng trực tiếp interpreter:

ExecStart=/opt/myapp/venv/bin/python /opt/myapp/app.py

Không cần source venv/bin/activate.

31. Chạy Gunicorn

Ví dụ:

ExecStart=/opt/myapp/venv/bin/gunicorn --bind 127.0.0.1:8000 wsgi:app

Nginx có thể reverse proxy tới 127.0.0.1:8000.

32. Giới hạn file descriptor

Nếu app cần nhiều connection:

LimitNOFILE=65535

Chỉ tăng khi workload cần và ứng dụng hỗ trợ.

33. Bảo mật systemd service

Systemd hỗ trợ nhiều hardening option như:

NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true

Nhưng các option này có thể làm app không đọc/ghi được nơi cần thiết.

Nên bật từng bước và test thay vì copy một block hardening lớn.

34. Kiểm tra unit file

Bạn có thể dùng:

systemd-analyze verify /etc/systemd/system/myapp.service

Lệnh này giúp phát hiện một số lỗi trong unit file.

35. Xem unit file systemd đang dùng

systemctl cat myapp

Điều này hữu ích nếu có override/drop-in.

36. Xem cấu hình runtime

systemctl show myapp

Bạn có thể lọc:

systemctl show myapp -p User -p ExecStart -p Restart

37. Dùng drop-in để override service package

Nếu service do package cung cấp, không nên sửa trực tiếp file trong /lib/systemd/system hoặc /usr/lib/systemd/system.

Dùng:

sudo systemctl edit ten-service

Systemd sẽ tạo override trong /etc/systemd/system/...d/.

38. Tình huống thực tế: app tự chạy sau reboot

Sau khi enable:

sudo reboot

Đăng nhập lại:

systemctl status myapp

Nếu active (running), service đã tự khởi động.

39. Tình huống thực tế: app crash giữa đêm

Với:

Restart=on-failure
RestartSec=5

systemd có thể tự start lại process.

Nhưng bạn vẫn cần monitoring để biết app đã crash. Auto restart không thay thế alert.

40. Kết hợp với Uptime Kuma

Bạn có thể monitor HTTP endpoint của app bằng Uptime Kuma.

Systemd lo process lifecycle, Uptime Kuma lo availability monitoring. Hai lớp này bổ sung cho nhau.

41. Backup unit file

Các file quan trọng:

/etc/systemd/system/myapp.service
/etc/myapp.env

Nên đưa unit file vào repository infrastructure/deploy nếu không chứa secret.

42. Xóa service

sudo systemctl disable --now myapp
sudo rm /etc/systemd/system/myapp.service
sudo systemctl daemon-reload
sudo systemctl reset-failed

Sau đó xóa app/user nếu không còn dùng.

43. Checklist tạo systemd service

  • App chạy thủ công bằng đúng user.
  • Có user riêng.
  • WorkingDirectory đúng.
  • ExecStart dùng path chính xác.
  • Restart phù hợp.
  • daemon-reload sau khi sửa unit.
  • systemctl enable nếu cần boot startup.
  • journalctl đọc được log.
  • Reboot test.
  • Có monitoring.

44. FAQ về systemd service trên VPS Ubuntu

systemd có thay PM2 không?

Không hoàn toàn. PM2 có tính năng riêng cho Node.js, còn systemd là service manager cấp hệ điều hành. Bạn có thể dùng trực tiếp systemd hoặc kết hợp tùy kiến trúc.

Có cần chạy app bằng root không?

Không, trừ khi có lý do đặc biệt. Nên dùng user riêng.

enable khác start thế nào?

start chạy service ngay. enable cấu hình để service được khởi động theo target khi boot.

Có cần daemon-reload mỗi lần restart app không?

Không. Chỉ cần khi unit file/drop-in thay đổi.

Restart=always có tốt không?

Không phải lúc nào. on-failure thường hợp lý hơn cho nhiều daemon vì tránh restart khi process tự exit hợp lệ.

Log service nằm ở đâu?

Có thể xem bằng:

journalctl -u ten-service

Kết luận

Tạo systemd service trên VPS Ubuntu giúp ứng dụng chạy ổn định hơn nhiều so với giữ một terminal SSH mở. Bạn có thể tự động start sau boot, restart khi lỗi, quản lý log và giảm quyền bằng user riêng.

Điểm quan trọng là chạy app foreground, dùng ExecStart chính xác, kiểm tra log khi lỗi và không lạm dụng root hoặc restart loop.

Nếu bạn đang triển khai API, bot hợp lệ, worker hoặc backend 24/7 trên VPS Ubuntu tại HUONG CLOUD, systemd là nền tảng quản lý process nên được thiết lập ngay từ đầu.

Nguồn tham khảo

Kết luận

Nếu chưa chắc cấu hình hoặc loại dịch vụ nào phù hợp, hãy gửi nhu cầu thực tế để đội ngũ hỗ trợ tư vấn trước khi mua.

Chưa biết nên chọn gói nào?

Xem bảng giá VPS hoặc liên hệ đội ngũ hỗ trợ để chọn cấu hình phù hợp.

Xem bảng giá VPS →