【Linux】systemd 自作サービスの作成手順まとめ|自動起動・常駐化・ユニットファイル設定を徹底解説

スクリプト設計実務レシピ

Linux環境で自作のシェルスクリプトやPythonプログラム、Go/Node.jsアプリケーションなどをバックグラウンドで安定稼働させたい場合、最も確実で標準的な手法が「systemd の自作サービス(Systemd Service Unit)」としての登録です。

nohup ./app &screencron@reboot を使った常駐化は手軽ですが、「サーバー再起動時に確実に自動起動させたい」「プロセスが異常終了(クラッシュ)した際に自動で再起動させたい」「ログを標準的な仕組みで一括管理したい」といった実務要件を満たすには限界があります。

systemdに自作サービスとして登録すれば、OS起動時の自動起動(systemctl enable)、異常終了時の即時リカバリ(Restart=always)、リソース制限、専用ユーザーでの安全な実行、journalctl によるログ収集などを統合的に実現できます。

この記事では、ユニットファイル(.service)の書き方、主要ディレクティブの意味、Bash・Pythonでの実践的なデーモン化手順、daemon-reload からの反映フロー、journalctl でのトラブルシューティングまで、現場で即コピペして使える形で徹底解説します。

  1. 【早見表】systemd 自作サービス登録の完全フロー チートシート
  2. 1. なぜ自作プログラムを systemd でデーモン化するのか?
    1. nohup・cron と systemd の比較
    2. ユニットファイルの配置場所
  3. 2. ユニットファイル(.service)の基本構造と主要ディレクティブ
    1. [Unit] セクション:サービスのメタ情報と依存関係
    2. [Service] セクション:プロセスの起動・停止・動作設定
    3. Type= の主な種類と選択基準
    4. [Install] セクション:自動起動(enable)の紐付け
  4. 3. 【実践例1】自作シェルスクリプトを常駐デーモン化する手順
    1. ステップ1:実行対象のスクリプトを作成する
    2. ステップ2:ユニットファイルを作成する
    3. ステップ3:反映・起動・自動起動の有効化
  5. 4. 【実践例2】Pythonスクリプト(仮想環境venv含む)を常駐化する手順
    1. ステップ1:専用ユーザーとアプリディレクトリの準備
    2. ステップ2:Python専用ユニットファイルの作成
    3. ステップ3:反映と起動テスト
  6. 5. 【実践例3】1回だけ実行するバッチ処理(Type=oneshot)の作成
  7. 6. 自作サービスの反映・管理・自動起動コマンド一覧
  8. 7. journalctl を使ったログ確認とデバッグ
    1. よく使うログ確認コマンド
  9. 8. 実務でよくあるエラーとアンチパターン解決チェックリスト
    1. 詳細デバッグ:systemd-analyze verify の活用
  10. まとめ & 関連リファレンス
    1. 関連記事
  11. 関連記事

【早見表】systemd 自作サービス登録の完全フロー チートシート

自作プログラムを systemd サービスとして登録し、自動起動・常駐化させるまでの標準フローです。

ステップ 作業内容 対象ファイル / ディレクトリ 実行コマンド例
Step 1 実行ファイルの準備 /opt/myapp/run.sh chmod +x /opt/myapp/run.sh
Step 2 ユニットファイル作成 /etc/systemd/system/myapp.service sudo nano /etc/systemd/system/myapp.service
Step 3 定義の再読込 systemd デーモン sudo systemctl daemon-reload
Step 4 サービスの起動と状態確認 稼働プロセス sudo systemctl start myapp
sudo systemctl status myapp
Step 5 OS起動時の自動起動有効化 シンボリックリンク生成 sudo systemctl enable myapp
Step 6 ログのリアルタイム監視 systemd-journald journalctl -u myapp -f

1. なぜ自作プログラムを systemd でデーモン化するのか?

Linuxでバックグラウンド処理を実行する方法にはいくつか選択肢がありますが、systemd は現代の主要ディストリビューション(Ubuntu, Debian, RHEL, AlmaLinux, Rocky Linux等)における標準のサービス管理フレームワークです。

nohup・cron と systemd の比較

