LinuxのsystemdサービスをType別に設計する方法|simple・forking・notify・oneshotの使い分けとPIDFile設定

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > Linuxトラブルシューティング > LinuxのsystemdサービスをType別に設計する方法|simple・forking・notify・oneshotの使い分けとPIDFile設定
「systemctl startでActiveになったはずなのに、ps auxでプロセスが見当たらない」「Type=forkingにしたらfailedのまま起動しない」——こういったsystemdサービスの謎挙動は、ほぼ例外なくTypeディレクティブの設定ミスが原因です。

systemdがサービスの「起動完了」をどう判断するかは、Typeの指定によってまったく変わります。Type=simpleのままforkして常駐するプログラムを登録すると、systemdは親プロセスが終了した時点で「プロセスが落ちた」と判断します。逆にType=forkingにしてもPIDFileがなければ、systemdはどのプロセスがサービスの本体なのかを特定できません。

この記事では、Type=simple・forking・notify・oneshotそれぞれの動作原理を実機コマンドで確認しながら、どの場面でどのTypeを使うべきかを解説します。PIDFileの設定方法、Restart設定との組み合わせ、journalctlによるType起因エラーの特定まで一通り押さえます。

動作確認環境: RHEL 9.4 / AlmaLinux 9.4(systemd 252)で検証済みです。

この記事のポイント

・Type=simpleはフォアグラウンド常駐プロセスに使い、forkするデーモンには使わない
・Type=forkingでは必ずPIDFileを指定しないとsystemdがプロセスを見失う
・Type=notifyはsd_notify()対応プログラムで最も確実な起動確認ができる
・Type=oneshotはRemainAfterExit=yesと組み合わせて一回限り実行スクリプトに使う


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Typeディレクティブがサービス起動に与える影響

systemdがサービスを管理する際、最も重要な情報のひとつが「いつサービスが起動完了したとみなすか」です。この判断基準を定めるのがTypeディレクティブです。

たとえばType=simple(省略時のデフォルト)では、ExecStartに指定したコマンドが実行された直後を「起動完了」とみなします。After=やWantedBy=など他のサービスとの依存関係は、このタイミングを基準に制御されます。起動シーケンス全体を時系列で把握したいときは systemd-analyze で起動時間計測 を活用してください。

Typeの誤設定が引き起こす典型的な問題は次の3パターンです。

・Type=simpleなのにプログラムがforkしてバックグラウンドに移る → 親プロセス終了が「サービス失敗」とみなされる
・Type=forkingなのにPIDFileが未指定 → systemdがデーモンのPIDを追跡できずfailedになる
・Type=simpleで起動に時間がかかるサービス → 依存するサービスが「起動完了」を待たずに先に動き出す

以降の各セクションで、4種類のTypeの動作を実機で確認していきます。

Type=simpleの動作と適した場面

1. Type=simpleの動作原理

Type=simpleは、ExecStartで起動したプロセスそのものをサービスのメインプロセスとして扱います。systemdはこのプロセスが起動した直後に「サービスが起動完了した」とみなし、After=で依存する次のサービスの起動を開始します。

フォアグラウンドで動き続けるプログラムへの適用例です。

# /etc/systemd/system/myapp.service [Unit] Description=My Application After=network.target [Service] Type=simple ExecStart=/usr/local/bin/myapp --foreground Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target

/usr/local/bin/myapp がフォアグラウンドで動き続けるプロセスであれば、Type=simpleが正解です。

$ systemctl status myapp * myapp.service - My Application Loaded: loaded (/etc/systemd/system/myapp.service; enabled) Active: active (running) since Tue 2026-09-30 09:15:22 JST; 5min ago Main PID: 12345 (myapp) Tasks: 3 (limit: 4915) Memory: 12.4M CPU: 124ms CGroup: /system.slice/myapp.service └─12345 /usr/local/bin/myapp --foreground

2. Type=simpleを使ってはいけない場面

Type=simpleを使ってはいけないのは、プログラムがfork(二重フォーク)してバックグラウンドに移るケースです。

