Turn expiring TLS certificates into Jira tickets each Monday

Every Monday we check the certificates on your internet-facing hosts, open a ticket for each one expiring within 30 days, and post a single Slack summary.

Deterministic Code
ShodanJiraSlack BotEngineeringOperationsNotifications & AlertsResearch & Monitoring
PromptCreate

Every Monday at 6:00am, catch the TLS certificates on our internet-facing hosts before they expire and turn each one into a Jira ticket. Build this as a deterministic code workflow on a cron trigger: a fixed search, a fixed date threshold, a fixed duplicate lookup, and fixed ticket creation. Nothing here needs reasoning or judgement at run time.

Start with Shodan Search Hosts using an SSL-scoped query for our organisation, for example `ssl org:"ACME Corp"` or `ssl hostname:example.com`. Expose the organisation or domain as a configurable input rather than hardcoding it, so the same workflow works for a different estate. Shodan uses filter syntax such as `apache country:DE port:443`, and the List Search Filters operation can confirm any filter before relying on it. Request the certificate fields you need on each match, specifically the hostnames, IP address, port, certificate subject common name, and certificate expiry. Search returns 100 results per page, so advance with the `page` parameter until the results run out, and keep to roughly one request per second because Shodan rate limits at 1 request per second and returns HTTP 429 above that.

For every match, read the certificate expiry date and compute whole days remaining against today in UTC. Certificate expiry comes back as a compact certificate timestamp rather than a plain ISO date, so normalise it into a real date before comparing, and skip any match where the expiry cannot be parsed instead of treating it as expiring. Then keep only the hosts whose certificate has already expired or expires within the next 30 days, and discard the rest. Identify each remaining item by hostname plus IP plus port, so the same certificate served on two different ports stays two separate items. Sort the survivors soonest first.

Before creating anything, dedupe against Jira. For each remaining host, run a Jira Search Issues (JQL) lookup for an existing open ticket that already names that host, restricted to the target project and to unresolved work, for example `project = SEC AND statusCategory != Done AND summary ~ "legacy.example.com"`. The `~` operator is a fuzzy text match, so confirm in code that the returned summary genuinely contains the exact hostname before treating it as a match. If an open ticket exists, skip the host and record the existing issue key for the summary. If none exists, the host is genuinely new and gets a ticket.

Raise the new ones with Jira Create Issues (Bulk), which accepts up to 50 issues per request, so chunk the list into batches of 50 if it is ever longer. Each ticket needs the hostname, the IP address, the port, the certificate subject, and the exact expiry date, both in the summary and in the description, so the ticket is actionable without opening the workflow. Title them like "TLS certificate expires 2026-08-19: shop.example.com (203.0.113.19:443)". Set priority from days remaining: already expired or 7 days or fewer becomes the most urgent priority available, 8 to 14 days the next band down, and 15 to 30 days the middle band. Map these onto whatever priority scheme the project actually uses rather than assuming the Jira defaults exist. Add a consistent label such as `tls-expiry` to every ticket so the dedupe lookup and any later reporting have a reliable handle. Mark already expired certificates explicitly in the summary text, because an expired certificate is an active outage rather than a future risk.

Finish by posting one Slack message with Send a Message that lists the certificates expiring this month, sorted soonest first. Each line shows the hostname, the IP and port, the expiry date, the days remaining, and either the key of the ticket just raised or the key of the existing ticket already tracking it, linked as `https://<your-site>.atlassian.net/browse/<ISSUE-KEY>` from the keys returned by the bulk create. Send one message for the whole run, never one message per host, and close with a count of new tickets raised versus items already tracked. If nothing has expired and nothing expires within 30 days, still post the message and say explicitly that all certificates are clear for the next 30 days, so a quiet week is never mistaken for a broken workflow.

Example output

TLS certificate check | Monday 10 August Expiring this month, soonest first: 1. legacy.example.com | 203.0.113.41:443 | EXPIRED 2 Aug 2026 | 8 days ago | SEC-914 (new) 2. vpn.example.com | 198.51.100.7:443 | 14 Aug 2026 | 4 days left | SEC-915 (new) 3. shop.example.com | 203.0.113.19:8443 | 19 Aug 2026 | 9 days left | SEC-916 (new) 4. api.example.com | 198.51.100.22:443 | 27 Aug 2026 | 17 days left | already tracked in SEC-880 5. status.example.com | 203.0.113.90:443 | 6 Sep 2026 | 27 days left | already tracked in SEC-871 3 new tickets raised, 2 already tracked. 1 certificate has already expired and needs attention today.

