SSD Nodes Learn Hosting plans →
Panduan Matt ConnorOleh Matt Connor · Diperbarui 2026-08-29

Memilih Type= systemd: simple, forking, notify

Unit tetap aktif saat daemon mati? Pahami perbedaan Type=simple, exec, forking, oneshot, dan notify, lalu temukan PID utama yang benar.

Mengapa systemd melaporkan unit sebagai aktif ketika prosesnya sudah mati

Unit service systemd tetap active selama satu proses yang disebut systemd sebagai proses utama masih hidup. Nilai Type= pada bagian [Service] menentukan proses tersebut. Jika nilainya salah, systemd dapat memantau shell wrapper atau proses induk berumur pendek, sementara daemon yang Anda perlukan mati di dalam unit yang sama. Unit tersebut melaporkan kondisi proses yang diperintahkan untuk dipantaunya.

Mengubah kebijakan restart tidak akan membantu dalam kasus ini. Restart= dijalankan ketika proses utama berhenti. Karena itu, Restart=always tidak pernah dijalankan selama PID utama (process identifier) masih merujuk ke sesuatu yang tetap berjalan. Perbaiki Type= terlebih dahulu. Tindakan systemd setelah proses utama benar-benar berhenti merupakan keputusan terpisah, yang dibahas dalam panduan tentang Restart= dan RestartSec=.

Apa yang sebenarnya ditentukan oleh Type=

Setiap nilai Type= menjawab dua pertanyaan sekaligus. Kapan systemd boleh menganggap unit ini sudah dimulai, dan proses mana yang menjadi proses utama.

Jawaban pertama mengendalikan urutan. Unit yang mencantumkan unit Anda dalam After= akan menunggu sampai systemd menyatakan unit Anda sudah dimulai. Type= yang melaporkan status "started" terlalu cepat dapat membuat unit dependen berjalan sebelum service Anda dapat meresponsnya.

Jawaban kedua mengendalikan supervisi. systemd menempatkan setiap proses yang dibuat oleh suatu unit ke dalam cgroup (control group), yaitu fitur kernel yang mengelompokkan proses agar dapat dibatasi dan dihentikan secara bersamaan. cgroup digunakan oleh systemctl stop untuk melakukan pembersihan: KillMode= secara default bernilai control-group, sehingga saat unit dihentikan, setiap proses di dalamnya akan menerima sinyal. PID utama memiliki cakupan yang lebih sempit. PID ini adalah satu-satunya proses yang jika berhenti akan mengakhiri unit, dan status keluarnya menjadi hasil unit tersebut. Kebingungan bermula ketika cgroup dianggap sebagai PID utama.

Type=simple melaporkan status started sebelum binary dijalankan

Type=simple adalah nilai default ketika ExecStart= ditetapkan dan Type= maupun BusName= tidak ada. systemd membuat proses, langsung menganggap unit telah dimulai, dan menetapkan proses tersebut sebagai PID utama. Unit lanjutan segera dimulai, bahkan sebelum binary service dijalankan.

Detail terakhir ini menjelaskan kejadian yang sering membingungkan. Kesalahan ketik pada path ExecStart= tetap menghasilkan start job yang berhasil, lalu kegagalan terjadi sesaat kemudian ketika eksekusi gagal. systemd mencatat kasus ini dengan exit code 203, yang dalam tabelnya disebut EXEC dan didefinisikan sebagai kegagalan menjalankan binary service. Jadi, systemctl start yang selesai tanpa error tidak membuktikan bahwa binary Anda ada.

Gunakan simple untuk program yang tetap berjalan di foreground dan tidak pernah memindahkan dirinya sendiri ke background. Ini mencakup sebagian besar daemon modern dan hampir semua program yang Anda tulis sendiri.

Type=exec menunggu hingga program benar-benar dimulai

Type=exec adalah simple dengan satu langkah tambahan. systemd menganggap unit telah dimulai hanya setelah fork dan eksekusi binary berhasil. Binary yang tidak ada atau User= yang tidak dapat di-resolve kini langsung menyebabkan start job gagal, bukan melaporkan keberhasilan lalu gagal secara diam-diam beberapa saat kemudian.