管理方式 自動起動 クラッシュ時再起動 ログ管理 プロセス制御
nohup / screen 不可(手動実行) 不可 ファイルへリダイレクト PID調査とkill
cron (@reboot) 可能 不可 メール / 手動リダイレクト PID調査とkill
systemd サービス 完全自動(enable) 自動復旧(Restart) journald で統合収集 systemctl で一元管理
  • 自動復旧(プロセス監視): 予期せぬエラーでプロセスが終了しても、Restart=always を設定しておけば自動的に再起動します。
  • ライフサイクルの完全管理: プロセスがフォークして子プロセスを生成した場合でも、Linuxの cgroup(Control Groups) により孤児プロセスを残さず確実に停止・再起動できます。
  • 実行権限の分離: User=Group= を指定することで、root 権限ではなく専用の非特権ユーザーで安全に実行できます。
  • 起動依存関係の制御: 「ネットワークが完全に開通した後(network-online.target)」や「データベース起動後(mysql.service)」など、起動順序を厳密に制御できます。

ユニットファイルの配置場所

systemd の設定ファイル(ユニットファイル)は複数箇所に存在しますが、自作サービスを作成する場合は必ず /etc/systemd/system/ に配置します。

  • /etc/systemd/system/(推奨・自作サービス用): システム管理者が手動で作成・カスタマイズする最優先ディレクトリ。パッケージ更新で上書きされません。
  • /lib/systemd/system/ または /usr/lib/systemd/system/ apt や dnf などのパッケージマネージャーがインストールするデフォルト定義。直接編集してはいけません。
  • ~/.config/systemd/user/(ユーザーサービス用): 一般ユーザー権限で管理するサービス用(systemctl --user で操作)。

2. ユニットファイル(.service)の基本構造と主要ディレクティブ

サービス定義ファイル(.service)は、主に [Unit][Service][Install] の3つのセクションで構成されます。

[Unit]
Description=My Custom Python Application
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/venv/bin/python /opt/myapp/main.py
Restart=always
RestartSec=5s
Environment="APP_ENV=production" "PORT=8080"
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

[Unit] セクション:サービスのメタ情報と依存関係

サービス全般の説明や、他のユニットとの起動順序・依存関係を定義します。

ディレクティブ 説明と実務での使い分け
Description サービスの説明文。systemctl status やシステムログに表示されます。
After 起動順序の指定。指定したターゲットやサービスが起動したに本サービスを起動します(例: After=network-online.target)。※依存関係自体は作らないため、対象が起動失敗しても本サービスは起動を試みます。
Before 指定したターゲットやサービスよりに本サービスを起動します。
Wants 軟弱な依存関係。本サービス起動時に対象も同時に起動しようと試みますが、対象が起動失敗しても本サービスの起動は継続します(推奨)。
Requires 強固な依存関係。対象が起動に成功しない限り、本サービスは起動できません。不要な指定は起動連鎖エラーの原因になるため注意が必要です。

[Service] セクション:プロセスの起動・停止・動作設定

プロセスの実行コマンド、実行ユーザー、再起動ポリシーなど、サービスの振る舞いの中核を定義します。

ディレクティブ 説明と設定値
Type プロセスの起動挙動(下記で詳解)。通常は simple(常駐プロセス)または oneshot(単発バッチ)。
ExecStart 起動時に実行するコマンドライン(必須)必ず絶対パスで記述します(例: /usr/bin/python3 /opt/app.py)。
ExecStop サービス停止時に実行するカスタムコマンド(省略時はメインプロセスに SIGTERM が送信されます)。
ExecReload systemctl reload 実行時に設定再読込を行うコマンド。
WorkingDirectory コマンド実行時のカレントディレクトリ。相対パスでファイルを参照するプログラムに必須。
User / Group サービスを実行する Linux ユーザー名およびグループ名。セキュリティ上、root 以外の専用ユーザー指定を推奨。
Restart 自動再起動の条件。no(再起動しない)、on-failure(異常終了時のみ)、always(終了理由を問わず常に再起動)。常駐デーモンには always または on-failure を指定。
RestartSec プロセス終了から再起動を試みるまでの待機時間(例: 5s, 10s)。急激な再起動ループ(フラッピング)を防ぎます。
Environment プロセスに渡す環境変数(例: Environment="PORT=8000" "NODE_ENV=production")。
EnvironmentFile 環境変数を記述した設定ファイル(.env 形式)の絶対パスを指定(例: EnvironmentFile=/etc/myapp.env)。
StandardOutput
StandardError
標準出力・標準エラー出力の転送先。デフォルトは journaljournalctl で閲覧可能)。

