GitHub Actions: Dead Link Checker

Catch broken links in CI/CD before they reach production

Add automated broken link detection to your GitHub Actions pipeline. Get native build annotations that appear directly on your pull requests and commits — no extra tools or API keys needed.

Native Annotations

Broken links appear as errors directly in your GitHub PR checks

Sub-second Checks

Quick mode checks a single page in under a second

No API Key Required

Free tier works without authentication

Pass/Fail Gating

Set a threshold to fail builds when broken links exceed your limit

What It Looks Like

When broken links are found, GitHub shows annotations like these on your build:

Warning: Found 2 broken link(s) on https://yoursite.com
Error: Broken link: https://yoursite.com/old-page (404 Not Found) found on https://yoursite.com/
Error: Broken link: https://external.com/gone (410 Gone) found on https://yoursite.com/links

When all links are healthy:

Notice: No broken links found on https://yoursite.com

Quick Start

Create the workflow file

Download the ready-made workflow directly into your repo:

mkdir -p .github/workflows && curl -o .github/workflows/check-links.yml https://51-68-119-197.sslip.io/tools/check-links.yml

Then edit SITE_URL in the file to point to your website. Or create it manually — add .github/workflows/check-links.yml to your repository:

name: Check for broken links

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 6 * * 1'  # Weekly on Monday at 6am

jobs:
  check-links:
    runs-on: ubuntu-latest
    steps:
      - name: Check for broken links
        run: |
          curl -sSL https://51-68-119-197.sslip.io/tools/check-links.sh | bash -s -- --threshold 0 https://yoursite.com

Or use the API directly with curl:

      - name: Check for broken links
        run: |
          RESULT=$(curl -s "https://51-68-119-197.sslip.io/api/deadlinks?url=https://yoursite.com&mode=quick&format=github")
          echo "$RESULT"
Replace the URL

Change https://yoursite.com to your actual website URL.

Commit and push

That's it. The check runs on every push and PR to main, plus weekly monitoring.

Advanced: Fail Build on Broken Links

Use the threshold parameter to fail your build when too many broken links are found:

name: Check for broken links

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  check-links:
    runs-on: ubuntu-latest
    steps:
      - name: Check for broken links
        run: |
          # threshold=0 means ANY broken link fails the build
          RESULT=$(curl -sf "https://51-68-119-197.sslip.io/api/deadlinks?url=https://yoursite.com&mode=quick&threshold=0&format=github")
          echo "$RESULT"

          # RESULT: FAIL/PASS is appended when threshold is set
          if echo "$RESULT" | grep -q 'RESULT: FAIL'; then
            exit 1
          fi

Check Only Internal or External Links

Focus your check with the check_only parameter:

# Check only internal links (same domain)
curl -s "https://51-68-119-197.sslip.io/api/deadlinks?url=https://yoursite.com&mode=quick&check_only=internal&format=github"

# Check only external links (other domains)
curl -s "https://51-68-119-197.sslip.io/api/deadlinks?url=https://yoursite.com&mode=quick&check_only=external&format=github"

Check GitHub README Links

New in v2.7: Check all links in any public repository's README.md. Uses github=owner/repo instead of url:

name: Check README links

on:
  push:
    branches: [main]
  schedule:
    - cron: '0 6 * * 1'

jobs:
  check-readme:
    runs-on: ubuntu-latest
    steps:
      - name: Check README for broken links
        run: |
          RESULT=$(curl -sf "https://51-68-119-197.sslip.io/api/deadlinks?github=${{ github.repository }}&format=github&threshold=0")
          echo "$RESULT"
          if echo "$RESULT" | grep -q 'RESULT: FAIL'; then
            exit 1
          fi

Uses ${{ github.repository }} to automatically use the current repo. Supports main/master branches and README.md/readme.md variants.

API Parameters