Type=exec diperkenalkan dalam systemd 240, sehingga tersedia pada semua distribusi server saat ini. Ubuntu 24.04 menggunakan systemd 255 dan Debian 13 menggunakan systemd 257 per Agustus 2026. Periksa versi Anda dengan systemctl --version.

Konsekuensinya adalah satu langkah sinkronisasi tambahan saat start. Keuntungannya adalah exit status yang akurat dari systemctl start. Untuk program foreground, gunakan exec, bukan simple.

Type=forking, dan bagaimana PID utama dapat hilang

Type=forking memberi tahu systemd bahwa proses dalam ExecStart= akan melakukan fork terhadap child lalu keluar dengan sengaja. systemd menunggu proses pertama tersebut keluar, lalu baru menganggap unit telah dimulai. Child yang ditinggalkan menjadi daemon. Kebiasaan ini berasal dari era SysV, ketika tidak ada komponen yang mengawasi daemon setelah init script selesai, dan file PID menjadi satu-satunya catatan tentang proses yang sedang berjalan. Keterbatasan ini merupakan salah satu alasan utama mengapa systemd menggantikan init script.

Masalahnya terletak pada identitas proses. Proses yang dijalankan systemd sudah tidak ada, sehingga systemd harus menentukan proses survivor mana yang menjadi proses utama. Atur PIDFile= ke file yang ditulis daemon, biasanya berupa path di bawah /run, lalu systemd membaca PID dari file tersebut. systemd juga memeriksa apakah PID dalam file itu merujuk ke proses yang sudah menjadi bagian dari service ini. Dengan demikian, file lama yang merujuk ke proses lain akan ditolak, bukan dipercaya.

Tanpa PIDFile=, GuessMainPID= berlaku, dan nilai default-nya adalah yes. Perkiraan ini hanya dapat diandalkan jika service akhirnya berjalan sebagai satu proses. Manual menjelaskan batasannya dengan jelas: jika daemon terdiri dari lebih dari satu proses, perkiraan tersebut dapat salah dan deteksi kegagalan berhenti berfungsi. Unit juga dapat berakhir dengan PID utama 0, yang berarti systemd tidak memiliki proses apa pun untuk diawasi.

Sebagian besar daemon yang melakukan fork juga memiliki opsi untuk tetap berjalan di foreground. Gunakan opsi tersebut bersama Type=exec dan hapus baris PIDFile=. Lebih sedikit komponen berarti lebih sedikit kemungkinan PID hilang.

Type=oneshot untuk pekerjaan yang selesai

Type=oneshot mengharapkan proses berjalan lalu keluar. systemd menandai unit sebagai started setelah proses tersebut keluar. Karena itu, oneshot merupakan bentuk yang tepat untuk apa pun yang harus ditunggu oleh unit lain. Ini juga merupakan default implisit ketika unit tidak menetapkan Type= maupun ExecStart=.

Dua perilaku khusus berlaku untuk oneshot. Hanya tipe ini yang menerima lebih dari satu baris ExecStart=, dan baris-baris tersebut dijalankan secara berurutan. Batas waktu start-nya juga dinonaktifkan secara default. Jadi, oneshot yang macet akan menunggu selamanya kecuali Anda menetapkan TimeoutStartSec= sendiri.

Setelah proses keluar, unit kembali ke status inactive. RemainAfterExit=yes membuatnya tetap active tanpa proses yang berjalan sama sekali. Ini merupakan bentuk yang disengaja dari gejala di bagian atas halaman ini, dan benar ketika tugas unit adalah meninggalkan state, bukan mempertahankan sesuatu tetap berjalan: memuat ruleset firewall atau menjalankan stack container. Pola ini digunakan oleh stack Docker Compose yang kembali berjalan setelah reboot, ketika unit menjalankan perintah compose, keluar, lalu tetap active karena container yang dimulainya tetap berjalan setelah unit tersebut berakhir. Unit oneshot juga merupakan jenis unit yang dipicu oleh jadwal. Ini merupakan bagian lain dari menjalankan pekerjaan dengan systemd timer, bukan cron.

Type=notify memungkinkan service memberi tahu saat service siap

Type=notify memindahkan keputusan tersebut ke service. systemd mempertahankan start job tetap terbuka sampai proses mengirim READY=1 melalui Unix socket yang path-nya diterima melalui environment variable NOTIFY_SOCKET. Interface C-nya adalah sd_notify(3), dan banyak server sudah mendukungnya.

