2026年5月21日
2026年5月21日
systemdのユニットファイルを作成する方法(serviceとtimer・自動再起動・上書き設定)
自作のアプリやスクリプトを、サーバーの起動時に自動で立ち上げたい、落ちたら自動で再起動させたい、決まった時刻に実行したい、というときに使うのがsystemdの「ユニットファイル」です。ユニットファイルを置くと、Nginxなどのサービスと同じように systemctl で起動・停止でき、ログは journalctl で読めます。
この記事のコマンドと設定は、2026年10月時点のUbuntu 24.04、Debian 12、AlmaLinux/Rocky Linux 9など、systemdを使う現行のディストリビューションで共通に使えるものです。
ユニットファイルの置き場所
| 場所 | 用途 |
|---|---|
/etc/systemd/system/ |
管理者が作るユニット、上書き設定(ここに置く) |
/usr/lib/systemd/system/(Debian系では /lib/systemd/system/ も) |
パッケージが入れるユニット。更新で上書きされるので直接編集しない |
~/.config/systemd/user/ |
ログインユーザーごとのユニット(systemctl --user で操作) |
ファイル名は 名前.service のように、種類を表す拡張子で終えます。この記事では myapp.service を例にします。
手順1: 実行用のユーザーを作る
アプリをrootで動かす必要がなければ、専用のユーザーを作ります。ログインできないシステムユーザーにしておきます。
sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp
sudo chown -R myapp:myapp /opt/myapp
手順2: serviceユニットを書く
Node.jsのアプリ /opt/myapp/server.js を常駐させる例です。
sudo nano /etc/systemd/system/myapp.service
[Unit]
Description=My Application
# ネットワークの設定が済んでから起動する
After=network-online.target
Wants=network-online.target
[Service]
Type=exec
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
Environment=NODE_ENV=production
EnvironmentFile=-/etc/myapp.env
ExecStart=/usr/bin/node /opt/myapp/server.js
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
ユニットファイルのコメントは、# か ; で始まる行に書きます。Type=simple # 説明 のように値の後ろに書くと、コメントではなく値の一部として扱われるので注意します。
各セクションの役割は次のとおりです。
| セクション | 主な項目 | 意味 |
|---|---|---|
[Unit] |
Description |
systemctl status などに表示される説明 |
After / Wants |
起動の順序と、一緒に起動したいユニット | |
[Service] |
Type |
起動完了をどう判断するか(次の表) |
User / Group |
実行するユーザーとグループ | |
WorkingDirectory |
作業ディレクトリ | |
Environment / EnvironmentFile |
環境変数。EnvironmentFile の前の - は、ファイルが無くてもエラーにしない指定 |
|
ExecStart |
起動するコマンド | |
Restart / RestartSec |
再起動の条件と、再起動までの待ち時間 | |
[Install] |
WantedBy |
systemctl enable したとき、どのターゲットから起動されるか。通常のサービスは multi-user.target |
ExecStart のプログラムは /usr/bin/node のように / から始まるフルパスで書くことを勧めます。ファイル名だけを書いた場合、systemdはコンパイル時に決まった検索パス(/usr/local/bin や /usr/bin など)から探し、シェルの PATH やログイン時の設定ファイル(.bashrc など)は読みません。nvmなどでユーザーのホームに入れたNode.jsを使う場合は、そのフルパスを書きます。
Typeの選び方
| Type | 起動完了とみなす時点 | 向いているプログラム |
|---|---|---|
simple |
プロセスを起動した直後(ExecStart があり Type を省略したときの既定値) |
フォアグラウンドで動き続けるもの |
exec |
プログラムの実行に成功した時点 | simple と同じ。実行ファイルが無いときに systemctl start が失敗として返る |
forking |
起動したプロセスが子プロセスを残して終了した時点 | 自分でデーモン化する古いタイプのプログラム(PIDFile も指定する) |
oneshot |
プロセスが終了した時点 | 一度実行して終わる処理(バックアップ、timerから呼ぶ処理など) |
notify |
プログラムが準備完了を通知した時点 | sd_notify に対応したプログラム |
自作のアプリはフォアグラウンドで動かし、Type=exec(または simple)にするのが扱いやすい形です。標準出力と標準エラーに書いたログは、そのままjournalに記録されます。
Restartの選び方
| 値 | 再起動する場合 |
|---|---|
no |
再起動しない(既定値) |
on-failure |
0以外の終了コード、シグナルによる終了、タイムアウトなど異常な終了のとき |
on-abnormal |
シグナルによる終了やタイムアウトのとき(終了コードによる終了では再起動しない) |
always |
正常終了を含め、systemctl stop 以外で止まったとき |
短い間に再起動が続くと、systemdは起動の回数制限(既定では10秒間に5回)にかかったとして再起動をやめ、ユニットを failed にします。制限は [Unit] セクションの StartLimitIntervalSec と StartLimitBurst で変えられます。起動直後に落ち続ける場合は、制限を緩めるより先に journalctl で原因を確かめます。
手順3: 読み込み、有効化して動作を確かめる
# 書式の誤りを確かめる
sudo systemd-analyze verify /etc/systemd/system/myapp.service
# systemd にユニットファイルを読み込ませる
sudo systemctl daemon-reload
# 起動し、サーバー起動時にも自動で起動するようにする
sudo systemctl enable --now myapp.service
# 状態とログを確かめる
systemctl status myapp.service
journalctl -u myapp.service -n 50
journalctl -u myapp.service -f
ユニットファイルを書き換えたら、daemon-reload を実行してから systemctl restart myapp で反映します。daemon-reload を忘れると、systemctl status に「changed on disk. Run 'systemctl daemon-reload' to reload units.」という警告が表示され、古い設定のまま動きます。ログの読み方はjournalctlでsystemdのログを確認する方法を参照してください。
アプリを再起動せずに設定を読み直させたい場合は、アプリが対応していれば ExecReload=/bin/kill -HUP $MAINPID を [Service] に書き、systemctl reload myapp で使います。
既存のユニットを上書きする(drop-in)
パッケージが入れたユニット(Nginx、PHP-FPMなど)を変えたいときは、元のファイルを編集せず、上書き用のファイル(drop-in)を作ります。
sudo systemctl edit nginx.service
エディタが開くので、変えたい項目だけを書いて保存します。内容は /etc/systemd/system/nginx.service.d/override.conf に保存され、保存時に自動で読み込まれます(反映には systemctl restart nginx が必要です)。
[Service]
Restart=on-failure
RestartSec=5s
LimitNOFILE=65535
ExecStart のように複数回書ける項目は、追記扱いになります。置き換えたい場合は、空の ExecStart= で一度消してから書きます。
[Service]
ExecStart=
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
上書きの結果は systemctl cat nginx.service で確かめられ、sudo systemctl revert nginx.service で上書きを取り消せます。
timerで定期的に実行する
cronの代わりに、timerユニットで定期実行できます。timerは、同じ名前のserviceユニットを起動します。ここではWordPressのWP-Cronを、アクセスに頼らず5分ごとにWP-CLIで実行する例を示します。WP-CLIが /usr/local/bin/wp に入っていて、WordPressが /var/www/html にあり、Webサーバーが www-data ユーザーで動いている環境の例です。
/etc/systemd/system/wp-cron.service:
[Unit]
Description=Run WordPress cron events
[Service]
Type=oneshot
User=www-data
ExecStart=/usr/local/bin/wp cron event run --due-now --path=/var/www/html --quiet
/etc/systemd/system/wp-cron.timer:
[Unit]
Description=Run WordPress cron events every 5 minutes
[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
# 有効にするのは timer のほう
sudo systemctl enable --now wp-cron.timer
# 次の実行予定と前回の実行時刻
systemctl list-timers wp-cron.timer
# 手動で一度実行して結果を確かめる
sudo systemctl start wp-cron.service
journalctl -u wp-cron.service -n 20
timerで動かすようにしたら、wp-config.php に define( 'DISABLE_WP_CRON', true ); を書いて、アクセスのたびにWP-Cronが動く仕組みを止めます。WP-Cronをサーバー側で動かす考え方はWP-Cronをサーバーのcronで実行する方法も参照してください。
時刻を指定する場合は OnCalendar を使います。
[Timer]
# 毎日 3:00 に実行する
OnCalendar=*-*-* 03:00:00
# 電源が切れていて実行できなかった分を、次の起動時に実行する
Persistent=true
OnCalendar の書式は、systemd-analyze calendar '*-*-* 03:00:00' で、次にいつ実行されるかを表示して確かめられます。
権限を絞る設定
サービスが侵害されたときの影響を小さくするため、[Service] に次のような設定を加えられます。
[Service]
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=full
ProtectHome=yes
| 項目 | 内容 |
|---|---|
NoNewPrivileges=yes |
setuidなどで権限を上げられないようにする |
PrivateTmp=yes |
サービス専用の /tmp を使う |
ProtectSystem=full |
/usr、/boot、/etc などを読み取り専用にする |
ProtectHome=yes |
/home、/root などを見えなくする |
アプリがホームディレクトリや /etc に書き込む場合は動かなくなるので、1つずつ加えて動作を確かめます。systemd-analyze security myapp.service で、ユニットの制限の状況を一覧できます。
うまく動かないとき
status=203/EXEC:ExecStartのプログラムが見つからないか、実行権限が無い。パスとchmod +xを確かめるstatus=200/CHDIR:WorkingDirectoryが無いか、Userで指定したユーザーが入れないstatus=217/USER:Userで指定したユーザーが存在しない- 手動では動くのにサービスでは動かない: 環境変数(
PATHやHOMEなど)の違いが多い原因です。必要な値をEnvironmentかEnvironmentFileに書く
まとめ
自作のプログラムをサービスにするには、/etc/systemd/system/ に [Unit]・[Service]・[Install] の3つのセクションを持つ .service ファイルを置き、daemon-reload の後に systemctl enable --now で有効にします。常駐するアプリは Type=exec と Restart=on-failure が扱いやすく、ログは journalctl -u で確かめます。既存のユニットは systemctl edit で上書きし、定期実行はserviceとtimerを組み合わせて作ります。