VPSでアプリを動かし始めると,必ず「サーバーを再起動したらプロセスが止まったまま」「クラッシュに気づかず半日落ちていた」という壁にぶつかる.この壁を越える鍵が,現代Linuxの中核であるsystemdだ.

systemdは「サービスを起動するもの」と思われがちだが,実体はサービス管理・定期実行・ログ収集・依存関係の解決・リソース制御を束ねる統合基盤だ.ここを理解しているかどうかで,VPS運用の安定性は天と地ほど変わる.

この記事は,VPSで自分のアプリ・Bot・バッチを動かす個人開発者,これからインフラを本気で学ぶエンジニアに向けて書いている.単なるコマンド集ではなく,「なぜそう書くのか」まで踏み込み,読み終えたときに自分のサービスをsystemdで本番運用できる状態を目指す.

扱う範囲は,unitの概念 → .serviceの完全解剖 → 操作コマンド → 自動復帰設計 → timerによる定期実行 → journaldログ → セキュリティ強化 → トラブル対処,と一気通貫だ.長いが,一度通せば一生使える土台になる.

なぜ個人開発者こそsystemdを学ぶべきか

「アプリは書けるが,本番で安定して動かし続けられない」── これは個人開発者に最も多い悩みだ.その大半は,プロセスの起動・監視・復帰をOSに任せられていないことが原因である.

node server.js & でバックグラウンド起動する,nohupで逃がす,screenの中で動かす── こうした「とりあえず動かす」手法は,サーバー再起動で消え,クラッシュで止まり,ログがどこにあるかも分からない.本番運用には耐えられない.

systemdにサービスとして登録すれば,OS起動時に自動で立ち上がり,落ちたら自動で復帰し,ログはjournaldに集約され,依存関係も解決される.「動かし続ける」仕事をOSが肩代わりしてくれるのだ.

そしてこの知識はどのLinuxディストリビューションでも通用する普遍スキルだ.Ubuntu,Debian,AlmaLinux,どれでもsystemdが標準であり,一度覚えれば環境を問わず効く.市場価値の高い「運用できるエンジニア」への最短の一歩になる.

systemdの全体像 ― すべては「unit」という統一概念

systemdを理解する第一歩は,「unit(ユニット)」という統一された管理単位を掴むことだ.systemdは,サービスもタイマーもマウントもソケットも,すべてを「unit」という同じ枠組みで扱う.

unitには種類があり,拡張子で区別される.個人開発でまず押さえるべきは.service(常駐プロセス)と.timer(定期実行)の2つだ.残りは必要になってから学べば十分だ.

unit種別拡張子役割
service.serviceデーモン・常駐プロセスの管理(最重要)
timer.timer定期実行(cronの代替)
socket.socketソケット起動(アクセス時に起動)
mount.mountファイルシステムのマウント
target.target複数unitをまとめた到達点(起動段階)

unitファイルの置き場所も理解しておこう.パッケージ提供の定義は /lib/systemd/system/に,自分で作る・上書きするものは /etc/systemd/system/に置く.後者が優先される.自作サービスは必ず後者に置く,と覚えておけば迷わない.

systemとuserの2階層がある

systemdには,システム全体で動く「system」と,ログインユーザー単位で動く「user」の2階層がある.本番のサーバープロセスは通常systemレベル(sudo systemctl ...)で扱う.

個人開発では基本的にsystemレベルだけ意識すればよいが,「ユーザーがログアウトしても動かしたい」のにuserレベルで登録してしまう,という取り違えは初学者がよくやるミスだ.本番常駐はsystemレベル,と固定しておこう.

.serviceファイルの完全解剖 ― 1行ずつ意味を理解する

systemd運用の9割は.serviceファイルを正しく書けるかに集約される.ここを曖昧にすると一生コピペから抜け出せない.逆に構造を理解すれば,どんなアプリでも自在にサービス化できる.

下記はNode製アプリを本番運用するための実用的な.serviceの例だ.まず全体を見て,その後セクションごとに解剖する.

/etc/systemd/system/myapp.service

[Unit]
Description=My Web App
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=deploy
Group=deploy
WorkingDirectory=/home/deploy/myapp
EnvironmentFile=/home/deploy/myapp/.env
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=3
StartLimitIntervalSec=60
StartLimitBurst=5

[Install]
WantedBy=multi-user.target

[Unit]セクション ― 依存と順序を定義する

Descriptionsystemctl statusなどで表示される説明文だ.後で自分が見て分かる名前にする.

After=network-online.target「ネットワークが完全に使える状態になってから起動する」という順序指定だ.Webアプリやネットワークを使うBotでは必須に近い.Wants=と併記することで,その状態を要求しつつ起動順を制御する.Afterは順序,Wants/Requiresは依存,という役割の違いを押さえておこう.