Type= の主な種類と選択基準

  • Type=simple(デフォルト・最頻出): ExecStart で指定したプロセスがそのままメインプロセスとして常駐し続ける場合に使用します(Node.js, Pythonスクリプト, FastAPI, Go, 前面で動くデーモン)。
  • Type=oneshot 処理を一度実行して正常終了するバッチ処理や初期化スクリプトに使用します。RemainAfterExit=yes を併用すると、終了後も active 状態を維持できます。
  • Type=forking 起動後にプロセス自らフォークしてバックグラウンドへ移行する、伝統的なデーモンプログラム(Apache, Nginx, 旧来のCプログラム等)に使用します。PIDFile= の指定が推奨されます。
  • Type=notify 起動完了時に sd_notify() API を通じて systemd に通知を送る高度な常駐プログラム用。

[Install] セクション:自動起動(enable)の紐付け

sudo systemctl enable コマンドを実行した際に、どのターゲット(ランレベルに相当)に登録するかを指定します。

  • WantedBy=multi-user.target 通常のサーバー環境(CUIマルチユーザーモード)で OS 起動時に自動起動させたい場合は、ほぼ常にこれを指定します。

3. 【実践例1】自作シェルスクリプトを常駐デーモン化する手順

サーバーの稼働状況やログを定期的に監視・記録する Bash スクリプトを、systemd サービスとして常駐化させる具体的な手順です。

ステップ1:実行対象のスクリプトを作成する

まず、/opt/scripts/server-monitor.sh に常駐型シェルスクリプトを配置し、実行権限を付与します。

sudo mkdir -p /opt/scripts
sudo nano /opt/scripts/server-monitor.sh

スクリプトの内容例:

#!/bin/bash
# /opt/scripts/server-monitor.sh

echo "[$(date '+%Y-%m-%d %H:%M:%S')] サーバー監視サービスを開始しました (PID: $$)"

# SIGTERM / SIGINT をトラップしてクリーンアップ終了
cleanup() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] 停止シグナルを受信しました。終了します。"
    exit 0
}
trap cleanup SIGTERM SIGINT

while true; do
    LOAD_AVG=$(awk '{print $1}' /proc/loadavg)
    DISK_USAGE=$(df -h / | awk 'NR==2 {print $5}')
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] LoadAvg: ${LOAD_AVG} | Disk /: ${DISK_USAGE}"
    sleep 60
done

実行権限(chmod +x)を付与し、手動でテスト実行できることを確認します。

sudo chmod +x /opt/scripts/server-monitor.sh
# 動作確認(Ctrl+C で停止)
/opt/scripts/server-monitor.sh

ステップ2:ユニットファイルを作成する

/etc/systemd/system/server-monitor.service を作成します。ヒアドキュメントと sudo tee コマンド を使用するとワンライナーで一発作成できます。

sudo tee /etc/systemd/system/server-monitor.service << 'EOF'
[Unit]
Description=Server Resource Monitor Daemon
After=network.target

[Service]
Type=simple
ExecStart=/bin/bash /opt/scripts/server-monitor.sh
Restart=always
RestartSec=5s
KillMode=mixed
TimeoutStopSec=10s

[Install]
WantedBy=multi-user.target
EOF

ステップ3:反映・起動・自動起動の有効化

ユニットファイルを作成したら、以下の順序で反映と起動を行います。

# 1. systemd に新しい設定ファイルを読み込ませる
sudo systemctl daemon-reload

# 2. サービスを即時起動し、OS起動時の自動起動を有効化
sudo systemctl enable --now server-monitor.service

# 3. 稼働状態を確認
sudo systemctl status server-monitor.service

Active: active (running) と緑色で表示されていれば、自作サービスの常駐化と自動起動設定は完了です。

4. 【実践例2】Pythonスクリプト(仮想環境venv含む)を常駐化する手順

APIサーバーやDiscord/Slack Bot、定期収集プログラムなどの Python アプリケーションを常駐化する場合、「仮想環境(venv)の指定」「専用非特権ユーザーでの実行」「ログのバッファリング解除」が実務上の重要なポイントになります。

ステップ1:専用ユーザーとアプリディレクトリの準備

セキュリティのため、root 権限ではなく専用のシステムユーザー(例: pyapp)を作成して実行します。

# ログイン不能なシステムユーザーを作成
sudo useradd -r -s /bin/false -d /opt/mybot pyapp

