Về blog

Systemd Service Not Starting? Cách Sửa

14 tháng 9, 2026

Một service systemd không chịu khởi động hầu như chẳng bao giờ là điều bí ẩn. Chạy systemctl status <unit>, rồi đọc 50 dòng journal cuối của unit đó bằng journalctl -u <unit> -n 50 --no-pager — giữa hai lệnh đó, một trong năm nguyên nhân thường được nêu thẳng: đường dẫn sai, thiếu binary, quyền sai, SELinux/AppArmor từ chối, hoặc lỗi cú pháp file unit. Lý do lỗi nằm trong log; các cách sửa bên dưới chỉ là so khớp mẫu với nó.

Làm sao xem service systemd bị lỗi vì đâu?

Xem status trước, journal sau:

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

status cho bạn biết trạng thái (inactive (dead), failed (exit-code), activating (auto-restart)) cùng vài dòng log cuối. Journal cho bạn toàn bộ câu chuyện: stdout, stderr, và những lời than phiền của chính systemd về unit.

Nếu unit đã từng lỗi và bạn muốn bản ghi của lần đó, thêm -b cho lần boot hiện tại hoặc --since today:

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

Bộ công cụ đầy đủ — các lần boot, mức ưu tiên, theo dõi output trực tiếp — xem journalctl cheat sheet. Cùng một phản xạ bàn phím.

Một cặp lệnh nữa đáng biết:

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

Lệnh đầu liệt kê mọi unit đỏ trên máy. Lệnh sau xóa trạng thái failed sau khi bạn đã sửa — chuyện thẩm mỹ, nhưng nó khiến các trang status đỡ báo động giả.

Những nguyên nhân phổ biến nhất là gì?

Sau khi log gọi tên triệu chứng, nguyên nhân gần như luôn là một trong năm thứ này:

Log báoNguyên nhân khả dĩCách sửa
status=203/EXECĐường dẫn ExecStart= sai hoặc thiếu interpreterĐường dẫn tuyệt đối, chmod +x, kiểm tra shebang
status=203/EXEC trên scriptScript dùng kết thúc dòng CRLF hoặc shebang saidos2unix script.sh, sửa dòng đầu tiên
status=1/FAILURE, app không log gìThiếu working directory hoặc biến môi trườngĐặt WorkingDirectory=, thêm Environment=
Permission deniedUser không đọc được file hoặc không bind được portSửa ownership; port dưới 1024 cần AmbientCapabilities=CAP_NET_BIND_SERVICE hoặc root
Unit is maskedCó người đã chạy systemctl masksystemctl unmask myapp.service
Sửa file unit “mà chẳng có tác dụng”Chưa reload daemonsystemctl daemon-reload

Nhà 203/EXEC xứng đáng được nhắc riêng vì nó là thứ nuốt cả buổi chiều. Systemd không dùng shell của bạn để chạy ExecStart=. Điều đó có nghĩa:

# Sai — không có shell, ~ không bao giờ được expand, không tra PATH
ExecStart=~/app/run.sh

# Đúng
ExecStart=/opt/app/run.sh

