Building a Multi-Workflow n8n Automation Platform for SOC Operations

Security Operations Centers generate a relentless stream of events — SIEM alerts, vulnerability disclosures, certificate expirations, service outages — and the analyst team is always outnumbered. Most SOCs address this with a patchwork of cron scripts, one-off integrations, and manual runbooks. n8n offers a better path: a self-hosted, code-capable workflow automation platform that can serve as the connective tissue between your security tools.

This guide walks through building a production n8n deployment with six core SOC workflows, covering the practical implementation details and pitfalls you will encounter along the way.

Deployment Architecture

Deploy n8n as a Docker container behind your reverse proxy, with persistent storage for workflows and credentials. A minimal docker-compose.yml looks like this:

services:
  n8n:
    image: n8nio/n8n:latest
    restart: always
    ports:
      - "127.0.0.1:5678:5678"
    environment:
      - N8N_HOST=automation.example-corp.com
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://automation.example-corp.com/
      - NODE_FUNCTION_ALLOW_BUILTIN=https,child_process,crypto
      - N8N_BASIC_AUTH_ACTIVE=true
    volumes:
      - n8n_data:/home/node/.n8n

The critical environment variable here is NODE_FUNCTION_ALLOW_BUILTIN. By default, n8n Code nodes run in a sandboxed context with no access to Node.js built-in modules. You must explicitly whitelist https, child_process, and any other modules your workflows need. Without this, your Code nodes will throw “Cannot find module” errors that are easy to misdiagnose.

Place your reverse proxy (Apache, Traefik, or nginx) in front, terminating TLS and enforcing authentication. n8n’s webhook endpoints will be publicly reachable if you are not careful — restrict access to trusted source IPs or require authentication tokens in webhook URLs.

Workflow 1: Service Health Monitoring

The foundation workflow runs on a cron trigger (every 5 minutes) and checks the health endpoints of your critical services. Use an HTTP Request node to hit each service’s /health endpoint, then a Code node to parse the responses and determine overall status.

const results = [];
for (const item of $input.all()) {
  const svc = item.json;
  if (svc.status !== 'healthy') {
    results.push({
      service: svc.name,
      status: svc.status,
      dependencies: svc.dependencies || {}
    });
  }
}
return results.length > 0 ? results.map(r => ({ json: r })) : [];

Route failures to your Matrix or Slack channel. The key design decision is deduplication: you do not want an alert every five minutes for a service that has been down for an hour. Use n8n’s built-in Static Data (accessible via $getWorkflowStaticData('global')) to track previously-alerted failures and only fire on state transitions — healthy-to-unhealthy and unhealthy-to-healthy.

Workflow 2: Wazuh Alert Routing with Deduplication

Configure Wazuh to forward alerts via webhook (using an integration command or ossec.conf’s <integration> block) to an n8n webhook endpoint. The incoming payload contains the full Wazuh alert JSON including rule ID, level, agent name, and description.

Build a routing pipeline:

  1. Webhook Trigger receives the alert
  2. Switch node routes by severity (level 10+ to critical channel, 7-9 to standard, below 7 to digest)
  3. Code node implements deduplication using a hash of rule.id + agent.name
  4. Suppression window prevents the same alert from firing more than once per configurable interval (typically 15-30 minutes)

The deduplication Code node pattern:

const crypto = require('crypto');
const staticData = $getWorkflowStaticData('global');
if (!staticData.alertCache) staticData.alertCache = {};

const alert = $input.first().json;
const key = crypto.createHash('md5')
  .update(${alert.rule.id}-${alert.agent.name})
  .digest('hex');

const now = Date.now();
const suppressMinutes = 30;

if (staticData.alertCache[key] &&
    (now - staticData.alertCache[key]) < suppressMinutes  60  1000) {
  return []; // Suppressed
}

staticData.alertCache[key] = now;
return [$input.first()];

Periodically purge stale cache entries to prevent memory growth. A separate cron-triggered workflow that cleans entries older than 24 hours works well.

Workflow 3: NVD CVE Scanning

