systemd --userのサービスがログアウトで止まる原因とlingering有効化手順

※本記事にはプロモーション(広告・アフィリエイトリンク)を含みます。
Linux機でNodeアプリをsystemdに常駐させ、systemctl --user enable --now まで実行したのに、SSHからログアウトするとサービスが止まってしまう——このページはその一点を解決します。原因はユーザー用systemdマネージャの寿命で、直す鍵は「lingering(居残り)」の有効化です。手順と確認方法、cronから叩くときの落とし穴まで通しで書きます。
なぜログアウトでユーザーサービスが止まるのか
systemctl --user で動くサービスは、ログインごとに起動する ユーザー専用の systemd マネージャ(systemd --user インスタンス) の配下にあります。このマネージャは、そのユーザーのログインセッションに紐づいて生まれます。
systemd の公式ドキュメント(loginctl / logind の説明)によると、通常は ユーザーの最後のセッションが終了すると、そのユーザーの systemd インスタンスも停止 します。SSH でログインして起動したサービスが、ログアウト(=最後のセッション終了)と同時に道連れで落ちるのはこのためです。enable は「次回マネージャ起動時に自動起動する」という予約であって、マネージャ自体を生かし続ける設定ではありません。
これを変えるのが lingering です。lingering を有効にしたユーザーは、ログインしていなくても、マシンの起動直後からユーザーマネージャが立ち上がり、ログアウト後も動き続けます。
| 状態 | ログイン中 | ログアウト後 | OS再起動後 |
|---|---|---|---|
| lingering 無効(既定) | 動く | 止まる | 止まったまま |
| lingering 有効 | 動く | 動き続ける | 自動で起動 |
ユーザーサービスを用意する
まずユニットファイルを ~/.config/systemd/user/ に置きます。system 側の /etc/systemd/system/ ではない点に注意してください。
# ~/.config/systemd/user/myapp.service
[Unit]
Description=My Node app
After=network-online.target
[Service]
WorkingDirectory=/home/kxkxk/app
ExecStart=/usr/bin/node /home/kxkxk/app/server.js
Restart=always
RestartSec=3
[Install]
WantedBy=default.target
ユーザーサービスの [Install] は WantedBy=multi-user.target ではなく default.target を使います。書いたら読み込んで有効化します。
systemctl --user daemon-reload
systemctl --user enable --now myapp.service
systemctl --user status myapp.service
ここまでで「ログイン中は動く」状態になります。この段階で一度ログアウト→再ログインして status を見ると、止まっていることが確認できるはずです。
lingering を有効化する
本題です。対象ユーザーに対して lingering を有効にします。
# 自分自身に対して
loginctl enable-linger $USER
# 別ユーザーを指定する場合(root 権限が必要)
sudo loginctl enable-linger kxkxk
このコマンドは /var/lib/systemd/linger/<ユーザー名> という空ファイルを作るだけで、ほかの設定ファイルを書き換えません。元に戻すときは次のコマンドで、このファイルが消えます。
loginctl disable-linger $USER
有効化した直後に、ログイン中でなくてもユーザーマネージャが起動するようになります。既に enable 済みのサービスは、マネージャの起動とともに自動で立ち上がります。
効いているかを確認する
設定が入ったかは2通りで確認できます。
# プロパティで確認(yes なら有効)
loginctl show-user $USER --property=Linger
# 実体ファイルで確認
ls -l /var/lib/systemd/linger/
Linger=yes と出ていればOKです。最終確認として、実際にログアウトして別経路(または時間を置いて再SSH)でプロセスが生きているかを見ます。
systemctl --user is-active myapp.service
ログアウト後も active を保っていれば成功です。確実を期すなら OS を再起動し、ログインする前の状態でサービスが上がっているかまで確認してください(起動直後にログインせず、別端末から systemctl --user を叩く方法は次節のとおりです)。
cron やスクリプトから操作するときの落とし穴
ログインシェル以外、たとえば cron や別ユーザーからの実行で systemctl --user ... を叩くと、Failed to connect to bus といったエラーになることがあります。これはユーザーマネージャへの接続に必要な環境変数が、ログインセッションの外では設定されないためです。
XDG_RUNTIME_DIR を明示してから叩くと通ります。
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
systemctl --user restart myapp.service
なお /run/user/<UID> は、lingering が有効なユーザーならログインしていなくても存在します(lingering が無効だと最後のログアウト時に消えます)。この点でも、常駐前提のユーザーには lingering を入れておくほうが扱いが素直になります。環境によってパスや変数名が異なる場合があるので、id -u で自分のUIDを確かめてから設定してください。
公開まで含めて組むなら、サービスをユーザー常駐させたうえで前段にトンネルを置く構成が扱いやすく、Cloudflare Tunnelのingressで複数サブドメインを振り分ける設定例 と組み合わせると、ポート開放なしで複数アプリを出し分けられます。
まとめ
systemctl --user のサービスがログアウトで止まるのは、ユーザーマネージャがセッションに紐づいて消えるためで、loginctl enable-linger $USER で居残りを有効にすれば解決します。次の一手として、loginctl enable-linger を実行したあと 一度ログアウトしてから systemctl --user is-active を確認 し、ログアウト後も動き続けることを自分の目で確かめてください。
よくある質問
systemctl --user enable したのにログアウトで止まるのはなぜ?
enable は次回ユーザーマネージャ起動時の自動起動予約にすぎず、マネージャ自体はログインセッションに紐づいて消えるためです。loginctl enable-linger で居残りを有効にすると解決します。
lingering が有効になっているか確認する方法は?
`loginctl show-user $USER --property=Linger` で Linger=yes なら有効です。実体は /var/lib/systemd/linger/ 配下に空ファイルとして作られるので、ls でも確認できます。
cron から systemctl --user を叩くと bus 接続エラーになる
ログインセッション外では接続用の環境変数が無いためです。export XDG_RUNTIME_DIR="/run/user/$(id -u)" を先に設定してから実行すると通ります。