古典的なUnixデーモンの多くは、起動時に次の動作をとります。

1. 親プロセス(ExecStartで起動したプロセス)が起動する
2. fork()して子プロセスを生成する
3. 親プロセスはexitする
4. 子プロセス(デーモン本体)がバックグラウンドで常駐し続ける

この場合にType=simpleを使うと、systemdは「3.の親プロセスがexitした」時点で「メインプロセスが終了した=サービスが落ちた」と判断します。Restart=on-failureが設定されていれば無限再起動ループに入り、設定がなければdeadになります。このような従来型デーモンにはType=forkingを使います。

Type=forkingの動作とPIDFileが必要な理由

1. Type=forkingの動作原理

Type=forkingは、「ExecStartのプロセスがforkして親プロセスが終了した後、子プロセスがデーモンとして動き続ける」という従来型のUnixデーモン動作を前提にしています。

systemdはExecStartの親プロセスがexitするまで待機し、exitを確認してから「サービスが起動完了した」とみなします。

# Type=forking の基本形 [Unit] Description=Legacy Daemon After=network.target [Service] Type=forking PIDFile=/run/mydaemon/mydaemon.pid ExecStart=/usr/sbin/mydaemon -d ExecReload=/bin/kill -HUP $MAINPID Restart=on-failure [Install] WantedBy=multi-user.target

2. PIDFileを必ず指定する理由

Type=forkingでPIDFileを省略すると、systemdはfork後にどのプロセスがサービスの本体(メインプロセス)なのかを追跡できなくなります。その結果、次のような問題が発生します。

・ExecReloadでのシグナル送信先がわからない($MAINPIDが空になる)
・systemdがサービスの死活を正確に監視できない
・journalctl -u での絞り込みが不正確になる

PIDFileにはデーモン自身が書き出すPIDファイルのパスを指定します。パスは通常 /run/サービス名/サービス名.pid の形式が使われます。

PIDFileを指定しなかった場合のjournalctlログを確認すると、エラーの原因がよくわかります。

$ journalctl -u mydaemon-nopidfile --no-pager | tail -8 Sep 30 09:20:11 server01 systemd[1]: Starting Legacy Daemon (no PIDFile)... Sep 30 09:20:11 server01 mydaemon[8532]: Starting mydaemon... Sep 30 09:20:11 server01 mydaemon[8532]: Forking to background Sep 30 09:20:11 server01 systemd[1]: mydaemon-nopidfile.service: Got final SIGCHLD Sep 30 09:20:11 server01 systemd[1]: mydaemon-nopidfile.service: Main process exited, code=exited, status=0/SUCCESS Sep 30 09:20:11 server01 systemd[1]: Failed to start Legacy Daemon (no PIDFile).

3. Apache HTTPDを例にしたType設定の変遷

Apache HTTPD(httpd)はもともと典型的なforkingデーモンでしたが、Apache 2.4からsd_notify()に対応し、RHELのパッケージ版ではType=notifyが使われています。httpd の基本操作でhttpdのステータス確認方法も参照できます。

自前ビルドや古いバージョンのhttpdでType=forkingを使う場合は、/run/httpd/httpd.pid を PIDFile に指定します。

Type=notifyの動作とsd_notify対応プログラム

1. Type=notifyの動作原理

Type=notifyは、プログラム自身がsystemdに「起動完了した」ことを通知するしくみです。プログラムがlibsystemdの sd_notify("READY=1 ") を呼び出すと、systemdはその通知を受け取って「起動完了」とみなします。

Type=simpleとの違いは「起動完了の通知をプログラムが能動的に送る」点です。設定ファイルの読み込みやソケットのbindなど、初期化処理がすべて完了してからREADY=1を送信するため、依存関係の制御が最も正確です。

# Type=notify の例(nginx) [Service] Type=notify ExecStart=/usr/sbin/nginx -g 'daemon off;' ExecReload=/bin/kill -s HUP $MAINPID KillMode=mixed KillSignal=SIGQUIT TimeoutStopSec=5

