SSL certificate expiry alert

Get a Telegram alert 14 days before any of your websites' SSL certificates expire.

Easy Saves ~1 hours/month Developer & ITBy Flowngn team

Works with n8n · Make · Zapier

How it works

Trigger: Schedule — every day at 9:00
  1. List the domains you want to watch.
  2. Look up each domain's certificates in the public crt.sh log.
  3. Work out how many days the newest certificate has left.
  4. Alert on Telegram if it's under 14 days.

Diagram

trueEvery day 9:00Schedule Trigger{ }Domains to checkCodeLook up certificatesHTTP Request{ }Days leftCode⑂Expires within 14 d…IfTAlert on TelegramTelegram
Trigger App Logic Code

Code

JavaScript used in the Code steps — already included in the template below.

Domains to check · Code node
// The domains to watch. Add yours here.
const DOMAINS = ["yourcompany.com", "shop.yourcompany.com"];

return DOMAINS.map((domain) => ({ json: { domain } }));
Days left · Code node
// Mode: Run Once for Each Item
// crt.sh lists every certificate issued for the domain. The newest expiry date is
// almost always the certificate your site is using now.
const DAY = 24 * 60 * 60 * 1000;
const domain = $("Domains to check").item.json.domain;
const certs = Array.isArray($json) ? $json : Object.values($json);

const newest = certs
  .map((c) => new Date(c.not_after + "Z").getTime())
  .filter((t) => !isNaN(t))
  .sort((a, b) => b - a)[0];

const daysLeft = newest ? Math.floor((newest - Date.now()) / DAY) : -1;

return { json: { domain, daysLeft, expires: newest ? new Date(newest).toISOString().slice(0, 10) : "unknown" } };

Build it

Pick your tool — same automation, step by step.

Copy and paste straight into an n8n canvas (Ctrl+V), or download and use Import from File. Then connect your own credentials and replace every YOUR_… placeholder.

Workflow JSON
Show code (196 lines)
{
  "name": "SSL certificate expiry alert (Flowngn)",
  "nodes": [
    {
      "parameters": {
        "content": "## SSL certificate expiry alert\nGet a Telegram alert 14 days before any of your websites' SSL certificates expire.\n\n**Setup**\n1. Open each node and connect your own credentials.\n2. Replace every YOUR_… placeholder.\n3. Test once, then activate.\n\nFree template from Flowngn",
        "height": 260,
        "width": 340
      },
      "id": "45822242-cb44-479c-a021-6611374be090",
      "name": "Setup notes",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -420,
        -60
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 9
            }
          ]
        }
      },
      "id": "45822242-cb44-479c-a021-6611374be090",
      "name": "Every day 9:00",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// The domains to watch. Add yours here.\nconst DOMAINS = [\"yourcompany.com\", \"shop.yourcompany.com\"];\n\nreturn DOMAINS.map((domain) => ({ json: { domain } }));"
      },
      "id": "45822242-cb44-479c-a021-6611374be090",
      "name": "Domains to check",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        300,
        0
      ]
    },
    {
      "parameters": {
        "url": "=https://crt.sh/?q={{ $json.domain }}&output=json&exclude=expired",
        "options": {
          "timeout": 30000
        }
      },
      "id": "45822242-cb44-479c-a021-6611374be090",
      "name": "Look up certificates",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        600,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// Mode: Run Once for Each Item\n// crt.sh lists every certificate issued for the domain. The newest expiry date is\n// almost always the certificate your site is using now.\nconst DAY = 24 * 60 * 60 * 1000;\nconst domain = $(\"Domains to check\").item.json.domain;\nconst certs = Array.isArray($json) ? $json : Object.values($json);\n\nconst newest = certs\n  .map((c) => new Date(c.not_after + \"Z\").getTime())\n  .filter((t) => !isNaN(t))\n  .sort((a, b) => b - a)[0];\n\nconst daysLeft = newest ? Math.floor((newest - Date.now()) / DAY) : -1;\n\nreturn { json: { domain, daysLeft, expires: newest ? new Date(newest).toISOString().slice(0, 10) : \"unknown\" } };",
        "mode": "runOnceForEachItem"
      },
      "id": "45822242-cb44-479c-a021-6611374be090",
      "name": "Days left",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        900,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.daysLeft }}",
              "rightValue": 14,
              "operator": {
                "type": "number",
                "operation": "lt"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "45822242-cb44-479c-a021-6611374be090",
      "name": "Expires within 14 days?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1200,
        0
      ]
    },
    {
      "parameters": {
        "chatId": "YOUR_CHAT_ID",
        "text": "=🔐 SSL certificate for {{ $json.domain }} expires in {{ $json.daysLeft }} days ({{ $json.expires }}).",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "45822242-cb44-479c-a021-6611374be090",
      "name": "Alert on Telegram",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1500,
        0
      ]
    }
  ],
  "connections": {
    "Every day 9:00": {
      "main": [
        [
          {
            "node": "Domains to check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Domains to check": {
      "main": [
        [
          {
            "node": "Look up certificates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Look up certificates": {
      "main": [
        [
          {
            "node": "Days left",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Days left": {
      "main": [
        [
          {
            "node": "Expires within 14 days?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Expires within 14 days?": {
      "main": [
        [
          {
            "node": "Alert on Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}

Open in the playground to edit →

Tips

  • crt.sh can be slow — if a check times out, the next day's run will catch it.

Related flows