[Service]セクション ― 起動の本体

Typeはサービスの起動形態を表す最重要項目だ.フォアグラウンドで動き続ける普通のアプリはsimple,自身でデーモン化する旧来型はforking,一度実行して終わる処理はoneshotを使う.迷ったらNode/Python/Goのアプリはsimpleで正しい.

User/Group専用の非root一般ユーザーを指定するのがセキュリティの基本だ.rootでアプリを動かすのは事故の温床になる.WorkingDirectoryはアプリのカレントディレクトリ,EnvironmentFile.envを読み込ませる指定で,秘密情報をunitファイルに直書きせずに済む.

ExecStart必ず絶対パスで書くnodeではなく/usr/bin/nodeだ.systemdはシェルのPATHを継承しないため,相対コマンド名は「No such file」で失敗する.これは最頻出のハマりどころなので強く意識したい.

Type意味使う場面
simpleExecStartのプロセスが本体Node/Python/Go等の常駐アプリ(既定)
forking自身で子プロセスを作りデーモン化旧来型デーモン
oneshot実行して終了する初期化・バッチ処理
notify準備完了をsystemdに通知対応アプリのみ

[Install]セクション ― 自動起動の紐づけ

WantedBy=multi-user.targetは,「通常のサーバー稼働状態になったらこのサービスも起動する」という宣言だ.systemctl enableしたとき,この指定に基づいてシンボリックリンクが張られ,OS起動時の自動起動が有効になる.

[Install]を書き忘れると,enableしても自動起動が効かない.「手動なら動くのに再起動すると上がらない」場合,まずここを疑う.

操作コマンド ― 日常運用で叩く systemctl

unitを書いたら,systemctlで操作する.編集後は必ずdaemon-reloadを打つのが鉄則だ.これを忘れると古い定義のまま動き,「直したのに変わらない」と混乱する.

サービスの基本操作

sudo systemctl daemon-reload        # unit編集後は必須
sudo systemctl enable --now myapp   # 自動起動ON + 即起動
sudo systemctl status myapp         # 状態とログ末尾を確認
sudo systemctl restart myapp        # 再起動
sudo systemctl stop myapp           # 停止
sudo systemctl disable myapp        # 自動起動OFF
コマンド効果
start / stop / restart起動 / 停止 / 再起動(今このとき)
enable / disableOS起動時の自動起動を ON / OFF
enable –now自動起動ON と 即起動 を同時に
status稼働状態・PID・直近ログを表示
is-active / is-enabled稼働中か / 自動起動が有効か を判定(スクリプト向き)
daemon-reloadunitファイルの変更を読み直す

enable(自動起動の設定)とstart(今起動する)は別の概念だという点が初学者の最大の混乱ポイントだ.「enableしたのに動いていない」のは,起動はしていないだけ.両方を一度に効かせたいならenable --nowを使う,と覚えれば事故らない.

自動復帰の設計 ― 「落ちても戻る」をRestartで作り込む

個人開発の本番運用で最も価値が高いのが,クラッシュからの自動復帰だ.アプリは必ずどこかで落ちる.落ちたまま放置されるか,秒で復帰するかは,Restartの設計次第で決まる.

ただしRestart=alwaysを素朴に付けるだけでは不十分だ.「起動直後に即クラッシュ」するバグがあると,無限再起動ループに陥り,CPUを焼き,ログを溢れさせる.これを防ぐのがStartLimit系の指定だ.

Restart値再起動する条件
no再起動しない(既定)
on-failure異常終了(非ゼロ終了・シグナル)時のみ
always正常・異常を問わず常に再起動
on-abnormalシグナル・タイムアウト等の異常時

実務の指針はシンプルだ.常駐し続けるべきアプリはalways,正常終了したら止まってよい処理はon-failureを選ぶ.そして必ずRestartSec(再起動間隔)とStartLimitBurst/IntervalSec(暴走ガード)をセットで指定する.

例えば「RestartSec=3StartLimitIntervalSec=60StartLimitBurst=5」は,「3秒おきに再起動を試み,60秒間に5回失敗したら諦めて止まる」という意味になる.無限ループを防ぎつつ,一時的な障害からは自動で立ち直る,バランスの取れた設定だ.

暴走を止めたあとの復旧

StartLimitに引っかかってサービスがfailed状態で止まったら,原因(多くはバグや設定ミス)を直したうえでsystemctl reset-failed myappでカウンタをリセットし,再度startする.「直したのに起動しない」ときはこのリセットを忘れていることが多い.

暴走停止からの復旧

journalctl -u myapp -n 50    # まず原因をログで確認
# 原因を修正したのち
sudo systemctl reset-failed myapp
sudo systemctl start myapp

systemd timer ― cronより強力で堅牢な定期実行