This daily workflow queries the NIST National Vulnerability Database API for CVEs matching your technology stack. Use an HTTP Request node pointed at https://services.nvd.nist.gov/rest/json/cves/2.0 with query parameters for keyword search and date range.

Critical pitfall: n8n Code nodes do not have fetch() or axios available. You must use require('https') and write callback-style HTTP requests, or use the built-in HTTP Request node for the actual API call and reserve Code nodes for parsing and filtering.

A practical pattern is to chain HTTP Request nodes (one per technology keyword — your web framework, database engine, reverse proxy, etc.) into a Merge node, then a Code node that deduplicates by CVE ID and filters by CVSS score threshold. Push anything above CVSS 7.0 to your alert channel immediately; batch lower-severity CVEs into the daily digest.

Respect NVD rate limits: unauthenticated requests are capped at 5 per 30 seconds. Add a Wait node between sequential API calls, or obtain an API key to increase your allowance to 50 per 30 seconds.

Workflow 4: SSL Certificate Monitoring

A daily cron workflow that checks certificate expiration for all your public-facing domains. The Code node approach using the https module:

const https = require('https');
const domains = ['web01.example-corp.com', 'api.example-corp.com', 'login.example-corp.com'];
const results = [];

for (const domain of domains) {
  const cert = await new Promise((resolve, reject) => {
    const req = https.request({ hostname: domain, port: 443, method: 'HEAD' }, (res) => {
      const cert = res.socket.getPeerCertificate();
      resolve({ domain, expiry: cert.valid_to, subject: cert.subject });
    });
    req.on('error', (e) => resolve({ domain, error: e.message }));
    req.end();
  });
  results.push(cert);
}

return results.map(r => ({ json: r }));

Flag certificates expiring within 30 days as warnings, within 7 days as critical. This catches renewals that fail silently — a surprisingly common problem when Let’s Encrypt hooks break or internal CA processes stall.

Workflow 5: Webhook-Triggered OSINT Pipeline

Create a webhook endpoint that accepts an IP address or domain and triggers an automated OSINT lookup. Chain lookups to VirusTotal, AbuseIPDB, Shodan, and your threat intelligence platform. Each lookup is an HTTP Request node with the appropriate API key stored in n8n credentials.

Aggregate results in a Code node and format a structured report posted back to your analyst chat channel. This turns a 10-minute manual process into a 15-second automated one, triggered by a simple webhook call or chat bot command.

Workflow 6: Daily SOC Digest

A cron workflow (running at 07:00 daily) that aggregates the previous 24 hours of activity:

  • Total alerts by severity from Wazuh (query the Wazuh API or Elasticsearch directly)
  • New CVEs matching your stack from the NVD workflow results
  • Certificate status summary
  • Service health incidents from the monitoring workflow
  • Any triggered OSINT lookups and their findings

Format as a structured message with sections and post to your team channel. This gives the morning shift a single-glance operational picture.

Common Pitfalls and Lessons Learned

The If node connection format changed in n8n 1.x. The If node now has “true” and “false” outputs instead of numbered outputs. If you are following older tutorials, your workflow connections will silently route to the wrong branch. Always verify routing after importing workflows from external sources.

Rate limiter conflicts arise when multiple workflows hit the same external API. If your CVE scanner and OSINT pipeline both call VirusTotal, they share a rate limit. Centralize API calls through a sub-workflow that implements queuing and rate tracking via Static Data.

fetch() is not available in n8n Code nodes. This trips up nearly everyone. Use require('https') for simple GET requests, or better yet, use n8n’s HTTP Request node and keep Code nodes focused on data transformation.

Error handling is not optional. Every HTTP Request node should have an error output connected to a notification. Silent failures in automation are worse than no automation — they create false confidence.

Credential rotation must account for n8n stored credentials. When you rotate an API key, update it in n8n’s credential store immediately, or your workflows will fail silently until an analyst notices the digest stopped arriving.

The goal is not to replace analysts but to ensure they spend their time on decisions, not data gathering. A well-built n8n platform handles the mechanical work — collecting, correlating, deduplicating, routing — so your team can focus on the judgments that actually require human expertise.

Scroll to Top