n8n is one of the most powerful open-source workflow automation tools out there. It's a fantastic fit for developers building SaaS products, monitoring systems, or internal tooling who need to automate their operations without paying per-execution fees.
The catch? Laravel Forge is designed primarily for PHP and Laravel applications. But thanks to its flexibility, you can absolutely run a complex Node.js app like n8n on it — with zero-downtime deploys — as long as you know how to wire the pieces together.
This guide walks through the exact, battle-tested steps to run n8n from source on a Forge server. More importantly, it explains why each piece is needed, so you can adapt it instead of just copy-pasting. Along the way we'll fix the three problems that trip most people up:
-
EACCESpermission errors frompnpmduring install/build - The infamous PM2 crash loop when PM2 is used as a wrapper
- 502 / 403 errors caused by missing WebSocket support in Nginx
Let's get into it.
Step 1 — Create the Site on Forge
- In the Laravel Forge dashboard, create a New Site.
- Set the project type to Static HTML. We choose Static HTML because we are not using the PHP-FPM stack — the app is managed entirely by PM2. Picking a PHP project type would just add moving parts we don't need.
- Once the site is created, connect it to the n8n GitHub repository so Forge can pull the source on every deploy.
Step 2 — The Deploy Script
This is where most of the magic (and most of the pain) lives. Two problems show up when building n8n from source on Forge:
-
pnpmpermission errors (EACCES) during install and build. - A PM2 crash loop when PM2 tries to run n8n through a wrapper instead of invoking Node directly.
The fix for the first is to run package commands through npx, which avoids needing root-level permissions. The fix for the second is to point PM2 at node directly and pass the n8n entry point as an argument.
Head to your site's Deploy Script in Forge and paste the following (again, swap in your real username and yourdomain.com):
$CREATE_RELEASE()
cd $FORGE_RELEASE_DIRECTORY
# 1. Use npx to sidestep root permission issues while installing packages
npx --yes pnpm install
# 2. Build the source
npx --yes pnpm build
# 3. Link the shared environment file into the release
if [ -f ../../.env ]; then
ln -nfs ../../.env .env
fi
# 4. Persist n8n data (SQLite database + workflow files) so it survives every deploy
mkdir -p ../../n8n_data
ln -nfs ../../n8n_data n8n_data
$ACTIVATE_RELEASE()
# 5. Write the PM2 process config
# We use "node" as the script and pass the n8n path in "args".
# This avoids the "missing module" crash loop that happens when PM2 wraps n8n.
mkdir -p /home/username/.pm2-conf
cat <<'EOF' > /home/username/.pm2-conf/n8n.json
{
"name": "n8n-server",
"cwd": "/home/username/yourdomain.com/current",
"script": "node",
"args": "./packages/cli/bin/n8n",
"instances": 1,
"exec_mode": "fork",
"env": {
"N8N_USER_FOLDER": "/home/username/yourdomain.com/n8n_data",
"PORT": 5678
}
}
EOF
# 6. Start the platform, or reload it if it's already running
pm2 start /home/username/.pm2-conf/n8n.json || pm2 reload n8n-server --update-env
pm2 save
Step 3 — Nginx Configuration (the WebSocket Secret)
The n8n UI is live — it streams execution progress to your browser in real time. That relies on WebSockets. If Nginx isn't configured to upgrade the connection and keep it open, you'll hit 502 or 403 errors and the UI will feel broken.
In Forge, open your site's Edit Nginx Configuration and replace the existing location / block with this:
location / {
proxy_pass http://127.0.0.1:5678;
proxy_http_version 1.1;
# WebSocket support so the live n8n UI works smoothly
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
# Pass the real client IP through to Node.js
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
The Upgrade and Connection 'upgrade' headers are what turn a plain HTTP proxy into one that can carry a WebSocket connection. The X-Forwarded-* headers make sure n8n sees the real visitor IP and correct protocol instead of 127.0.0.1.
Step 4 — Environment Variables
From the site's Environment panel in Forge, add these variables so webhooks resolve correctly and n8n knows its public base URL:
WEBHOOK_URL=https://yourdomain.com/
N8N_HOST=yourdomain.com
N8N_PROTOCOL=https
VUE_APP_URL_BASE_API=https://yourdomain.com/
WEBHOOK_URL is especially important: without it, any webhook nodes you build will generate URLs pointing at the wrong host, and external services won't be able to reach them.
Note: n8n's environment variables evolve between versions. If a variable above doesn't behave as expected on your version, check the current n8n configuration docs for the up-to-date equivalent.
Deploy 🎉
Hit Deploy Now.
Once the script finishes, n8n will be running efficiently, kept alive and restart-safe by PM2, and secured behind Nginx on your Forge server.
If this helped you get n8n running, drop a comment with your setup — and if you scaled to PostgreSQL or added a worker/queue setup, I'd love to hear how it went.
Top comments (0)