What does this prompt do?

  • Every Monday morning, looks up the certificates running on your public, internet-facing hosts and works out which ones have already expired or run out within the next 30 days.
  • Finds certificates nobody has written down, including forgotten subdomains, old load balancers, and test boxes that never made it onto an internal renewal list.
  • Checks your Jira project before filing anything, so a host that already has an open renewal ticket never collects a second one week after week.
  • Opens a ticket per genuinely new certificate with the hostname, address, port, certificate name, and exact expiry date, with urgency set by how many days are left.
  • Posts one Slack message listing every certificate expiring this month, with direct links to the tickets that were raised.

What do I need to use this?

  • A Shodan account with search access, used to see the certificates on your internet-facing hosts. Searching uses query credits, so a paid membership is recommended for weekly runs.
  • The company name or domain your public hosts sit under, so the search only ever returns your own estate and not the wider internet.
  • A Jira project where certificate renewal tickets should be created, and permission to create issues in it.
  • A Slack workspace and the channel where the weekly summary should land.

How can I customize it?

  • Change the timing. Monday at 6am is a good weekly rhythm, but daily works if your estate moves quickly, and the first of the month suits slower ones.
  • Move the thresholds. The 30 day window and the 7 day urgent cutoff are both just numbers. Teams commonly run 30, 14, and 7 day steps, or widen the window to 100 days to give renewals plenty of runway.
  • Point it at the right places. Choose which Jira project receives tickets, which Slack channel gets the summary, and how days remaining map onto your own priority names.

FAQs

How does it know which hosts belong to us?
You give it your organisation name or your domain when you set it up, and the search is scoped to that. It only ever looks at hosts already visible on the public internet under your name, so it will not wander off into someone else's infrastructure.
Will it open the same ticket again every week?
No. Before creating anything it searches your Jira project for an open ticket that already names that host. If one exists, the host is skipped and only appears in the Slack summary. You only get a new ticket for a certificate that genuinely does not have one yet.
Can it find certificates we did not know about?
Yes, and that is often the real value. It works from what is actually reachable on the internet rather than from a list someone maintains by hand, so forgotten subdomains, old load balancers, and staging boxes show up too. Those are usually the ones that expire unnoticed.
What happens to a certificate that has already expired?
It comes through as the most urgent item, in the same band as anything with less than seven days left, and it is clearly marked as already expired in both the ticket and the Slack summary so it is impossible to scroll past.
Will it post to Slack even when nothing is expiring?
Yes, on purpose. A quiet week still gets a message saying everything is clear for the next 30 days. If it only spoke up when something was wrong, you would have no way to tell a calm week apart from a check that quietly stopped running.

Related templates

Build a credential rotation checklist when someone leaves

When someone leaves, we check which shared passwords they used in their final months and post a ranked rotation list to your security channel.

1Password
Rippling
Slack Bot
+1
Agentic Task
Weekly Amazon S3 bucket security audit posted to Slack

Every Monday, check every S3 bucket for public exposure, missing encryption and weak backup settings, then get the risks ranked in Slack.

Amazon S3
Slack Bot
Google Sheets
Agentic Task
Log Agorapulse social conversations to HubSpot contacts

Twice every weekday, the conversations from your social inbox land on the right HubSpot contact timelines, with a Slack recap for sales.

Agorapulse
HubSpot
Slack Bot
Agentic Task
Clean up HubSpot contacts from your Amazon SES suppression list

Every morning, find the addresses that hard bounced or filed a spam complaint, update the matching HubSpot contacts, and post a short Slack recap.

Amazon SES
HubSpot
Slack Bot
Agentic Task
Draft polite follow-ups for emails that never got a reply

Every weekday at 4pm, spot the threads that went quiet, stage a ready-to-send nudge in your mailbox, and get a ranked Slack recap.

Aurinko
Google Sheets
Slack Bot
Agentic Task
Replay failed SQS messages when a bug fix is merged

When you merge a fix in GitHub, this agent checks the matching dead-letter queue, replays the failed messages, and reports back on the pull request and in Slack.

Amazon SQS
GitHub
Slack Bot
Agentic Task

Never explain another expired certificate outage.

Set this up once and every certificate on your public estate comes to you with a month to spare, already in a ticket and already assigned.