Ini adalah jawaban yang akurat untuk pertanyaan "apakah service sudah dimulai". simple dan exec melaporkan bahwa service sudah dimulai sebelum service membaca konfigurasinya atau membuka listening socket. Akibatnya, unit dependen dapat dimulai terlalu cepat dan gagal pada koneksi pertamanya. notify melaporkan bahwa service sudah dimulai tepat ketika service tersebut menyatakan dirinya siap.

systemd hanya menerima pesan tersebut dari main process. Inilah yang dimaksud dengan NotifyAccess=main, dan Type=notify menyiratkannya. Jika pesan berasal dari child process atau helper, tetapkan NotifyAccess=all. Shell script dapat memanggil systemd-notify --ready, tetapi perintah tersebut berjalan sebagai proses singkat terpisah. Karena itu, perintah tersebut memerlukan NotifyAccess=all, dan systemd mungkin tidak dapat mengaitkan pesan jika pengirimnya sudah keluar. Service yang mendukung protokol tersebut secara langsung lebih andal.

Ada dua pengaturan terkait yang perlu diketahui. Type=notify-reload, yang tersedia sejak systemd 253, memperluas handshake yang sama ke proses reload. Dengan demikian, systemctl reload mengembalikan hasil ketika service melaporkan bahwa reload telah selesai, bukan ketika signal dikirim. WatchdogSec= meminta service yang mendukung notifikasi untuk mengirim pesan keep-alive pada interval tertentu. systemd menganggap tenggat yang terlewat sebagai kegagalan.

Type=dbus dan Type=idle

Type=dbus menunggu hingga service mendaftarkan nama pada D-Bus, yaitu bus pesan yang digunakan oleh service sistem dan desktop untuk saling berkomunikasi. Opsi ini memerlukan BusName= dan menjadi default segera setelah BusName= ditetapkan. Gunakan opsi ini hanya untuk service yang benar-benar mendaftarkan nama bus.

Type=idle berperilaku seperti simple, tetapi menunda eksekusi program hingga job yang berada dalam antrean selesai dikirim, dengan batas waktu lima detik. Opsi ini digunakan agar output konsol saat boot tidak bercampur dengan pesan status. Opsi ini bukan alat pengaturan urutan dan tidak sesuai untuk service biasa.

Mengapa skrip wrapper membuat systemd mengawasi PID yang salah

Berikut pola yang menghasilkan gejala awal.

[Service]
Type=simple
ExecStart=/opt/app/run.sh
#!/bin/bash
export APP_ENV=production
/opt/app/bin/server --config /etc/app.yaml &
/opt/app/bin/exporter --port 9101

systemd mencatat shell sebagai PID utama. Shell tetap berjalan selama exporter berjalan di foreground. Jika server berhenti, shell tidak menyadarinya. Akibatnya, PID utama masih berjalan, unit masih active, dan Restart= tidak memiliki apa pun untuk ditindaklanjuti. Kedua proses tetap berada dalam cgroup unit sepanjang waktu, sehingga systemctl stop tetap melakukan pembersihan dengan benar. Pengawasanlah yang rusak, bukan pembersihan.

Perbaikan bergantung pada jumlah proses long-running yang sebenarnya dimiliki unit.

Jika hanya ada satu, ganti shell dengan proses tersebut.

#!/bin/bash
export APP_ENV=production
exec /opt/app/bin/server --config /etc/app.yaml

exec mengganti shell dengan program yang disebutkan dan mempertahankan PID yang sama, sehingga PID yang dicatat systemd sekarang menjadi milik daemon. Lebih baik lagi, hapus wrapper tersebut. Environment= dan EnvironmentFile= membawa variabel, sedangkan ExecStartPre= menjalankan langkah penyiapan. Dengan demikian, systemd dapat menjalankan daemon secara langsung dan mengetahui PID-nya sejak awal.

Jika ada dua, tidak ada satu PID yang mewakili unit tersebut. Pisahkan keduanya menjadi dua unit, lalu atur urutannya dengan After= dan Wants=. Satu unit untuk setiap proses adalah susunan yang diawasi systemd dengan baik. Hanya dengan cara ini setiap proses memperoleh perilaku restart sendiri.