2. Type=notifyに対応している主なソフトウェア

・nginx 1.9.x以降: sd_notify()を利用してREADY=1を送信
・Apache 2.4(パッケージ版): RHEL 8/9ではType=notify対応
・OpenSSH sshd: RHEL 9のパッケージ版でType=notify対応
・PostgreSQL 12以降: pg_ctl start経由でsd_notifyに対応
・systemd組み込みサービス: systemd-networkd、systemd-resolved等

Type=notifyに対応していないプログラムにType=notifyを指定すると、systemdはREADY=1通知を永遠に待ち続け、TimeoutStartSec(デフォルト90秒)が経過するとfailedになります。

3. プログラム側でsd_notifyを使う実装例(C言語)

新規開発するデーモンにはlibsystemdを組み込んでType=notifyを使うことを推奨します。

#include <systemd/sd-daemon.h> int main(void) { /* ... 初期化処理(設定ファイル読込・ソケットbind等) ... */ /* 起動完了をsystemdに通知 */ sd_notify(0, "READY=1"); /* メインループ */ while (running) { /* ... */ } return 0; } # ビルド時にlibsystemdをリンクする gcc -o mydaemon mydaemon.c -lsystemd

Type=oneshotの動作と起動時スクリプトへの応用

1. Type=oneshotの動作原理

Type=oneshotは「ExecStartに指定したコマンドが実行されて終了する」ことを前提にしています。systemdはExecStartが終了するまで次のサービスの起動を待ち(After=依存関係の制御)、終了後はdeactivated(inactive)になります。

「起動時に一度だけ実行する設定スクリプト」「ネットワーク設定の初期化」「フラグファイルの生成」などに適しています。

2. RemainAfterExit=yesとの組み合わせ

Type=oneshotの注意点は、スクリプトが終了するとサービスがdeactivatedになることです。「実行が完了した状態をactive(running)として表示したい」場合は RemainAfterExit=yes を組み合わせます。

# /etc/systemd/system/network-init.service [Unit] Description=Network Initialization Script After=network-online.target Wants=network-online.target [Service] Type=oneshot RemainAfterExit=yes ExecStart=/usr/local/sbin/network-init.sh ExecStop=/usr/local/sbin/network-cleanup.sh [Install] WantedBy=multi-user.target

RemainAfterExit=yesを付けると、ExecStartが完了後もサービスはactive(exited)として表示されます。

$ systemctl status network-init * network-init.service - Network Initialization Script Loaded: loaded (/etc/systemd/system/network-init.service; enabled) Active: active (exited) since Tue 2026-09-30 09:00:01 JST; 1h 20min ago Process: 1234 ExecStart=/usr/local/sbin/network-init.sh (code=exited, status=0/SUCCESS) Main PID: 1234 (code=exited, status=0/SUCCESS)

「active (exited)」という表示がType=oneshot + RemainAfterExit=yes の特徴です。

Restart設定との組み合わせ実践例

Restart設定は、プロセスが終了した際の再起動動作を制御します。ただしTypeによって「プロセスの終了」の意味が異なるため、Restart設定の動作もTypeと密接に関係します。

1. Restart設定の選択肢と用途

・Restart=no(デフォルト): 再起動しない
・Restart=on-failure: 終了コードが0以外、シグナルによる異常終了時に再起動
・Restart=always: 正常終了(exit 0)でも含めて常に再起動
・Restart=unless-stopped: systemctl stop以外のすべての終了で再起動

一般的な常駐デーモンには Restart=on-failure が推奨です。

2. Restart=on-failureを使ったType=simple実践例

[Service] Type=simple ExecStart=/usr/local/bin/myapp --foreground Restart=on-failure RestartSec=10 StartLimitInterval=60 StartLimitBurst=3

・RestartSec=10: 再起動前に10秒待機(連続クラッシュを防ぐ)
・StartLimitInterval=60: 60秒の間に
・StartLimitBurst=3: 3回以上再起動が発生するとサービスをfailedにして停止する

