インフラ作業支援ラボInfra Support Lab← ガイド一覧← 戻る

systemdのExecStart=の書き方|絶対パス・引数・リダイレクトで失敗する原因

ExecStart=はシェルへそのまま渡されるコマンド行ではありません。実行ファイル・引数・作業ディレクトリ・シェル構文を分けて考えると、起動失敗を減らせます。

このページで分かること

  • ExecStart=の基本的な書き方
  • 絶対パスと実行ファイル検索の考え方
  • パイプ・リダイレクト・&&がそのまま使えない理由
  • status=203/EXECが出たときの確認順

先に結論

ExecStart=では、実行ファイルと引数を明確に指定します。実行ファイルは絶対パスを使うと環境差を減らせます。パイプやリダイレクトなどが必要なら、目的に合うsystemdの設定を使うか、必要性を確認したうえで明示的にシェルを起動します。

このページの役割: ExecStart=のコマンド行と起動失敗に絞った個別テーマです。Unit全体の設計はsystemd Unit設定ガイド、すでにサービスがfailedの場合はsystemdが起動しないトラブルガイドを参照してください。

ExecStart=を適用前にレビュー

Unitファイルを貼り付けて、実行パスやシェル構文など起動失敗につながる設定を確認できます。

systemd Unitをレビュー

ExecStart=の基本

ExecStart=は、サービス開始時に実行するプログラムと引数を指定します。たとえばPythonアプリを起動するなら、次のように実行ファイルとスクリプトを分けて書きます。

[Service] ExecStart=/usr/bin/python3 /opt/example/app.py

ここで最初の要素は実行するプログラム、その後ろはそのプログラムへ渡す引数です。普段の対話シェルで入力する「コマンド文字列」と完全に同じものとして扱わないことが重要です。

実務の基本: Unitに書く前に、command -v python3readlink -fなどで実行ファイルの場所を確認し、サービスの実行ユーザーでもアクセスできるか確認します。

実行ファイルは絶対パスが分かりやすい

systemdは単純な実行ファイル名を、コンパイル時に決められた固定の検索パスから解決できる場合があります。そのため「絶対パス以外は必ず失敗する」というわけではありません。

考え方
ExecStart=/usr/bin/python3 ...どの実行ファイルを使うか明確。環境差を避けやすく、運用手順にも残しやすい。
ExecStart=python3 ...単純なファイル名ならsystemdの固定検索パスで解決される場合がある。ただしログインシェルのPATHやaliasを使う仕組みではない。
注意: ./appbin/appのようにスラッシュを含む相対指定を、普段のシェルと同じ感覚で使わないようにします。実行場所やファイル配置を明示した方が安全です。

引数と引用符は「シェルと同じ」と決めつけない

スペースを含む1つの引数は引用符でまとめられます。ただし、systemdのコマンド行解析には独自の規則があり、シェルの展開すべてが使えるわけではありません。

[Service] ExecStart=/usr/bin/example --message "hello world" --config /etc/example/app.conf

環境変数を使う場合も、対話シェルの設定ファイルやaliasは読み込まれません。必要な環境はEnvironment=EnvironmentFile=などで明示する方が追跡しやすくなります。

パイプ・リダイレクト・&&はそのままシェル構文にならない

次のような書き方を「シェルで実行される」と考えるのは危険です。

# シェルと同じ動作を期待しない ExecStart=/usr/bin/example | /usr/bin/grep ERROR ExecStart=/usr/bin/example >> /var/log/example.log ExecStart=/usr/bin/prepare && /usr/bin/example

|>>>&&などを解釈するシェルが自動で挟まるわけではありません。

代替を先に検討: ログ出力ならStandardOutput=・journal、事前処理ならExecStartPre=など、systemd側の機能で目的を表現できないか確認します。

どうしてもシェル構文が必要なら、シェルを明示して実行します。

[Service] ExecStart=/bin/sh -c '/usr/bin/example | /usr/bin/grep ERROR'
シェルを使う場合: 引用符、変数展開、終了コードの扱いが増えるため、単純なExecStart=より障害原因が追いにくくなります。Unitで表現できる処理はUnit設定へ分けるのがおすすめです。

相対ファイルを使うならWorkingDirectory=も確認

実行ファイル自体だけでなく、アプリへ渡した相対パスも問題になります。アプリが./config.ymlを読む設計なら、想定する作業ディレクトリを明示する必要があります。

[Service] WorkingDirectory=/opt/example ExecStart=/usr/bin/python3 app.py

ただし、運用上は設定ファイルなども絶対パスで指定すると依存関係が見えやすくなります。

[Service] ExecStart=/usr/bin/python3 /opt/example/app.py --config /etc/example/app.yml

status=203/EXECが出たら実行そのものを確認

status=203/EXECは、systemdがプロセスを実行する段階で失敗したことを示します。設定内容だけでなく、実体・権限・ファイル形式まで確認します。

確認項目確認例
ファイルが存在するかls -l /opt/example/start.sh
実行権限があるかnamei -l /opt/example/start.sh / test -x /opt/example/start.sh
指定ユーザーが到達できるかsudo -u appuser test -x /opt/example/start.sh
スクリプトのshebanghead -n 1 /opt/example/start.sh
Unitで実際に何を指定しているかsystemctl cat example.service
systemctl status example.service --no-pager -l journalctl -u example.service -b --no-pager -n 100 systemctl cat example.service
chmodだけで直さない: 203/EXECには、ファイル不存在、親ディレクトリへ到達できない、実行権限、shebang先の不存在など複数の原因があります。ログを見てから変更します。

複数のExecStart=はType=oneshotかを確認

通常の長時間動作するサービスでは、複数のExecStart=を並べて順番に処理する設計にはしません。複数コマンドを順に実行できるのは、主にType=oneshotの用途です。

[Service] Type=oneshot ExecStart=/usr/bin/prepare-example ExecStart=/usr/bin/apply-example RemainAfterExit=yes

長時間動作するデーモンで準備処理が必要なら、ExecStartPre=と本体のExecStart=を分けると意図が分かりやすくなります。

[Service] ExecStartPre=/usr/bin/example-check ExecStart=/usr/bin/example-server --config /etc/example/server.conf

変更前・変更後の確認手順

  1. 実行ファイルと引数を手動で確認する
  2. Unitを保存したらsystemd-analyze verifyで構文や参照を確認する
  3. systemctl daemon-reloadでUnit変更を読み直す
  4. systemctl start後にstatusとjournalを確認する
  5. 再起動後も必要ならenable状態も別途確認する
systemd-analyze verify /etc/systemd/system/example.service systemctl daemon-reload systemctl start example.service systemctl status example.service --no-pager -l journalctl -u example.service -b --no-pager -n 100
適用前チェック: 「シェルで動いたからsystemdでも動く」ではなく、実行ファイル・引数・ユーザー・作業ディレクトリ・環境変数をUnitの条件で確認します。

関連ガイド

ExecStart=を適用前にレビュー

Unitを貼り付けて、実行パス、権限、シェル構文など起動失敗につながる設定を確認できます。

systemd Unitをレビュー