Perubahan yang dibuat ExitType=cgroup

ExitType= ditambahkan dalam systemd 250. Nilai default-nya adalah main: unit dianggap berhenti ketika proses utama berakhir. Dengan ExitType=cgroup, unit dianggap tetap berjalan selama ada proses apa pun dalam cgroup-nya yang masih aktif.

[Service]
Type=simple
ExitType=cgroup
ExecStart=/opt/app/launcher

Ini menyelesaikan satu masalah tertentu. Launcher yang menjalankan pekerjaan sebenarnya lalu keluar akan membuat systemd menganggap unit berhenti berdasarkan ExitType=main, kemudian menghentikan proses yang masih berjalan. Dengan ExitType=cgroup, unit mengikuti seluruh grup tersebut.

Pahami hal yang tidak diselesaikan oleh fitur ini. ExitType=cgroup mempertahankan status aktif unit selama setidaknya satu proses masih hidup. Jadi, unit yang menampung dua daemon tetap aktif setelah salah satunya berhenti. Fitur ini memperbaiki kasus launcher. Fitur ini tidak mengubah satu unit menjadi supervisor untuk beberapa proses yang independen. ExitType= juga tidak dapat digabungkan dengan Type=oneshot.

Cgroup juga menjadi tempat pencatatan penggunaan resource. Karena itu, batas seperti MemoryMax= dan CPUQuota= berlaku untuk setiap proses yang dibuat oleh unit, terlepas dari apa yang dinyatakan Type= tentang PID utama. Bagian tersebut dibahas dalam membatasi penggunaan memori dan CPU service dengan systemd.

Cara menemukan proses yang sebenarnya dipantau systemd

Kerjakan pemeriksaan ini pada unit yang sedang Anda debug, secara berurutan. Baca konfigurasi yang dimuat systemd, lalu baca proses yang dilacaknya, kemudian bandingkan dengan process table.

systemctl cat app.service
systemctl show -p Type,ExitType,GuessMainPID,PIDFile,MainPID,RemainAfterExit app.service

systemctl cat mencetak unit file beserta setiap drop-in yang berlaku untuknya. Dengan demikian, Anda membaca konfigurasi yang dimuat systemd, bukan file yang Anda ingat pernah diedit. systemctl show mencetak nilai efektif, termasuk nilai default yang tidak pernah Anda catat. Catat nilai MainPID sebelum melanjutkan.

systemd-cgls --unit=app.service
ps -o pid,ppid,stat,etime,args -p "$(systemctl show -p MainPID --value app.service)"

systemd-cgls mencantumkan setiap proses dalam cgroup unit. Baris ps menjelaskan satu proses yang diawasi systemd. Baca keduanya secara bersamaan. Nilai MainPID sebesar 0 berarti systemd tidak memiliki proses untuk dipantau. MainPID yang mengarah ke shell, sementara cgroup juga berisi daemon Anda, menunjukkan kasus wrapper seperti di atas. Jika cgroup berisi lebih banyak proses daripada yang Anda perkirakan, berarti ada launcher atau daemon yang melakukan forking.

systemctl status app.service
journalctl -u app.service -b

systemctl status mencetak baris status dan hierarki cgroup secara bersamaan, sehingga sering kali menjawab kedua pertanyaan sekaligus. journalctl -u yang dibatasi pada boot ini dengan -b menampilkan peristiwa start dan stop yang dicatat systemd untuk unit tersebut, beserta exit code yang diterimanya. Jika daemon menulis ke file log sendiri, bukan ke journal, baca file tersebut juga karena systemd hanya dapat mencatat apa yang diterimanya.

Saat Anda mengubah Type=, lakukan reload lalu restart.

systemd-analyze verify /etc/systemd/system/app.service
sudo systemctl daemon-reload
sudo systemctl restart app.service

systemd-analyze verify mem-parse file dan melaporkan pengaturan yang tidak dapat diterimanya. daemon-reload membuat systemd membaca ulang unit file dari disk. Perubahan pada Type= tidak berlaku untuk unit yang sudah berjalan, sehingga restart wajib dilakukan.