ParameterValueDescription
urlstringURL to check (or use github instead)
githubowner/repoCheck links in a GitHub repo's README.md
modequick | fullquick for CI/CD (sub-second, single page). full for deep crawl.
formatgithub | json | csv | markdowngithub for annotations, markdown for PR comments
thresholdintegerMax broken links before fail. JSON: "pass": false. GitHub format: RESULT: FAIL. Use 0 for zero tolerance.
check_onlyinternal | externalFilter by link type. Omit to check all.

Full CI/CD Example with Multiple Sites

name: Link health check

on:
  schedule:
    - cron: '0 */6 * * *'  # Every 6 hours
  workflow_dispatch:        # Manual trigger

jobs:
  check-links:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        site:
          - https://yoursite.com
          - https://docs.yoursite.com
          - https://blog.yoursite.com
    steps:
      - name: Check ${{ matrix.site }}
        run: |
          echo "Checking ${{ matrix.site }}..."
          curl -s "https://51-68-119-197.sslip.io/api/deadlinks?url=${{ matrix.site }}&mode=quick&threshold=0&format=github"

Post Results as a PR Comment

Use format=markdown to get a formatted report you can post as a GitHub PR comment:

name: Link check on PR

on:
  pull_request:
    branches: [main]

jobs:
  check-links:
    runs-on: ubuntu-latest
    steps:
      - name: Check for broken links
        id: linkcheck
        run: |
          REPORT=$(curl -s "https://51-68-119-197.sslip.io/api/deadlinks?url=https://yoursite.com&mode=quick&threshold=0&format=markdown")
          echo "report<> $GITHUB_OUTPUT
          echo "$REPORT" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

      - name: Post PR comment
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `${{ steps.linkcheck.outputs.report }}`
            })

The markdown report includes a summary table, broken links detail, redirect chains, and mixed content warnings — all formatted for GitHub rendering.

Add a Status Badge to Your README

Show your project's link health with a live badge. Add this to your README.md:

![Dead Links](https://51-68-119-197.sslip.io/badge/deadlinks?url=https://yoursite.com)

The badge updates on each request and shows:

Dead links badge example Live example for example.com

Also available: /badge/seo?url= (SEO score A-F) and /badge/perf?url= (response time).

Complete CI/CD + Badge Setup

Combine automated checking with a README badge for full link health visibility:

name: Check links and update badge

on:
  schedule:
    - cron: '0 6 * * 1'  # Weekly
  workflow_dispatch:

jobs:
  check-links:
    runs-on: ubuntu-latest
    steps:
      - name: Check for broken links
        run: |
          curl -sSL https://51-68-119-197.sslip.io/tools/check-links.sh | bash -s -- --threshold 0 https://yoursite.com

# In your README.md, add:
# ![Link Health](https://51-68-119-197.sslip.io/badge/deadlinks?url=https://yoursite.com)

Need More?

The free tier allows 1 check per 5 minutes. For higher rate limits and deep crawl mode, check our pricing plans or use the API via RapidAPI.

Try the interactive tool | Full API documentation

FAQ

Do I need an API key?
No. The free tier works without authentication. You only need a key for higher rate limits.
What's the rate limit?
Free tier: 1 request per 5 minutes per IP. GitHub Actions runners share IPs, so for frequent checks consider a paid plan.
What's the difference between quick and full mode?
Quick mode checks all links on a single page without a browser (sub-second). Full mode crawls multiple pages with a headless browser (30-60 seconds). Use quick for CI/CD.
Does it check both internal and external links?
Yes, by default it checks all links. Use check_only=internal or check_only=external to filter.
Can I post results as a PR comment?
Yes! Use format=markdown to get a formatted Markdown report with summary table, broken links detail, and redirect chains. Pipe it into actions/github-script to post as a PR comment automatically. See the "Post Results as a PR Comment" section above.
Can I use this with other CI/CD platforms?
Yes! The format=github output uses standard ::error:: and ::warning:: syntax. For other platforms, use format=json or format=markdown and parse the response.
How does the README badge work?
The badge at /badge/deadlinks?url=YOUR_URL returns a live SVG image showing the current broken link count. It checks on each request (cached briefly). Add the markdown image to your README and it updates automatically.