StartLimitInterval + StartLimitBurst を設定することで、バグによる無限再起動ループを防ぐことができます。設定しないと、プログラムのバグがある場合にCPUとメモリを食い尽くすことがあります。

3. Type=oneshotにRestartが不要な理由

Type=oneshotはそもそも「終了することが正常な動作」であるため、Restart=on-failureを設定しても意味が異なります。一回限りの実行が目的のスクリプトにRestartを付けると、スクリプトが失敗するたびに何度も再実行されます。Type=oneshotには原則Restart=noのままにしてください。

journalctl -xeでType起因エラーを特定する手順

1. よくあるType起因エラーとジャーナルの見方

Typeの設定ミスが引き起こすエラーは、journalctlで詳細を確認することで原因を特定できます。

$ journalctl -xe -u myservice --no-pager | tail -20

-xe オプションは「拡張情報付き・ジャーナルの末尾」を表示します。起動失敗直後の原因は末尾10~20行に集中しています。

2. エラーメッセージ別の対処法

「Main process exited, code=exited, status=1/FAILURE」
ExecStartに指定したコマンドがエラーで終了しています。Type=simpleでforkしたプログラムが終了コード1を返している可能性があります。まずExecStartのコマンドを単体で実行してエラー内容を確認します。

「Failed to read PID from file …: No such file or directory」
Type=forkingでPIDFileに指定したファイルが生成されていません。デーモン自身がPIDファイルを書き出すパスとPIDFileのパスが一致していないケースが大半です。デーモンの設定ファイル(--pid-file オプション等)を確認してください。

「Start request repeated too quickly」
StartLimitInterval内にStartLimitBurstを超える回数の再起動が発生した場合に表示されます。プログラムのクラッシュが原因であれば、まずクラッシュの根本原因を解消します。一時的にStartLimitBurstを増やして凌ぐことは根本解決にはなりません。

「Timeout waiting for notify message(timeout)」
Type=notifyを指定したが、プログラムがsd_notify("READY=1")を送信していません。プログラムがsd_notifyに対応しているかを確認し、対応していなければType=simpleまたはType=forkingに変更してください。

3. systemctl status のCGroupで実行状態を確認する

$ systemctl status myservice * myservice.service - My Service Loaded: loaded (/etc/systemd/system/myservice.service; enabled) Active: activating (start) since Tue 2026-09-30 10:00:00 JST; 30s ago Main PID: 0 (unknown) CGroup: /system.slice/myservice.service

「Active: activating (start)」が90秒以上続く場合、Type=notifyでREADY=1が届いていないか、Type=forkingでPIDFileの読み取り待ちになっています。TimeoutStartSecを確認し、必要に応じて延長または原因を調査します。

Linuxセミナーでは実際に自分のサービスをsystemdに登録して動作確認する演習も含めています。現場で使えるLinuxサーバー構築のスキルを体系的に身につけたい方はセミナー詳細をご覧ください。

本記事のまとめ

systemdのTypeディレクティブはサービスの起動確認方式を決める重要な設定です。プログラムの動作に合ったTypeを選ぶことで、謎の起動失敗を防ぐことができます。

Type 適した場面 注意点
simple フォアグラウンドで動き続けるプロセス forkするデーモンには使わない
forking fork-daemonize方式の従来型デーモン PIDFileの指定が必須
notify sd_notify()対応プログラム(nginx等) 非対応プログラムはtimeoutになる
oneshot 一回実行して終了するスクリプト RemainAfterExit=yesで状態保持

実際に遭遇しやすいのは「Type=simpleで書いたがforkingデーモンだった」というケースです。まずExecStartに指定したプログラムがフォアグラウンドで動くのかバックグラウンドにforkするのかを確認し、Typeを決定してください。不明な場合は strace や journalctl -xe でforkの有無を確認する方法が有効です。

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、

Linux Master Pro Seminarでは、systemdサービスの設計から本番運用まで、現役サーバー管理者が20年以上の実務経験をもとに体系的に解説します。

>> セミナー詳細・お申し込みはこちら

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。