※本ページには広告(アフィリエイトプログラム等)が含まれます。
※本ページには広告(アフィリエイトプログラム等)が含まれます。掲載しているコマンドの出力は 2026年8月16日時点で運用中の VPS(Debian 12・メモリ 958MB)から採取した実測値です。ユーザー名はマスクしています。
🎯 結論:常駐させたいなら unit ファイル 10 行。既存サービスの変更は Drop-In で上書きする
結論から書きます。VPS で自作のスクリプトを常駐させたいなら、`nohup` でも `screen` でもなく systemd の unit ファイルを書くのが正解です。10 行ほどで、再起動後の自動復帰・異常終了時の再起動・ログの集約がまとめて手に入ります。
そしてもう 1 つ、意外と知られていないのが既存サービス(nginx や php-fpm)の設定を変えたいとき、元の unit ファイルを編集してはいけないという点です。パッケージ更新で上書きされます。正解は Drop-In です。
- ✅ 自作スクリプトを常駐 → `/etc/systemd/system/<name>.service` を作る
- ✅ 落ちても復帰させたい → `Restart=always` と `RestartSec`
- ✅ 既存サービスの設定変更 → Drop-In(`systemctl edit`)。元ファイルは触らない
- ✅ ログを見たい → `journalctl -u <name> -f`。標準出力が自動で入る
- ⚠️ メモリの少ない VPS → サービスごとの使用量を測ってから増やす
- ⛔ `nohup … &` で放置 → 再起動で消える。落ちても気づけない
まず現状を測る
いくつサービスが動いているか
手を入れる前に、いま何が動いているかを数えます。
$ systemctl list-units --type=service --state=running --no-pager --no-legend | wc -l
19
メモリ 958MB の VPS で 19 個。ここに自作のサービスを足していくので、余裕がどれくらいあるかを先に把握しておきます。
どのサービスがメモリを食っているか
systemd はサービス単位でメモリ使用量を持っているので、そのまま集計できます。
$ systemctl list-units --type=service --state=running --no-pager --no-legend \
| awk '{print $1}' \
| while read u; do
m=$(systemctl show "$u" -p MemoryCurrent --value)
[ "$m" != "[not set]" ] && echo "$m $u"
done | sort -rn | head -6 \
| awk '{printf "%8.1f MB %s\n", $1/1048576, $2}'
実際の出力がこれです。
| メモリ | サービス |
|---|---|
| 325.0 MB | php8.3-fpm.service |
| 203.1 MB | cron.service |
| 75.7 MB | mariadb.service |
| 21.7 MB | nginx.service |
| 18.3 MB | systemd-journald.service |
| 15.8 MB | webmin.service |
php-fpm が 325MB で突出しています。全体 958MB のうち 3 分の 1 です。ここに 7B クラスの LLM を載せようとしても入らない、といった判断がこの表からできます。
起動にかかっている時間
$ systemd-analyze
Startup finished in 1.788s (kernel) + 4.451s (userspace) = 6.240s
$ systemd-analyze blame | head -6
8.753s chkrootkit.service
1.993s networking.service
1.522s nginx.service
1.500s [email protected]
1.408s php8.3-fpm.service
1.250s mariadb.service
ここで引っかかる人が多いので補足します。`blame` の合計は起動時間と一致しません。chkrootkit が 8.753 秒かかっているのに全体は 6.240 秒です。並列に起動していて、しかも起動完了の判定を待たないユニットがあるからです。`blame` は「遅いユニット探し」には使えますが、足し算してはいけません。
自作スクリプトを常駐させる unit ファイル
最小構成
`/etc/systemd/system/myapp.service` を作ります。これだけで動きます。
[Unit]
Description=My background worker
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=<user>
WorkingDirectory=/home/<user>/myapp
ExecStart=/usr/bin/python3 /home/<user>/myapp/worker.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
各行が何をしているか
| ディレクティブ | 役割 | 省くとどうなるか |
|---|---|---|
After / Wants=network-online.target |
ネットワーク確立後に起動 | 起動直後に接続失敗して落ちる |
User |
実行ユーザー | root で動く(危険) |
WorkingDirectory |
カレントディレクトリ | 相対パスの読み込みが全部ずれる |
ExecStart |
実行コマンド(絶対パス必須) | PATH が対話シェルと違うので見つからない |
Restart=always |
落ちたら再起動 | 落ちたまま止まる |
RestartSec |
再起動までの待ち | 即死ループで CPU を焼く |
とくに `ExecStart` は絶対パスが要点です。systemd の実行環境は対話シェルと PATH が違うので、`python3` と書いただけでは見つからないことがあります。
有効化して起動する
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now myapp
$ systemctl status myapp --no-pager
$ journalctl -u myapp -f
`daemon-reload` を忘れると、ファイルを直したのに反映されません。変更したら必ず打ちます。標準出力は自動で journal に入るので、ログ出力の仕組みを自前で書く必要はありません。
既存サービスの設定は Drop-In で変える
元の unit ファイルを編集してはいけない
`/lib/systemd/system/php8.3-fpm.service` のようなパッケージが配布しているファイルを直接編集すると、更新で上書きされます。気づかないうちに設定が消えるので、事故として最悪の部類です。
Drop-In なら追記で上書きできる
正解は `/etc/systemd/system/<name>.service.d/` に断片を置くことです。実際にこの VPS で使っている例がこれです。
$ cat /etc/systemd/system/php8.3-fpm.service.d/umask.conf
[Service]
UMask=0002
たった 2 行です。これで php-fpm が作るファイルのパーミッションが変わり、Web サーバーと CLI の両方から書けるようになります。元のファイルは 1 文字も触っていません。
合成結果を確認する
Drop-In が効いているかは `systemctl cat` で見ます。元ファイルと断片が順に表示され、後ろが勝ちます。
$ systemctl cat php8.3-fpm | tail -8
RestrictNamespaces=true
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/php8.3-fpm.service.d/umask.conf
[Service]
UMask=0002
`status` にも `Drop-In:` の行が出るので、こちらでも確認できます。
$ systemctl status php8.3-fpm --no-pager | head -8
● php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager
Loaded: loaded (/lib/systemd/system/php8.3-fpm.service; enabled; preset: enabled)
Drop-In: /etc/systemd/system/php8.3-fpm.service.d
└─umask.conf
Active: active (running) since Sun 2026-08-16 17:00:37 JST; 4h 56min ago
Main PID: 691 (php-fpm8.3)
Status: "Processes active: 0, idle: 5, Requests: 19379, slow: 0, Traffic: 2.20req/sec"
Memory: 324.9M
`systemctl edit php8.3-fpm` を使うと、この断片ファイルを対話的に作れます。手で `mkdir` する必要はありません。
メモリの少ない VPS で気をつけること
制限をかけて巻き添えを防ぐ
メモリ 958MB のような環境では、1 つのプロセスが暴走すると関係ないサービスまで OOM Killer に殺されます。自作サービスには上限を付けておくと安全です。
[Service]
MemoryMax=200M
MemoryHigh=150M
CPUQuota=50%
Restart の暴走を止める
`Restart=always` は便利ですが、起動直後に必ず落ちる不具合が入ると永久に再起動し続けます。回数の上限を付けます。
[Unit]
StartLimitIntervalSec=300
StartLimitBurst=5
止めたいときは disable まで
`stop` だけだと再起動で戻ってきます。恒久的に止めるなら `disable` まで打ちます。
$ sudo systemctl stop myapp
$ sudo systemctl disable myapp
よくある質問
Q1. cron と systemd、どちらで常駐させるべきですか?
結論:常駐なら systemd、定時実行なら cron か systemd timer です。ずっと動き続けるプロセスを cron で起こすと二重起動の管理が必要になります。`Restart=always` で自動復帰できる systemd のほうが素直です。
Q2. unit ファイルを直したのに反映されません
結論:`sudo systemctl daemon-reload` を打っていないからです。systemd は unit ファイルをメモリに読み込んで持っているので、ファイルを変えただけでは反映されません。変更したら必ず `daemon-reload` → `restart` の順で打ちます。
Q3. ExecStart のコマンドが見つからないと言われます
結論:絶対パスで書いてください。systemd の実行環境は対話シェルと PATH が異なります。`which python3` で確認した絶対パス(例:`/usr/bin/python3`)をそのまま書くのが確実です。
Q4. 既存サービスの設定を変えたいときは?
結論:Drop-In を使います。`/lib/systemd/system/` のファイルを直接編集するとパッケージ更新で上書きされます。`systemctl edit <name>` で `/etc/systemd/system/<name>.service.d/` に断片を作り、`systemctl cat` で合成結果を確認してください。
Q5. systemd-analyze blame の合計が起動時間と合いません
結論:並列に起動しているので合いません。実際にこの VPS では blame の最上位が 8.753 秒なのに、起動全体は 6.240 秒でした。blame は遅いユニットを見つける道具であって、足し算する表ではありません。
Q6. ログはどこに出ますか?
結論:標準出力・標準エラーがそのまま journal に入ります。`journalctl -u <name> -f` で追えます。ログファイルの出力先を自前で設計する必要はありません。
まとめ
systemd の unit ファイルは 10 行ほどで書けて、再起動後の自動復帰・異常終了時の再起動・ログ集約がまとめて手に入ります。`nohup` で放置する理由はもうありません。
- 🎯 自作スクリプトの常駐 → `/etc/systemd/system/` に unit を作り `enable –now`
- 🎯 既存サービスの変更 → Drop-In(実例:`UMask=0002` の 2 行)
- 🎯 メモリの少ない VPS → `MemoryMax` と `StartLimitBurst` で巻き添えを防ぐ
出力はすべて 2026年8月16日時点で運用中の VPS から採取したものです。ログの追い方はAI を使ったログの切り分け、サーバーの初期構築はさくらの VPS の初期セットアップ、メモリが足りないときはスワップの追加にまとめています。