Kemudian uji perubahan tersebut. Ambil PID proses yang sebenarnya ingin Anda pantau dari systemd-cgls, lalu hentikan proses itu. Segera jalankan systemctl is-active app.service. Jika Type= sudah benar, unit keluar dari status active. Jika tetap active, systemd masih memantau proses lain.

Type= systemd service mana yang harus digunakan

  • Program yang tetap berjalan di foreground: Type=exec.
  • Program yang mendukung notifikasi kesiapan: Type=notify, dan notify-reload jika program tersebut juga mengonfirmasi reload.
  • Daemon yang mengharuskan dirinya berjalan di background: Type=forking dengan PIDFile=, atau gunakan opsi untuk tetap berjalan di foreground dengan Type=exec.
  • Skrip yang menjalankan tugas lalu keluar: Type=oneshot, serta RemainAfterExit=yes jika tujuannya adalah meninggalkan state.
  • Launcher yang keluar sementara proses turunannya tetap berjalan: Type=simple dengan ExitType=cgroup.

Jika Anda tidak yakin Type= yang diperlukan daemon pihak ketiga, baca unit file yang disertakan dalam paketnya terlebih dahulu. Menjalankan systemctl cat pada unit yang disediakan oleh distribusi akan menampilkan Type= yang dipilih upstream. Pilihan tersebut telah diuji oleh lebih banyak orang daripada pilihan Anda.

FAQ

Mengapa unit systemd saya tetap berstatus aktif setelah prosesnya berhenti?

Karena proses yang dianggap systemd sebagai proses utama masih berjalan. systemd memantau satu PID untuk setiap service, yang dipilih berdasarkan Type=, bukan setiap proses dalam cgroup unit tersebut. Skrip wrapper yang dijalankan dengan Type=simple adalah penyebab yang umum: shell menjadi PID utama, sehingga unit tetap aktif ketika daemon yang dijalankan shell di latar belakang berhenti. Jalankan systemctl show -p MainPID app.service, lalu tampilkan cgroup unit dengan systemd-cgls --unit=app.service, dan bandingkan keduanya.

Apa perbedaan antara Type=simple dan Type=exec?

Type=simple menganggap unit sudah dimulai segera setelah systemd membuat proses, sebelum binary dijalankan. Karena itu, path yang salah pada ExecStart= tetap menghasilkan job start yang berhasil, lalu diikuti kegagalan. Type=exec menunggu hingga eksekusi berhasil, sehingga kegagalan tersebut dilaporkan langsung oleh job start. Keduanya menganggap proses yang sama sebagai PID utama. Type=exec memerlukan systemd 240 atau yang lebih baru.

Apakah saya masih memerlukan PIDFile= dengan Type=forking?

Ya, jika daemon menulis file tersebut. Tanpa file itu, systemd menggunakan GuessMainPID=, yaitu perkiraan yang hanya andal untuk service yang akhirnya berjalan sebagai satu proses. Jika perkiraan tersebut salah atau tidak dapat dibuat, deteksi kegagalan dan restart otomatis tidak lagi berfungsi untuk unit tersebut. Arahkan PIDFile= ke path persis yang ditulis daemon, biasanya di bawah /run.

Kapan saya harus menggunakan RemainAfterExit=yes?

Gunakan ketika tujuan unit adalah mengubah keadaan sistem, bukan mempertahankan proses agar tetap berjalan. Unit Type=oneshot yang memuat aturan firewall atau memulai container stack akan berhenti segera setelah tugasnya selesai. Tanpa RemainAfterExit=yes, unit menjadi tidak aktif, sehingga systemctl stop tidak memiliki apa pun untuk dihentikan dan tidak dapat menjalankan pembersihan ExecStop=. Dengan pengaturan tersebut, unit tetap aktif tanpa proses apa pun. Dalam konteks ini, perilaku tersebut memang diharapkan.

Apakah perubahan Type= memerlukan daemon-reload?

Ya, dan unit juga harus direstart. systemctl daemon-reload membuat systemd membaca ulang unit file dari disk, tetapi instance yang sedang berjalan tetap menggunakan Type= yang dipakainya saat mulai. Jalankan sudo systemctl daemon-reload, lalu sudo systemctl restart app.service sebelum melakukan pengujian. Jika tidak, Anda masih mengamati perilaku supervisi yang lama.