# ディレクトリ作成と権限設定
sudo mkdir -p /opt/mybot
sudo chown -R pyapp:pyapp /opt/mybot

Python 仮想環境の構築とスクリプトの作成:

# 仮想環境の作成
sudo -u pyapp python3 -m venv /opt/mybot/venv

# サンプルスクリプト /opt/mybot/app.py
sudo -u pyapp tee /opt/mybot/app.py << 'EOF'
import time
import sys
import os

print(f"Python Bot Started! ENV={os.getenv('APP_ENV', 'unknown')}", flush=True)

try:
    while True:
        print("Bot is processing heartbeat task...", flush=True)
        time.sleep(30)
except KeyboardInterrupt:
    print("Bot stopped gracefully.", flush=True)
EOF

ステップ2:Python専用ユニットファイルの作成

/etc/systemd/system/mybot.service を作成します。

sudo tee /etc/systemd/system/mybot.service << 'EOF'
[Unit]
Description=My Python Heartbeat Bot
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=pyapp
Group=pyapp
WorkingDirectory=/opt/mybot

# 仮想環境内の python バイナリを絶対パスで指定
ExecStart=/opt/mybot/venv/bin/python /opt/mybot/app.py

# Pythonの標準出力バッファリングを無効化(journalctl に即時反映)
Environment="PYTHONUNBUFFERED=1"
Environment="APP_ENV=production"

# クラッシュ時の自動再起動設定
Restart=always
RestartSec=5s

# 停止シグナルと猶予時間
KillSignal=SIGTERM
TimeoutStopSec=15s

[Install]
WantedBy=multi-user.target
EOF

Python特有の設定ポイント:

  • ExecStart=/opt/mybot/venv/bin/python 仮想環境(venv)を activate する必要はありません。venv/bin/python を直接指定するだけで、その仮想環境にインストールされたライブラリが自動的に読み込まれます。
  • Environment="PYTHONUNBUFFERED=1" Python の標準出力バッファリングをオフにします。これを指定しないと、print() の出力が journalctl にリアルタイムで表示されない現象が発生します。

ステップ3:反映と起動テスト

sudo systemctl daemon-reload
sudo systemctl enable --now mybot.service
sudo systemctl status mybot.service

5. 【実践例3】1回だけ実行するバッチ処理(Type=oneshot)の作成

常駐するデーモンではなく、「OS起動時に1度だけ初期化スクリプトを実行したい」「日次バックアップを実行したい」という場合は Type=oneshot を使用します。

sudo tee /etc/systemd/system/system-backup.service << 'EOF'
[Unit]
Description=One-time System Daily Backup
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup-script.sh
# 終了後も active (exited) 状態を維持して起動済みとみなす場合
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF
  • RemainAfterExit=yes を指定すると、スクリプト実行が終了した後もサービスのステータスが active (exited) として保持されます。他のサービスが Requires=system-backup.service で依存している場合に便利です。
  • 定期実行したい場合は、cron を利用するか、対応する systemd.timer(タイマーユニット)を組み合わせることで柔軟なスケジュール実行が可能です。基本操作は cron設定の基本とよく使う書き方 も参照してください。

6. 自作サービスの反映・管理・自動起動コマンド一覧

自作サービス作成後に日常運用で頻繁に使用するコマンドのまとめです。詳細は systemctlコマンドの使い方 も併せてご確認ください。

操作目的 実行コマンド
ユニット定義の再読込 sudo systemctl daemon-reload
サービスの起動 / 停止 / 再起動 sudo systemctl start <service>
sudo systemctl stop <service>
sudo systemctl restart <service>
設定変更の反映(Reload) sudo systemctl reload <service>
詳細ステータスの確認 sudo systemctl status <service>
自動起動の有効化(即時起動も同時に行う) sudo systemctl enable --now <service>
自動起動の無効化 sudo systemctl disable <service>
稼働中かどうかの確認(スクリプト判定用) systemctl is-active <service> (activeなら終了コード0)
自動起動が有効かの確認 systemctl is-enabled <service> (enabledなら終了コード0)
ユニットファイルの構文エラー事前チェック systemd-analyze verify /etc/systemd/system/<service>.service

7. journalctl を使ったログ確認とデバッグ

systemd で管理されるサービスは、標準出力(stdout)および標準エラー出力(stderr)が自動的に systemd-journald に収集されます。標準エラー出力のリダイレクト を手動で行う必要はありません。