定期実行といえばcronだが,現代のVPSではsystemd timerを選ぶ価値が大きい.timerは.timer.serviceのペアで動き,cronにない利点を多く持つ.

最大の利点は(1)実行ログがjournaldに残る (2)サーバー停止中に逃した実行を起動後に拾える(Persistent) (3)依存関係や前後関係を制御できる (4)実行の重複を防げることだ.cronの「動いているのかログがなくて分からない」「サーバーが落ちていた間の分が飛ぶ」という弱点を解消する.

毎日3時にバックアップする timer + service

# /etc/systemd/system/backup.service
[Unit]
Description=Daily backup job
[Service]
Type=oneshot
User=deploy
ExecStart=/home/deploy/scripts/backup.sh

# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily at 03:00
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target

Type=oneshotは「実行して終わる処理」を表す.バックアップやバッチはこれだ.OnCalendarが実行スケジュールで,書式は柔軟(weekly*-*-01 04:00=毎月1日など).Persistent=true「停止中に逃した実行を起動後に補う」cron超えの肝だ.

timerの有効化と確認

timerは.serviceではなく.timerの方をenableする点に注意.systemctl list-timersで次回実行時刻が一覧でき,「ちゃんと仕込めているか」が一目で確認できる.これがcronにない安心感だ.

timerの有効化と確認

sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers --all   # 次回実行時刻を確認
sudo systemctl start backup.service  # 手動で即実行テスト

OnCalendarの書式早見

スケジュール書式は慣れると強力だ.代表的なパターンを押さえておこう.

OnCalendar意味
*-*-* 03:00:00毎日 03:00
Mon *-*-* 09:00毎週月曜 09:00
*-*-01 04:00毎月1日 04:00
*-*-* *:0/1515分おき
hourly / daily / weekly省略表記

journaldでログを読む ― journalctl実践

systemdで動かすサービスのログは,すべてjournaldに集約される.アプリが標準出力・標準エラーに吐いた内容が自動で記録されるため,「ログファイルがどこにあるか分からない」問題が消える.

ログを読みこなせるかどうかが,障害対応の速さを決める.journalctlの実用オプションを身につけよう.

journalctl 実用コマンド集

journalctl -u myapp                 # myappのログ全部
journalctl -u myapp -n 100          # 末尾100行
journalctl -u myapp -f              # リアルタイム追従(tail -f相当)
journalctl -u myapp --since "1 hour ago"
journalctl -u myapp -p err          # エラー以上だけ
journalctl -u myapp --since today --no-pager

-uでサービスを絞り,-fでリアルタイム追従,-p errで重要度フィルタ,--sinceで期間指定.この4つを組み合わせれば,「いつ・何が・どう壊れたか」をログから即座に再構成できる.

ログでディスクを溢れさせない

journaldのログは放置すると肥大化し,ディスクを圧迫する./etc/systemd/journald.conf上限を設定しておくのが安全運用の作法だ.SystemMaxUse=500Mのように上限を切れば,古いログから自動で削除される.

journaldのサイズ上限を設定

# /etc/systemd/journald.conf
[Journal]
SystemMaxUse=500M
MaxRetentionSec=30day
# 反映
sudo systemctl restart systemd-journald
journalctl --disk-usage   # 現在の使用量確認

セキュリティ強化 ― systemdでサービスを「閉じ込める」

systemdの隠れた強力機能が,サービス単位のサンドボックス化だ.万一アプリに脆弱性があっても,systemdの制限でOS全体への被害を抑え込める.数行足すだけで防御力が大きく上がる,費用対効果の高い対策だ.

下記の強化オプションを.serviceの[Service]に追加していく.すべてを一度に入れて動かなくなったら,1つずつ外して切り分ければよい.

[Service]に足すハードニング指定

NoNewPrivileges=true       # 権限昇格を禁止
PrivateTmp=true            # /tmpを専用に隔離
ProtectSystem=strict       # /usr等を読み取り専用化
ProtectHome=true           # /homeへのアクセス遮断
ReadWritePaths=/home/deploy/myapp/data  # 書込許可を限定
ProtectKernelTunables=true
RestrictAddressFamilies=AF_INET AF_INET6

ProtectSystem=strictReadWritePathsの組み合わせは特に強力だ.アプリが書き込んでよい場所を明示的に1つだけ許可し,それ以外のファイルシステムを読み取り専用にする.これだけで「侵害されてもファイルを書き換えられない」状態を作れる.

よくあるトラブルと切り分けの定石

最後に,systemd運用で頻出するトラブルと,その切り分けをまとめる.大半はsystemctl statusjournalctl -uの2つで原因にたどり着ける.

「起動できない/すぐ落ちる」

