Playbook: N8N_CLOUD_API_KEY Rotation
Part of the n8n playbook index (#2641) — Credential rotation category. Consumed by
scripts/check-p24-workflows-connection.sh(auth + retry audit,docs/n8n-workflow-inventory.md§Stale API key tracker).
Service: n8n Cloud (https://app.n8n.cloud)
Secret: N8N_CLOUD_API_KEY
Rotation frequency: 90 days (or immediately on suspected exposure)
Last rotated: 2026-06-27
Next due: 2026-09-27
What uses this credential
| Consumer | How | Effect if missing |
|---|---|---|
monitoring/exporters/ | Polls n8n Cloud workflow execution stats | Workflow health metrics missing from Grafana |
credential-exporter | Checks key age for rotation alerts | Rotation alert suppressed |
| Scripts triggering n8n Cloud webhooks | API auth header | Script calls to n8n Cloud fail with 401 |
Where stored
| Location | Key name | How to update |
|---|---|---|
secrets/monitoring.env.sops | N8N_CLOUD_API_KEY | SOPS write pattern (see below) |
secrets/n8n-bms4.env.sops | N8N_CLOUD_API_KEY | Same SOPS write pattern |
| vps-i1 deployed env | /opt/p24-infra/monitoring/.env | Auto-synced by secrets-sync.yml on merge to dev/main |
| bms-4 deployed env | /opt/p24-infra/bms-4/.env | Auto-synced by secrets-sync.yml on merge to dev/main |
Note: Key exists in both SOPS files because both vps-i1 (monitoring exporters) and bms-4 (n8n workflows) may reference n8n Cloud status.
Login credentials
- Email:
secrets/monitoring.env.sopskeyN8N_CLOUD_LOGIN_EMAIL(radieu@gmail.com) - Password:
secrets/monitoring.env.sopskeyN8N_CLOUD_LOGIN_PASSWORD— reference key name only, never display value - Added 2026-08-05 (#2641) — previously this section named no concrete SOPS key, and a separate doc
(
docs/password-rotation-procedures.md) claimed a different, wrong location (.env.local,N8N_CLOUD_PASSWORD). Both blocked the Option A automation below until fixed.
Automation status
Playwright-automatable — Claude can rotate this without human involvement. n8n Cloud provides an API Keys section under Account Settings. Spawn a Playwright agent with the prompt below.
Rotation steps
Option A — Playwright agent (preferred)
Spawn a background agent with this prompt:
You are rotating N8N_CLOUD_API_KEY for the n8n Cloud account at https://app.n8n.cloud.
IMPORTANT: Never display any secret value in your response. Reference key names only.
1. Read the n8n Cloud login email/password from SOPS silently into $env:N8N_CLOUD_EMAIL / $env:N8N_CLOUD_PASS
(keys: N8N_CLOUD_LOGIN_EMAIL / N8N_CLOUD_LOGIN_PASSWORD in secrets/monitoring.env.sops)
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
Use Select-String on the SOPS output — never print the value.
2. Use Playwright to navigate to https://app.n8n.cloud
- Login with email radieu@gmail.com and the password from step 1
- Click the user avatar / account menu (top right)
- Go to Settings → API (or Account → API Keys)
- Find the existing key(s) named "p24-infra" or similar
- Delete the old key
- Click "Create API Key"
- Set label: p24-infra-<YYYY-MM-DD>
- Copy the new key value into $env:NEW_KEY (never print it)
3. Update secrets/monitoring.env.sops:
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
Follow the SOPS write pattern: decrypt → update N8N_CLOUD_API_KEY line → re-encrypt → canary.
Temp file: secrets/monitoring-edit.env.sops
Use [System.IO.File]::WriteAllText with UTF8Encoding($false).
4. Update secrets/n8n-bms4.env.sops with the same new key value:
Same SOPS write pattern; temp file: secrets/n8n-bms4-edit.env.sops
5. Verify the new key works:
$r = Invoke-RestMethod "https://api.n8n.cloud/api/v1/workflows" `
-Headers @{ "X-N8N-API-KEY" = $env:NEW_KEY }
Write-Host "Workflows accessible: $($r.data.Count)"
$env:NEW_KEY = ""
6. Commit and open PR targeting main:
git add secrets/monitoring.env.sops secrets/n8n-bms4.env.sops
git commit -m "chore: rotate N8N_CLOUD_API_KEY (scheduled 90d)"
Push and open PR via gh pr create.
7. Append to docs/secrets-rotation-log.md:
| <YYYY-MM-DD> | N8N_CLOUD_API_KEY | scheduled 90d rotation | AI-agent | SOPS (monitoring + n8n-bms4) |
Option B — Manual (fallback)
Step 1 — Log into n8n Cloud
URL: https://app.n8n.cloud
Email: radieu@gmail.com
Password: read from secrets/monitoring.env.sops (key name in SOPS — never print value)
Step 2 — Delete old API key
→ Account menu (top-right avatar) → Settings → API
→ Find key named p24-infra or similar
→ Click delete → Confirm
Step 3 — Create new API key
→ Click "Create API Key"
→ Label: p24-infra-<YYYY-MM-DD>
→ Copy value immediately (shown once only)
Step 4 — Update secrets/monitoring.env.sops
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
$plain = sops --decrypt --input-type dotenv --output-type dotenv secrets\monitoring.env.sops
$tempPath = "$PWD\secrets\monitoring-edit.env.sops"
$env:NEW_KEY = "<paste-from-step-3>"
$updated = $plain -replace "^N8N_CLOUD_API_KEY=.*", "N8N_CLOUD_API_KEY=$env:NEW_KEY"
[System.IO.File]::WriteAllText($tempPath, ($updated -join "`n") + "`n",
[System.Text.UTF8Encoding]::new($false))
$enc = sops --encrypt --input-type dotenv --output-type dotenv $tempPath
[System.IO.File]::WriteAllText("$PWD\secrets\monitoring.env.sops",
($enc -join "`n") + "`n", [System.Text.UTF8Encoding]::new($false))
sops --decrypt --input-type dotenv --output-type dotenv secrets\monitoring.env.sops | Out-Null
if ($LASTEXITCODE -ne 0) { throw "SOPS corrupt — do NOT commit" }
[System.IO.File]::Delete($tempPath)
Step 5 — Update secrets/n8n-bms4.env.sops (same pattern)
$plain = sops --decrypt --input-type dotenv --output-type dotenv secrets\n8n-bms4.env.sops
$tempPath = "$PWD\secrets\n8n-bms4-edit.env.sops"
$updated = $plain -replace "^N8N_CLOUD_API_KEY=.*", "N8N_CLOUD_API_KEY=$env:NEW_KEY"
[System.IO.File]::WriteAllText($tempPath, ($updated -join "`n") + "`n",
[System.Text.UTF8Encoding]::new($false))
$enc = sops --encrypt --input-type dotenv --output-type dotenv $tempPath
[System.IO.File]::WriteAllText("$PWD\secrets\n8n-bms4.env.sops",
($enc -join "`n") + "`n", [System.Text.UTF8Encoding]::new($false))
sops --decrypt --input-type dotenv --output-type dotenv secrets\n8n-bms4.env.sops | Out-Null
if ($LASTEXITCODE -ne 0) { throw "SOPS corrupt — do NOT commit" }
[System.IO.File]::Delete($tempPath)
$env:NEW_KEY = ""
Step 6 — Commit and push
git add secrets/monitoring.env.sops secrets/n8n-bms4.env.sops
git commit -m "chore: rotate N8N_CLOUD_API_KEY (scheduled 90d)"
git push
gh pr create --base main --title "chore: rotate N8N_CLOUD_API_KEY"
Step 7 — Append to docs/secrets-rotation-log.md
| <YYYY-MM-DD> | N8N_CLOUD_API_KEY | scheduled 90d rotation | radieu | SOPS (monitoring + n8n-bms4) |
Verification
# 1. Confirm SOPS updated in both files
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
$m = sops --decrypt --input-type dotenv --output-type dotenv secrets\monitoring.env.sops |
Select-String "^N8N_CLOUD_API_KEY="
$n = sops --decrypt --input-type dotenv --output-type dotenv secrets\n8n-bms4.env.sops |
Select-String "^N8N_CLOUD_API_KEY="
Write-Host "monitoring.env.sops N8N_CLOUD_API_KEY length: $($m.ToString().Split('=',2)[1].Length)"
Write-Host "n8n-bms4.env.sops N8N_CLOUD_API_KEY length: $($n.ToString().Split('=',2)[1].Length)"
# 2. Confirm both values match (compare hashes — never display values)
$mv = $m.ToString().Split('=',2)[1]
$nv = $n.ToString().Split('=',2)[1]
Write-Host "Values match: $($mv -eq $nv)" # Must be TrueEscalation
| Symptom | Action |
|---|---|
| Can’t find API Keys section in n8n Cloud UI | n8n Cloud UI changes frequently — check under Account → Settings → Advanced |
| Login fails | Try password reset at https://app.n8n.cloud/forgot-password (radieu@gmail.com) |
| API returns 403 instead of 401 | Account may have IP restrictions; try from a known-good IP |
| Both SOPS files have different values after rotation | Re-run step 4-5 above to sync both files |
| secrets-sync.yml not yet triggered | Services on vps-i1 and bms-4 still use old key until merge + container restart |
Prevention
- The
credential-rotation.ymlGH Actions workflow (Monday 06:00 UTC) opens ahuman-actionissue whennext_duepasses. - n8n Cloud does not send expiry warnings — only the GH Actions check triggers rotation.
- Keep both SOPS files (
monitoring.env.sops+n8n-bms4.env.sops) in sync — both must be updated in the same commit. - After rotation, update this playbook’s Last rotated and Next due dates.
Audit Log — Log to infra_operations
After this operation completes, log it to the infra_operations audit table.
Python (Linux server — bms-4, vps-i1, vps-h1, or similar):
import sys
sys.path.insert(0, '/opt/p24-infra')
from scripts.lib.log_op import log_op
log_op(
actor="claude", # "radieu" for manual human ops, "claude" for agent
op_type="credential_rotation",
resource="N8N_CLOUD_API_KEY",
result="success", # "success" | "failed" | "skipped"
detail="Scheduled rotation — n8n Cloud API key regenerated and SOPS updated",
env="vps-i1",
gh_issue=2730,
)PowerShell (Windows dev machine):
$env:SUPABASE_URL = (Get-Content "C:\code_2026\p24-infra\.env.local" | Select-String "^SUPABASE_URL=").ToString().Split("=",2)[1].Trim()
$env:SUPABASE_SERVICE_KEY = (Get-Content "C:\code_2026\p24-infra\.env.local" | Select-String "^SUPABASE_SERVICE_KEY=").ToString().Split("=",2)[1].Trim()
python -c "
import os, sys
sys.path.insert(0, 'C:/code_2026/p24-infra')
from scripts.lib.log_op import log_op
log_op('claude', 'credential_rotation', 'N8N_CLOUD_API_KEY', 'success', 'Scheduled rotation — n8n Cloud API key regenerated and SOPS updated', 'vps-i1')
"
$env:SUPABASE_URL = ''; $env:SUPABASE_SERVICE_KEY = ''