n8nをMacにインストールして常時動かす|launchdと二重起動の直し方

n8nをMacにインストールして動かすところまでは、npmで数分です。困るのはその先で、「Macを再起動したら止まっていた」「ターミナルを閉じたら止まった」「launchdに任せたのに、なぜか落ち続ける」といったことが起きます。この記事では、弊社がn8nを1台のMacで常時動かすために組んだ起動の形と、実際に起きた二重起動の不具合、その直し方を書きます。
この記事の結論
- n8nは
npm install -g n8nで入り、n8n startで起動します。常時動かすなら、起動はlaunchd(macOSの常駐プログラムの管理役)に1本だけ任せます。 - ログインしなくても起動させたいなら、置き場所は
/Library/LaunchDaemonsです。ユーザーのLaunchAgentは、そのユーザーがログインしている間しか動きません。 - launchdとは別に
screenなどでn8nを立てると、ポート5678の取り合いになります。後から起動した側は「port 5678 is already in use」で終了し、launchdが立て直すたびに同じ理由で落ち続けます。 - 環境変数のファイルは1つにまとめ、起動スクリプトからそれだけを読みます。
実際に起きたこと
弊社では、n8nを含む常駐サービスを、もともと screen -dmS で手で起動していました。screen は、ターミナルを閉じても裏で動かし続けるための道具です。この形だと、Macが再起動した瞬間にサービスが全部止まり、自動では戻りません。しかも自動ログインを切っていたため、ユーザーのLaunchAgentも起動しませんでした。
そこで、ログインしなくても起動時に動くLaunchDaemonへ移しました。本番の起動系は /Library/LaunchDaemons/jp.marta-ai.n8n.plist(KeepAlive=true)で、そこから起動スクリプト ~/bin/start-n8n.sh を呼び、スクリプトが環境変数のファイル ~/.n8n-env.sh を読み込んでから n8n start を実行します。
移したあと、テストで再起動しました。GUIでは誰もログインしていない状態で、起動から2分のうちに常駐サービスが全部自動で戻りました。n8nは起動直後のログに「Activated workflow」が71件出ていました。
問題が起きたのは2026-07-12〜13です。launchdで動いているn8nとは別に、screen -dmS n8n でn8nを立ててしまいました。先に立ったほうがポート5678を取り、launchd側のn8nは起動するたびに落ちました。ログには port 5678 is already in use が延々と出ていました。
同じ時期に、もう1つ穴が見つかりました。環境変数のファイルが ~/.n8n-env.sh と ~/.n8n.env の2つあり、~/.n8n.env のほうは2026-07-12のキーの集約から取り残されていました。もし古いほうを読んで再起動していたら、AIRTABLE_TOKEN が無いまま起動し、それを使う有効なワークフロー67本が全部失敗する状態でした。
原因
ポートが使われていると、n8nは終了する
n8nが待ち受けるポートは、環境変数 N8N_PORT で決まり、既定は5678です。n8n本体のソースでは、Webサーバーの起動時にエラーが起き、その種類が EADDRINUSE(ポートが使用中)だと、次のメッセージを出してプロセスを終了します。
this.server.on('error', (error) => {
if (error.code === 'EADDRINUSE') {
this.logger.error(`n8n's port ${port} is already in use. Do you have another instance of n8n running already?`);
}
...
process.exit(1);
つまり、2つ目のn8nは「立ち上がらずに終わる」だけです。ここまでは正しい動きです。
KeepAliveが、落ちたn8nを立て直し続ける
launchdの KeepAlive は、そのジョブを常に動かしておく指定です。Appleのドキュメントには、launchdはこのジョブを動かし続けようとする、と書かれています。n8nが終了すると、launchdはすぐに立て直します。ところがポートはscreen側のn8nが持ったままなので、立て直したn8nもまた EADDRINUSE で終了します。これが繰り返されます。弊社の環境では、15秒おきに落ちては立ち上がる動きが止まりませんでした。
LaunchAgentはログインしないと動かない
Appleのドキュメントでは、ユーザーエージェント(LaunchAgent)は、そのユーザーがログインしている間だけ動くと説明されています。デーモンかエージェントかは、plistを置くフォルダで決まります。/Library/LaunchDaemons に置けばデーモンです。自動ログインを切ったMacでn8nを起動時から動かすには、デーモンにするしかありませんでした。
実際、Webサーバーのnginxだけが、Homebrewが作るユーザーのLaunchAgentのまま残っていました。自動ログインを切っていたため、再起動後にnginxだけが起動しませんでした。
直し方
1. npmでn8nをインストールする
先にNode.jsが入っている必要があります。n8nの公式ドキュメントにも、Node.jsを入れておくよう書かれています。入っているか、どの版かは node -v で確かめます。対応する版の範囲は、公式ドキュメントのnpmのページに載っています。範囲から外れていたら、先にNode.jsを入れ替えます。
node -v
npm install -g n8n
which n8n
which n8n で、どこに入ったかを控えます。npmの入れ先(prefix)は環境ごとに違います。弊社のMacでは、Homebrewのnpmで入れた機械と、ホームの下の ~/.npm-global/ に入った機械がありました。後で書くplistには、この実際のパスを書きます。パスがずれると、launchdは古いn8nや存在しないファイルを起動しようとします。
2. 起動スクリプトを1本作る
環境変数は1つのファイルにまとめ、起動スクリプトからだけ読みます。弊社では bash -lc 'source ~/.zshrc' の形をやめました。PATHがうまく伝わらず、n8n: command not found になったためです。
#!/bin/bash
# ~/bin/start-n8n.sh
source "$HOME/.n8n-env.sh"
# node のある場所を足す(which node で確かめた場所)
export PATH="/opt/homebrew/bin:$PATH"
# which n8n で控えた絶対パスで起動する
exec /Users/XXXX/.npm-global/bin/n8n start
n8nの起動コマンドは、1行目が #!/usr/bin/env node のスクリプトです。n8nを絶対パスで呼んでも、nodeはPATHから探されます。そのため、n8nの場所は絶対パスで書き、nodeの場所はPATHに足します。例の2つのパスは、手順1で which n8n と which node で控えたものに書き換えてください。
環境変数のファイルには、キーのほかに次のような設定も入れます。
# ~/.n8n-env.sh(中身は例・キーは伏せています)
export GENERIC_TIMEZONE="Asia/Tokyo"
export TZ="Asia/Tokyo"
export N8N_BLOCK_ENV_ACCESS_IN_NODE=false
export AIRTABLE_TOKEN="XXXX"
GENERIC_TIMEZONE が無いと、n8nはスケジュールを America/New_York で解釈します。詳しくはn8nのスケジュールが13時間ずれる原因と直し方に書きました。N8N_BLOCK_ENV_ACCESS_IN_NODE=false は、Codeノードから $env を読むための設定です。こちらは「access to env vars denied」が出る原因と直し方にまとめています。
ファイルは自分だけが読める権限にします。
chmod 600 ~/.n8n-env.sh
chmod 755 ~/bin/start-n8n.sh
3. LaunchDaemonのplistを作る
デーモンはroot(管理者)の権限で動くので、UserName で実際に動かすユーザーを指定します。ログの出力先も決めておきます。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>jp.marta-ai.n8n</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/XXXX/bin/start-n8n.sh</string>
</array>
<key>UserName</key>
<string>XXXX</string>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/n8n.log</string>
<key>StandardErrorPath</key>
<string>/tmp/n8n.log</string>
</dict>
</plist>
書いたら、形が正しいかを確かめます。
plutil -lint jp.marta-ai.n8n.plist
4. 先に、ほかの起動方法をすべて止める
ここが二重起動を防ぐ要の手順です。plistを登録する前に、screenやLaunchAgentで動いているn8nを止めます。まず、ポート5678を誰が持っているかを見ます。
lsof -nP -iTCP:5678 -sTCP:LISTEN
screen -ls
screenの中にn8nがあれば、そのセッションを閉じます。
screen -S n8n -X quit
lsof -nP -iTCP:5678 -sTCP:LISTEN # 何も出なければ空いた
screenを閉じても、中で動いていたnodeのプロセスが残ることがあります。弊社では、別のサービスをLaunchDaemonへ移したときに、screenを終了しても子のnodeが残ってポートを持ち続け、新しいデーモンが待ち受けを始められませんでした。残ったプロセスを止めると、すぐにKeepAliveが引き継ぎました。lsof にまだ出るなら、そのPIDを止めます。
kill <PID>
以前にユーザーのLaunchAgent(~/Library/LaunchAgents/ の下)でn8nを動かしていたなら、そちらも外します。
5. plistを登録する
sudo cp jp.marta-ai.n8n.plist /Library/LaunchDaemons/
sudo chown root:wheel /Library/LaunchDaemons/jp.marta-ai.n8n.plist
sudo launchctl bootstrap system /Library/LaunchDaemons/jp.marta-ai.n8n.plist
6. これ以降、n8nを手で立てない
再起動したいときは、n8nのプロセスを止めるだけにします。KeepAliveなので、launchdが自動で立て直します。弊社の再起動用のスクリプトも、プロセスを止めるだけの中身です。「止まっているかもしれないから、とりあえず screen で立てる」が、今回の事故の入口でした。
直ったかを確かめる
launchdから見た状態を確かめます。
sudo launchctl print system/jp.marta-ai.n8n | grep state
# state = running なら動いている
次に、ポートを持っているのが1つだけかを見ます。
lsof -nP -iTCP:5678 -sTCP:LISTEN
ログでは2つを数えます。ただし /tmp/n8n.log は消されずに追記され続けます。ファイル全体を数えると、前の起動の分まで入ってしまいます。KeepAliveで何度も立ち直ったあとや、二重起動を直した直後は、特に数が膨らみます。そこで、再起動の前にログの行数を控え、それより後ろの行だけを数えます。
# 再起動の前に、いまの行数を控える
before=$(wc -l < /tmp/n8n.log)
# ここで n8n を再起動する(プロセスを止めると launchd が立て直す)
# 控えた行より後ろだけを数える
tail -n +$((before + 1)) /tmp/n8n.log | grep -c "is already in use" # 0 なら二重起動なし
tail -n +$((before + 1)) /tmp/n8n.log | grep -c "Activated workflow"
1つ目が0なら、今回の起動で二重起動は起きていません。2つ目は、今回の起動で有効になったワークフローの数です。管理画面やAPIで数えた有効なワークフローの本数と合うかを見ます。合わなければ、別のn8nが動いているか、古いDBを読んでいる疑いがあります。
最後に、本当に再起動して確かめます。弊社は、テストで再起動し、誰もログインしていない状態で全部が戻るのを見てから本番の運用に入りました。who で誰もいないこと、uptime で起動したばかりであることを確かめます。
それでも直らないとき
n8n: command not foundがログに出る:launchdから動くスクリプトは、ターミナルのPATHを引き継ぎません。n8nはwhich n8nで控えた絶対パスで起動し、nodeの場所はスクリプトの中でPATHに足します。n8nやNode.jsを入れ直して入れ先が変わったときも、ここがずれます。- キーが無いエラーでワークフローが一斉に失敗する:環境変数のファイルが2つないかを探します。弊社では名前の似たファイルが2つあり、片方が古いままでした。ファイルを1つに統合し、起動スクリプトが読むのはそれだけにします。
- Mac自体が止まっていて、何も戻らない:弊社では、全部が止まった障害を調べたら、Macへの給電が切れていたことがありました。パニックの記録は無く、給電が戻ると自動で冷起動し、デーモンも全部戻りました。ソフトを疑う前に、
last rebootなどで起動した時刻を確かめます。 - 手でシャットダウンしたあと、電源が戻っても起動しない:きれいにシャットダウンしたあとは、停電から戻ったときに自動で電源を入れる設定(
autorestart)が効きません。計画停電の前に止めるなら、pmset schedule poweronで電源を入れる時刻を予約しておきます。 - 止める前にDBの控えを取りたい:n8nのSQLiteは
cpで写すと壊れることがあります。n8nのSQLiteをcpでバックアップしてはいけない理由と安全な取り方に手順があります。
まとめ
- n8nは
npm install -g n8nで入れ、常時動かすならlaunchdに起動を任せます。 - ログインなしで起動時から動かすなら、plistは
/Library/LaunchDaemonsに置き、UserNameとKeepAliveを指定します。 - launchdとscreenで二重に立てると、後から立ったn8nが「port 5678 is already in use」で終了し、KeepAliveが立て直しては落ちる、を繰り返します。
- 登録の前に、
lsofでポートが空いていることを確かめ、screenや残ったnodeのプロセスを止めます。再起動はプロセスを止めるだけにします。 - 環境変数のファイルは1つにし、起動スクリプトからだけ読みます。
参考にした公式ドキュメント






