{ "id": "ERCWB3oSYbgsUiqL", "name": "NPM SSL Certificate Monitor", "nodes": [ { "parameters": { "rule": { "interval": [ { "field": "cronExpression", "expression": "0 9 * * *" } ] } }, "id": "schedule-trigger", "name": "Daily SSL Check", "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1.2, "position": [ 240, 400 ] }, { "parameters": { "jsCode": "// Process SSL certificates and check expiration\nconst certificates = $input.all();\nconst today = new Date();\nconst WARNING_DAYS = 7;\n\nconsole.log('=== NPM SSL Certificate Monitor ===');\nconsole.log('Total certificates:', certificates.length);\n\nconst certificateStatus = [];\nlet expiringCerts = [];\nlet skippedCount = 0;\n\nfor (const item of certificates) {\n const cert = item.json;\n \n // Skip if no expiration date or invalid\n if (!cert.expires_on || cert.expires_on === 0) {\n console.log('Skipping certificate (no expiration):', cert.nice_name || cert.id);\n skippedCount++;\n continue;\n }\n \n const domainNames = cert.domain_names?.join(', ') || cert.nice_name || `Certificate ${cert.id}`;\n \n // Create date and validate it\n const expiryDate = new Date(cert.expires_on * 1000);\n if (isNaN(expiryDate.getTime())) {\n console.log('Skipping certificate (invalid date):', domainNames);\n skippedCount++;\n continue;\n }\n \n const daysRemaining = Math.ceil((expiryDate - today) / (1000 * 60 * 60 * 24));\n const isExpiring = daysRemaining <= WARNING_DAYS;\n \n let status = 'OK';\n if (daysRemaining < 0) {\n status = 'EXPIRED';\n } else if (isExpiring) {\n status = 'EXPIRING SOON';\n }\n \n const certInfo = {\n domain: domainNames,\n expiryDate: expiryDate.toISOString().split('T')[0],\n daysRemaining: daysRemaining,\n status: status,\n isExpiring: isExpiring\n };\n \n certificateStatus.push(certInfo);\n \n if (isExpiring) {\n expiringCerts.push(certInfo);\n console.log(`🔴 ${domainNames}: ${daysRemaining} days (expires ${certInfo.expiryDate})`);\n } else {\n console.log(`✅ ${domainNames}: ${daysRemaining} days`);\n }\n}\n\nconsole.log('\\n=== Summary ===');\nconsole.log('Valid certificates:', certificateStatus.length);\nconsole.log('Skipped (invalid/missing expiration):', skippedCount);\nconsole.log('Expiring within', WARNING_DAYS, 'days:', expiringCerts.length);\n\nreturn [{\n json: {\n totalCerts: certificateStatus.length,\n expiringCount: expiringCerts.length,\n hasExpiringCerts: expiringCerts.length > 0,\n allCertificates: certificateStatus,\n expiringCertificates: expiringCerts\n }\n}];" }, "id": "process-certificates", "name": "Process SSL Certificates", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 848, 400 ] }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 1 }, "conditions": [ { "id": "f5646765-356f-4270-b42b-8e86a3a64568", "leftValue": "={{ $json.hasExpiringCerts }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "id": "check-expiring", "name": "Any Expiring Certs?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [ 1040, 400 ] }, { "parameters": { "jsCode": "// Format warning email for expiring certificates\nconst data = $input.item.json;\nconst expiringCerts = data.expiringCertificates || [];\n\nlet tableRows = '';\nfor (const cert of expiringCerts) {\n const statusIcon = cert.status === 'EXPIRED' ? '🔴' : '⚠️';\n tableRows += `\n \n ${statusIcon} ${cert.domain}\n ${cert.expiresOnFormatted}\n ${cert.daysRemaining} days\n ${cert.status}\n `;\n}\n\nconst subject = `⚠️ SSL Certificate Expiration Warning - ${expiringCerts.length} Certificate${expiringCerts.length > 1 ? 's' : ''}`;\n\nconst htmlBody = `\n\n\n\n \n\n\n
\n
\n

⚠️ SSL Certificate Expiration Warning

\n
\n \n
\n

Summary

\n \n
\n \n

Certificates Requiring Attention

\n \n \n \n \n \n \n \n \n \n \n ${tableRows}\n \n
DomainExpiration DateDays RemainingStatus
\n \n
\n

Action Required: Please renew or update these SSL certificates in your Nginx Proxy Manager.

\n

Open Nginx Proxy Manager →

\n

This is an automated alert from your NPM SSL Certificate Monitor workflow.

\n
\n
\n\n\n`;\n\nreturn [{\n json: {\n subject: subject,\n htmlBody: htmlBody,\n to: 'admin@ben.io',\n expiringCount: expiringCerts.length\n }\n}];" }, "id": "format-warning-email", "name": "Format Warning Email", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1248, 320 ] }, { "parameters": {}, "id": "manual-trigger", "name": "Manual Trigger", "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, "position": [ 240, 272 ] }, { "parameters": { "sendTo": "={{ $json.to }}", "subject": "={{ $json.subject }}", "message": "={{ $json.htmlBody }}", "options": {} }, "id": "send-gmail", "name": "Send Gmail Alert", "type": "n8n-nodes-base.gmail", "typeVersion": 2.1, "position": [ 1440, 400 ], "webhookId": "c9d0a896-4275-4387-a2c8-af1f138559a9", "credentials": { "gmailOAuth2": { "id": "Os1ux3h3zFlC2XkG", "name": "Gmail account" } } }, { "parameters": { "url": "https://npm.dfw.ben.io/api/nginx/certificates", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "Authorization", "value": "=Bearer {{ $json.token }}" } ] }, "options": {} }, "id": "fetch-certs", "name": "Fetch Certificates", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 640, 400 ] }, { "parameters": { "operation": "select", "schema": { "__rl": true, "mode": "list", "value": "public" }, "table": { "__rl": true, "value": "npm_tokens", "mode": "list", "cachedResultName": "npm_tokens" }, "where": { "values": [ { "column": "service_name", "value": "npm_dfw" } ] }, "options": {} }, "type": "n8n-nodes-base.postgres", "typeVersion": 2.6, "position": [ 448, 400 ], "id": "18c7d30b-bd5e-450c-9800-4f883c641b1c", "name": "Select rows from a table", "credentials": { "postgres": { "id": "Ik8CFyap8ic2Md3M", "name": "n8n-infra" } } } ], "connections": { "Daily SSL Check": { "main": [ [ { "node": "Select rows from a table", "type": "main", "index": 0 } ] ] }, "Process SSL Certificates": { "main": [ [ { "node": "Any Expiring Certs?", "type": "main", "index": 0 } ] ] }, "Manual Trigger": { "main": [ [ { "node": "Select rows from a table", "type": "main", "index": 0 } ] ] }, "Format Warning Email": { "main": [ [ { "node": "Send Gmail Alert", "type": "main", "index": 0 } ] ] }, "Any Expiring Certs?": { "main": [ [ { "node": "Format Warning Email", "type": "main", "index": 0 } ] ] }, "Fetch Certificates": { "main": [ [ { "node": "Process SSL Certificates", "type": "main", "index": 0 } ] ] }, "Select rows from a table": { "main": [ [ { "node": "Fetch Certificates", "type": "main", "index": 0 } ] ] } }, "settings": { "saveExecutionProgress": true, "saveManualExecutions": true, "saveDataErrorExecution": "all", "saveDataSuccessExecution": "all", "executionOrder": "v1" }, "triggerCount": 1, "versionId": "67bbccd4-8c5e-4cc5-8969-9460ed529b23", "owner": { "type": "personal", "projectId": "FeLO36wNUAcn61Wj", "projectName": "Ben W ", "personalEmail": "admin@ben.io" }, "parentFolderId": "eWW72giJDI4fxlWw", "isArchived": false }