"Give a small boy a hammer and he will find that everything he encounters needs a pounding."
It used to be that when I needed to run small scheduled security automation tasks, I'd set them up in the crontab on a Linux box and pipe results into sendmail. At first the box was an actual server, then it became a VPS (still love me some ARP Networks) or a cloud compute instance.
Then serverless become all the rage and I moved on to using Python scripts in AWS Lambda, triggered by a CloudWatch Events rule with output piped into SNS.
More recently, I've been doing some of this with GitHub Actions - turns out it is not just for CI/CD, and is useful as a basic job-running solution. No server to manage, no cloud infrastructure to Terraform up... just good ol' YAML-ized bash wrapped with sprinkings of workflow syntax.
The first example is just an automated daily Nuclei run against a list (using the matrix strategy) of base URLs.
name: Nuclei Scan
on:
schedule:
- cron: "0 8 * * *" # Runs at 08:00 UTC every day
push:
branches: [ nuclei-scan ]
workflow_dispatch:
jobs:
nuclei-scan:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
url:
- https://google.com
- https://microsoft.com
steps:
- name: Scan ${{ matrix.url }} with Nuclei
uses: projectdiscovery/nuclei-action@508c1868d4884e9fb6eb4d8d680cbc6b493c7ec5
with:
target: ${{ matrix.url }}
This uses the public nuclei-action which makes it pretty straightforward.
Output is accessible through the GitHub Actions tab on the repository is lives in:

(There's also the option of writing output to files, then adding them to the GitHub Actions workflow run as artifacts. This is documented at https://docs.github.com/en/actions/advanced-guides/storing-workflow-data-as-artifacts).
The second example sets a list of URLs & strings to check HTTP responses for. If the match isn't found, it'll error & also ping a Slack webhook with a notification. (Slack Workflow Builder with custom messages / variables and an "Acknowledge" button was useful here.)
name: HTTP Response Scan
on:
schedule:
- cron: "0 8 * * *" # Runs at 08:00 UTC every day
push:
branches: [ http-response-scan ]
workflow_dispatch:
jobs:
check-http-response-match:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
# `target` is the URL to check
# `match` is the string to check the HTTP response for
# `check` describes what is being checked & is used in failure notifications
- target: "https://abridge.io"
match: "