Memilih Type= systemd: simple, forking, notify
Unit tetap aktif meski daemon sudah berhenti? Pahami Type= simple, exec, forking, oneshot, dan notify untuk menemukan PID utama yang benar.
Mengapa systemd melaporkan unit sebagai aktif ketika prosesnya sudah berhenti
Unit service systemd tetap active selama satu proses yang disebut systemd sebagai proses utama masih berjalan. Nilai Type= pada bagian [Service] menentukan proses tersebut. Jika memilih nilai yang salah, systemd akhirnya memantau shell wrapper atau proses induk berumur pendek, sementara daemon yang Anda perlukan berhenti di dalam unit yang sama. Unit tersebut melaporkan status proses yang diperintahkan untuk dipantaunya.
Mengubah kebijakan restart tidak akan membantu dalam kasus ini. Restart= bekerja ketika proses utama berhenti, sehingga Restart=always tidak pernah dijalankan selama PID utama (process identifier) masih merujuk pada sesuatu yang 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 pengurutan. Unit yang mencantumkan unit Anda dalam After= akan menunggu sampai systemd menyatakan unit Anda sudah dimulai. Type= yang melaporkan "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 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 muncul ketika cgroup dianggap sebagai PID utama.
Type=simple melaporkan bahwa service dimulai 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 memperlakukan proses tersebut sebagai PID utama. Unit lanjutan segera dimulai, bahkan sebelum binary service dijalankan.
Detail terakhir ini menjelaskan kejutan yang umum terjadi. Kesalahan ketik pada path ExecStart= tetap menghasilkan start job yang berhasil, lalu kegagalan muncul sesaat kemudian ketika eksekusi gagal. systemd mencatat kasus tersebut dengan exit code 203. Dalam tabelnya sendiri, kode ini disebut EXEC dan didefinisikan sebagai kegagalan menjalankan binary service. Jadi, jika systemctl start kembali tanpa error, hal itu tidak membuktikan bahwa binary Anda ada.
Gunakan simple untuk program yang tetap berjalan di foreground dan tidak memindahkan dirinya sendiri ke background. Ini mencakup sebagian besar daemon modern dan hampir semua program yang Anda tulis sendiri.
Type=exec menunggu program benar-benar berjalan
Type=exec adalah simple dengan satu langkah tambahan. systemd menganggap unit telah berjalan 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 tanpa pesan beberapa saat kemudian.
Type=exec diperkenalkan pada systemd 240, sehingga semua distribusi server saat ini telah memilikinya. Ubuntu 24.04 menggunakan systemd 255 dan Debian 13 menggunakan systemd 257, per Agustus 2026. Periksa versi Anda dengan systemctl --version.
Biayanya adalah satu langkah sinkronisasi tambahan saat start. Keuntungannya adalah status exit yang akurat dari systemctl start. Untuk program foreground, gunakan exec, bukan simple.
Type=forking dan cara main PID dapat hilang
Type=forking memberi tahu systemd bahwa proses dalam ExecStart= akan melakukan fork terhadap proses anak lalu keluar secara sengaja. systemd menunggu proses pertama tersebut keluar, lalu baru menganggap unit telah dimulai. Proses anak yang ditinggalkan adalah daemon.
Kesulitannya terletak pada identitas proses. Proses yang dijalankan systemd sudah tidak ada, sehingga systemd harus menentukan proses yang masih berjalan dan 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 bahwa PID dalam file tersebut merujuk ke proses yang sudah menjadi bagian dari service ini. Dengan demikian, file usang yang mencantumkan proses lain akan ditolak dan tidak dipercaya.
Tanpa PIDFile=, GuessMainPID= berlaku, dan nilai bawaannya adalah yes. Perkiraan ini hanya dapat diandalkan jika service berakhir 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 main PID 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 cara untuk kehilangan PID.
Type=oneshot untuk pekerjaan yang selesai
Type=oneshot mengharapkan proses berjalan lalu keluar. systemd baru menandai unit sebagai started setelah proses tersebut keluar. Karena itu, oneshot sesuai untuk pekerjaan yang harus ditunggu oleh unit lain. Ini juga merupakan default implisit ketika unit tidak menetapkan Type= maupun ExecStart=.
Dua perilaku khusus berlaku pada 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 menjadi inactive. RemainAfterExit=yes membuatnya tetap active meskipun tidak ada proses yang berjalan. Inilah bentuk yang disengaja dari gejala pada bagian atas halaman ini. Perilaku tersebut benar jika tugas unit adalah meninggalkan suatu state, bukan mempertahankan proses agar tetap berjalan, misalnya memuat ruleset firewall atau menjalankan container stack. Pola ini digunakan oleh Docker Compose stack yang kembali berjalan setelah reboot, ketika unit menjalankan perintah compose, keluar, lalu tetap active karena container yang dimulainya terus berjalan setelah unit tersebut selesai. Unit oneshot juga merupakan jenis unit yang dipicu oleh jadwal. Ini adalah bagian lain dari menjalankan pekerjaan dengan systemd timer, bukan cron.
Type=notify membuat service dapat menyatakan bahwa service sudah siap
Type=notify menyerahkan keputusan tersebut kepada service. systemd menahan job start hingga proses mengirim READY=1 melalui Unix socket yang path-nya diterima proses dalam variabel lingkungan NOTIFY_SOCKET. Antarmuka C-nya adalah sd_notify(3), dan banyak server sudah mendukungnya.
Ini adalah jawaban yang tepat untuk pertanyaan "apakah service sudah dimulai". simple dan exec melaporkan bahwa service sudah dimulai sebelum service membaca konfigurasinya atau membuka socket listening. 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 proses utama. Inilah arti NotifyAccess=main, dan Type=notify menyiratkannya. Jika pesan dikirim dari child process atau helper, tetapkan NotifyAccess=all. Shell script dapat memanggil systemd-notify --ready, tetapi perintah itu berjalan sebagai proses singkat yang terpisah. Karena itu, perintah tersebut memerlukan NotifyAccess=all, dan systemd mungkin tidak dapat mengaitkan pesan yang pengirimnya sudah keluar. Service yang menjalankan protokol tersebut secara langsung lebih andal.
Ada 2 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 menggunakan notifikasi untuk mengirim pesan keep-alive pada interval tertentu. systemd menganggap tenggat waktu yang terlewat sebagai kegagalan.
Type=dbus dan Type=idle
Type=dbus menunggu hingga service mendaftarkan sebuah nama pada D-Bus, yaitu message bus 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 mengantre selesai dikirim, dengan batas waktu lima detik. Opsi ini ada agar output konsol saat boot tidak bercampur dengan pesan status. Opsi ini bukan alat pengurutan dan tidak digunakan pada service biasa.
Mengapa skrip wrapper membuat systemd memantau PID yang salah
Berikut bentuk konfigurasi 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 9101systemd mencatat shell sebagai PID utama. Shell tetap berjalan selama exporter berjalan di foreground. Jika server berhenti, shell tidak menyadarinya. Karena itu, PID utama masih berjalan, unit masih active, dan Restart= tidak memiliki tindakan yang dapat dilakukan. Kedua proses terus berada dalam cgroup unit, sehingga systemctl stop tetap membersihkannya dengan benar. Yang rusak adalah supervisi, bukan pembersihan.
Perbaikannya bergantung pada jumlah proses yang berjalan lama dalam unit.
Jika hanya ada satu proses, ganti shell dengan proses tersebut.
#!/bin/bash
export APP_ENV=production
exec /opt/app/bin/server --config /etc/app.yamlexec mengganti shell dengan program yang disebutkan dan mempertahankan PID yang sama. Dengan demikian, PID yang dicatat systemd sekarang menjadi milik daemon. Lebih baik lagi, hapus wrapper tersebut. Environment= dan EnvironmentFile= membawa variabel, sedangkan ExecStartPre= menjalankan langkah setup. Dengan begitu, systemd dapat menjalankan daemon secara langsung dan mengetahui PID-nya sejak awal.
Jika ada dua proses, tidak ada satu PID yang dapat mewakili unit tersebut. Pisahkan keduanya menjadi dua unit, lalu atur urutannya dengan After= dan Wants=. Satu unit untuk setiap proses adalah susunan yang paling baik untuk disupervisi systemd. Ini juga satu-satunya cara agar setiap proses memiliki perilaku restart sendiri.
Perubahan yang dilakukan ExitType=cgroup
ExitType= ditambahkan di systemd 250. Nilai defaultnya adalah main: unit dianggap berhenti ketika proses utama keluar. Dengan ExitType=cgroup, unit dianggap berjalan selama masih ada proses apa pun dalam cgroup-nya yang aktif.
[Service]
Type=simple
ExitType=cgroup
ExecStart=/opt/app/launcherIni menyelesaikan satu masalah tertentu. Launcher yang memulai pekerjaan utama lalu keluar akan membuat systemd menganggap unit berhenti berdasarkan ExitType=main dan menghentikan proses yang masih berjalan. Dengan ExitType=cgroup, unit mengikuti seluruh grup proses.
Pahami hal yang tidak diselesaikannya. ExitType=cgroup mempertahankan status aktif unit selama setidaknya satu proses masih berjalan. Jadi, unit yang menampung dua daemon tetap aktif setelah salah satunya berhenti. Ini memperbaiki kasus launcher. Ini tidak mengubah satu unit menjadi supervisor untuk beberapa proses independen. ExitType= juga tidak dapat digunakan bersama Type=oneshot.
Cgroup juga menjadi tempat pencatatan penggunaan sumber daya. Karena itu, batas seperti MemoryMax= dan CPUQuota= berlaku untuk setiap proses yang dibuat unit, terlepas dari keterangan Type= tentang PID utama. Aspek tersebut dibahas dalam membatasi penggunaan memori dan CPU service dengan systemd.
Cara menemukan proses yang sebenarnya dipantau systemd
Lakukan pemeriksaan ini pada unit yang sedang Anda debug, secara berurutan. Baca konfigurasi yang dimuat systemd, lalu baca proses yang dilacaknya, kemudian bandingkan dengan tabel proses.
systemctl cat app.service
systemctl show -p Type,ExitType,GuessMainPID,PIDFile,MainPID,RemainAfterExit app.servicesystemctl cat mencetak file unit 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. Nilai 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 -bsystemctl status mencetak baris status dan hierarki cgroup secara bersamaan. Karena itu, perintah ini sering menjawab kedua pertanyaan sekaligus. journalctl -u yang dibatasi ke boot ini dengan -b menampilkan peristiwa start dan stop yang dicatat systemd untuk unit tersebut, beserta kode exit yang diterimanya. Jika daemon menulis ke file log sendiri, bukan ke journal, baca file tersebut juga. systemd hanya dapat mencatat data yang sampai kepadanya.
Jika Anda mengubah Type=, lakukan reload lalu restart.
systemd-analyze verify /etc/systemd/system/app.service
sudo systemctl daemon-reload
sudo systemctl restart app.servicesystemd-analyze verify mengurai file dan melaporkan pengaturan yang tidak dapat diterimanya. daemon-reload membuat systemd membaca ulang file unit dari disk. Perubahan pada Type= tidak diterapkan ke unit yang sedang berjalan. Karena itu, restart wajib dilakukan.
Kemudian uji perubahan tersebut. Ambil PID proses yang benar-benar ingin Anda periksa dari systemd-cgls, lalu hentikan proses itu. Segera jalankan systemctl is-active app.service. Jika Type= benar, unit akan 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, dannotify-reloadjika program juga mengonfirmasi proses reload. - Daemon yang mengharuskan dirinya berjalan di background:
Type=forkingdenganPIDFile=, atau gunakan opsi untuk tetap berjalan di foreground melaluiType=exec. - Skrip yang menjalankan tugas lalu keluar:
Type=oneshot, sertaRemainAfterExit=yesjika tujuannya adalah meninggalkan state. - Launcher yang keluar sementara proses turunannya tetap berjalan:
Type=simpledenganExitType=cgroup.
Jika Anda tidak yakin Type= yang diperlukan daemon pihak ketiga, baca file unit 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 konfigurasi Anda.
FAQ
Mengapa unit systemd saya tetap berstatus active ketika 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. Penyebab yang umum adalah skrip wrapper yang dijalankan dengan Type=simple: shell menjadi PID utama, sehingga unit tetap berstatus active 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 hasilnya.
Apa perbedaan antara Type=simple dan Type=exec?
Type=simple menganggap unit telah dijalankan segera setelah systemd membuat proses, sebelum binary dieksekusi. Karena itu, path yang salah dalam ExecStart= tetap menghasilkan job start yang berhasil, lalu diikuti kegagalan. Type=exec menunggu sampai eksekusi berhasil, sehingga kegagalan tersebut dilaporkan langsung oleh job start. Keduanya memperlakukan 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 dilakukan, deteksi kegagalan dan restart otomatis berhenti berfungsi untuk unit itu. 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 inactive, sehingga systemctl stop tidak memiliki apa pun untuk dihentikan dan tidak dapat menjalankan pembersihan ExecStop=. Dengan opsi tersebut, unit tetap berstatus active tanpa proses apa pun. Perilaku ini memang diharapkan dalam kasus ini.
Apakah perubahan Type= memerlukan daemon-reload?
Ya, dan unit juga harus di-restart. 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.