よく使うログ確認コマンド

# 1. サービスのログをリアルタイムで追尾(tail -f 相当)
journalctl -u mybot.service -f

# 2. 直近100行のログを表示(ページャーを使わず一括出力)
journalctl -u mybot.service -n 100 --no-pager

# 3. 今回のOS起動以降のログのみを表示
journalctl -u mybot.service -b

# 4. 指定した時刻以降のエラーログのみを抽出
journalctl -u mybot.service --since "1 hour ago" -p err

# 5. プロセス終了コードや詳細情報を表示
journalctl -u mybot.service -e -o verbose

8. 実務でよくあるエラーとアンチパターン解決チェックリスト

自作サービスが起動しない、または意図通りに動かない場合の代表的な原因と対処法です。

発生する問題・エラー 主な原因 解決手順・対処法
status=203/EXEC 実行ファイルのパス間違い、または実行権限(+x)不足、シバン(#!/bin/bash)の誤り。 ExecStart を絶対パスで記述する。chmod +x /path/to/script を実行する。
status=217/USER User=Group= に存在しないユーザー・グループが指定されている。 id <username> でユーザーが存在するか確認し、スペルミスを修正する。
設定変更が反映されない .service ファイル編集後に daemon-reload を実行していない。 sudo systemctl daemon-reload && sudo systemctl restart <service> を実行する。
Pythonのログが出ない 標準出力がバッファリングされている。 [Service]Environment="PYTHONUNBUFFERED=1" を追加する。
起動直後に再起動を繰り返す(フラッピング) スクリプトが即終了している、またはネットワーク未接続でクラッシュ。 After=network-online.target を指定する。RestartSec=5s で間隔を空け、スクリプトの exit コードを確認する。
相対パスのファイルが見つからない WorkingDirectory が設定されていないため、カレントディレクトリが / になっている。 [Service]WorkingDirectory=/opt/myapp を明示的に設定する。
Permission denied で起動失敗 指定した User= に対象スクリプトやログ保存先ディレクトリの読み書き権限がない。 sudo chown -R <user>:<group> /opt/myapp で適切な所有権を設定する(詳細は Permission deniedの直し方 参照)。

詳細デバッグ:systemd-analyze verify の活用

ユニットファイルを編集した際は、サービスを起動する前に systemd-analyze verify コマンドで構文チェックを行うと、記述ミスを即座に発見できます。

sudo systemd-analyze verify /etc/systemd/system/mybot.service

※何も出力されなければ構文チェック合格です。誤ったディレクティブ名や無効な設定値がある場合は警告メッセージが表示されます。

まとめ & 関連リファレンス

Linux で自作スクリプトやプログラムをデーモン化する際は、systemd ユニットファイル(/etc/systemd/system/*.service)を作成するのが最も堅牢で信頼性の高いアプローチです。

  • ユニット作成の基本: [Unit] で依存関係、[Service] で絶対パスによる ExecStartRestart=always[Install]multi-user.target を設定。
  • セキュリティ対策: root 実行を避け、専用の非特権ユーザー(User=)を指定。
  • 反映と運用の鉄則: 編集後は必ず sudo systemctl daemon-reload を実行し、journalctl -u <service> -f でログを監視。

本番環境での再起動戦略やフラッピング防止設計についてさらに深く学びたい方は、systemdユニットの基本と再起動戦略 もあわせてご活用ください。

関連記事

Bash玄

はじめまして!Bash玄です。

エンジニアとしてシステム運用に携わる中で、手作業の多さに限界を感じ、Bashスクリプトを活用して業務を効率化したのがきっかけで、この道に入りました。「手作業は負け」「スクリプトはシンプルに」をモットーに、誰でも実践できるBashスクリプトの書き方を発信しています。

このサイトでは、Bashの基礎から実践的なスクリプト作成まで、初心者でもわかりやすく解説しています。少しでも「Bashって便利だな」と思ってもらえたら嬉しいです!

# 好きなこと
- シンプルなコードを書くこと
- コマンドラインを快適にカスタマイズすること
- 自動化で時間を生み出すこと

# このサイトを読んでほしい人
- Bashに興味があるけど、何から始めればいいかわからない人
- 定型業務を自動化したい人
- 効率よくターミナルを使いこなしたい人

Bashの世界に一歩踏み出して、一緒に「Bash道」を極めていきましょう!

Bash玄をフォローする

コメント