まずsystemctl status myappで終了コードとログ末尾を見る.ExecStartの絶対パス間違い,Userの権限不足,WorkingDirectoryの不在,.envの読み込み失敗がほとんどだ.journalctl -u myapp -n 50でアプリ自身のエラーを確認すれば,アプリのバグか設定の問題かを切り分けられる.

「再起動したら自動で上がらない」

systemctl is-enabled myappenabledになっているか確認する.disabledなら[Install]セクションの記述漏れか,enableのし忘れだ.After=network-online.targetの指定漏れで「ネットワーク前に起動して失敗」しているケースも多い.

「編集したのに反映されない」

daemon-reloadの打ち忘れが定番だ.unitファイルを触ったら必ずsudo systemctl daemon-reloadrestartの順で叩く.これを習慣にするだけで,無駄な混乱の大半が消える.

補論:systemd運用は「壊して直せるVPS」で初めて身につく

ここまで読んでも,systemdは実際に手を動かして,壊して,直すまでは本当の意味で身につかない.unitファイルを書き,わざと失敗させ,journalctlで原因を追い,直す── この往復こそが「運用できるエンジニア」を作る.

その練習場として理想的なのが,スナップショットで元に戻せるVPSだ.思い切った設定変更をしても,失敗したら数分で正常な状態に巻き戻せる.この安心感が,学習のスピードを何倍にも引き上げる.たとえば 高速NVMe・50種類以上のOSテンプレートに対応した国内VPS─シン・VPS─ はNVMe SSDでdaemon-reloadや再起動が軽快に走り,スナップショットで「設定を壊す前」へ即座に復帰できるので,systemdの実験環境として非常に向いている.

さらに,サービスを世に公開するなら独自ドメインが要る.取り扱い400種類以上のドメイン取得サービス─ムームードメイン─ で取得したドメインをVPSに向け,systemdで常駐させたアプリをHTTPSで公開すれば,「OS起動時に自動で立ち上がり,落ちても自動で戻る」本番サービスが,月数百円〜の固定費で完成する.これは個人開発における最もコスパの高い投資の一つだ.

よくある質問(FAQ)

Q1.systemdとPM2やforever,supervisorはどう使い分ける?

PM2等はアプリ層のプロセスマネージャで手軽だが,OS全体の起動シーケンス・ログ・依存解決を統合するのはsystemdだ.Node限定の手軽さならPM2,OS標準で言語非依存・堅牢に運用するならsystemd,が指針になる.本番の土台はsystemdに寄せ,必要ならその中でPM2を動かす構成もある.

Q2.結局Restartはalwaysとon-failureどちらにすべき?

常駐し続けるべきWeb/Bot/APIはalways,正常終了したら止まってよいバッチはon-failureが基本だ.いずれもRestartSecとStartLimit系を併記して,即クラッシュ時の無限ループを必ず防ぐこと.

Q3.cronは捨ててtimerに全部移すべき?

必須ではないが,ログが残る・停止中の分を拾える・状態を確認できる利点は大きい.重要な定期処理(バックアップ・バッチ)からtimerへ移すのがおすすめだ.単純で軽い処理はcronのままでも問題ない.

Q4.ExecStartでパイプやリダイレクトを使いたい

ExecStartはシェルを介さないため,パイプ(|)やリダイレクト(>)は直接書けない.必要ならExecStart=/bin/bash -lc 'コマンド | コマンド'のようにシェル経由で書くか,処理をスクリプトファイルにまとめてそれを呼ぶのが定石だ.

Q5.ユーザー権限(rootless)で常駐させたい

userレベルのsystemd(systemctl --user)で可能だが,ログアウトで止まらないようloginctl enable-linger ユーザー名が必要だ.本番常駐は基本systemレベル+専用一般ユーザーの方が管理が素直で,権限分離もしやすい.

Q6.ログがディスクを食い尽くさないか心配

journaldのSystemMaxUseMaxRetentionSecで上限を切れば自動で古いログが消える.VPS契約直後にこの上限設定を済ませておくのがおすすめだ.journalctl --disk-usageで現状も確認できる.

まとめ ― systemdを制する者がVPS運用を制する

systemdは,サービスの常駐・自動復帰・定期実行・ログ集約・セキュリティ強化を一手に引き受ける,VPS運用の心臓部だ.ここを理解すれば,「動かし続ける」仕事のほとんどをOSに任せられる.

今日やるべきことは1つ.自分のアプリを1本,.serviceファイルにしてenable –nowで起動し,わざとkillして自動復帰するのを確認すること.この成功体験が,あなたを「作れる人」から「動かし続けられる人」へ引き上げる.

そして本記事の全テクニックは,スナップショットで戻せるVPS1台があれば,今夜すべて試せる.読むだけで終わらせず,手を動かして「自分のサーバーの神経系」を手に入れよう.