Và bản thân script phải executable và bắt đầu bằng một shebang thật (#!/bin/bash hoặc #!/usr/bin/env bash). Script chạy ngon từ terminal của bạn nhưng lỗi 203 dưới systemd hầu như luôn rơi vào một trong các trường hợp: không executable, kết thúc dòng CRLF, shebang trỏ vào hư không, hoặc đường dẫn tương đối.

Vì sao chạy thủ công được mà boot lên thì không?

Lỗi thứ tự kinh điển. Nếu log báo lỗi ngay sau boot nhưng unit lại khởi động ngon khi bạn systemctl start thủ công, service của bạn đang thua một cuộc đua — nó với lấy network, một ổ đĩa vừa mount, hoặc một database trước khi thứ đó tồn tại.

Cách sửa là khai báo dependency thay vì cầu nguyện:

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

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

After= sắp thứ tự khởi động; Wants= khiến systemd thực sự bật dependency lên. network-online.target chỉ có tác dụng nếu service network-wait được bật trên distro của bạn, nên kiểm tra systemctl is-enabled NetworkManager-wait-online.service (hoặc tương đương của systemd-networkd). Cái guard ExecStartPre= là cách rẻ tiền và trung thực để fail ầm ĩ bằng một thông báo đọc được thay vì một stack trace.

Biến thể thứ hai: service khởi động lúc boot rồi chết ngay. Tìm những thứ môi trường tương tác của bạn có mà boot không có — khác biệt PATH, một virtualenv, một HOME. Đặt rõ những gì bạn cần:

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

Vì sao service của tôi không ghi log vào journal?

Nếu journalctl -u chẳng hiện gì, kiểm tra ba thứ theo thứ tự:

  1. StandardOutput=StandardError= trong unit — chúng phải là journal (mặc định) hoặc journal+console. Có thể ai đó đã đặt chúng thành null.
  2. App ghi ra file thay vì stdout. Systemd chỉ bắt stdout/stderr; ứng dụng ghi log ra file đi vòng qua journal hoàn toàn. Hoặc trỏ app về stdout, hoặc đọc file đó.
  3. Giới hạn dung lượng đã vứt các dòng cũ: xem journalctl --disk-usage, và SystemMaxUse= trong /etc/systemd/journald.conf nếu journal đang nghẹn.

Để debug chính lúc khởi động, không gì bằng nhét một shell vào đúng context của lúc boot:

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

Hoặc, với các vụ thực sự bí ẩn, chạy chính xác lệnh ExecStart= bằng user của unit trong một shell — phần lớn khác biệt môi trường lộ diện trong mười giây đầu.

Làm sao để nó tự restart sau khi crash?

Chính sách mặc định là Restart=no: service chết nằm yên, và bạn biết tin qua một user. Sửa điều đó cho từng service:

[Service]
Restart=on-failure
RestartSec=5
Giá trịRestart khi nào
no (mặc định)Không bao giờ
on-failureExit khác 0, nhận signal, timeout
alwaysMọi lần exit, kể cả sạch sẽ
on-watchdogChỉ khi watchdog timeout

Đi kèm Restart=on-failure với một guard giới hạn tần suất khởi động để vòng lặp crash không đập nát cái máy: StartLimitIntervalSec=StartLimitBurst= trong section [Unit]. Năm lần lỗi trong sáu mươi giây thì nên gọi người, không phải quay CPU.

Nếu bạn đang lắp cái này thành một job định thời thay vì daemon, hãy cân nhắc cron vs systemd timer trước — timer cho bạn log journal và thứ tự dependency miễn phí, đúng cái mà bài này cứ phải với tới suốt.

Checklist 60 giây

  1. systemctl status <unit> — đọc trạng thái và vài dòng cuối.
  2. journalctl -u <unit> -n 50 --no-pager — tìm lỗi thật.
  3. 203/EXEC? Sửa path, shebang, quyền. Permission denied? Sửa user và ownership của file.
  4. Chỉ lỗi lúc boot? Thêm After=/Wants=network-online.target và một guard ExecStartPre=.
  5. Vừa sửa file unit? systemctl daemon-reload && systemctl restart <unit>.
  6. Thêm Restart=on-failure để lần crash sau tự lên tiếng thay vì lẩn trốn.

Phần lớn khoảnh khắc “systemd rắc rối thật” quy về lỗi nằm trong journal từ đầu đến giờ. Đọc nó trước khi sửa file unit, không phải sau.

— mrsaynothing

— mrsaynothing

Ghi chú thực địa về AI, Linux và self-hosting.

Thảo luận bài này trên dev.to dev.to ↗

Nhận how-to tiếp theo qua email

Một email mỗi bài viết. Sửa xong rồi đi tiếp.

self-hosted · không bên thứ ba · hủy đăng ký một cú bấm

cái này là gì?

GGUF Quantization: Nên Chọn Mức Nào?

Thích kiểu viết này? Tôi build như vậy để kiếm sống. thuê tôi