STATUS ÜBERPRÜFEN
I AM LISTENING TO
|

lychee Link Checker: Find Broken Links in Your Docs, Sites and READMEs in Seconds

17. August 2026
.SHARE

Table of Contents

You know that feeling when you click a link in a project’s README and it 404s right in your face? Me too. Broken links are the quiet rot of the web — they creep into documentation, blog posts, and large codebases without anyone noticing, and they make otherwise solid content look abandoned. The fix is a tool that checks links automatically, and the best one I’ve used is lychee.

lychee is a fast, async, stream-based link checker written in Rust. It scans Markdown, HTML, and reStructuredText files (plus whole websites), finds broken hyperlinks and mail addresses, and spits out a clear report — or lets you integrate it straight into CI so broken links never ship in the first place. It runs as a command-line tool, a reusable library, and a GitHub Action.

In this guide I’ll walk you through what makes lychee worth using, how to install and configure it, how to wire it into your CI so it runs automatically, and where it fits compared to the other link checkers out there. By the end you’ll have a setup that catches dead links before your readers ever do.

Why Bother Checking Links at All?

Let me make the pitch quickly, because link checking sounds like a chore until you see what it saves you:

  • Reader trust. A single broken link in a tutorial or guide makes readers wonder if the rest is stale too. Link rot is a signal of neglect, even when it isn’t.
  • SEO and search. Broken links hurt crawlability and user experience signals. Search engines follow your links, and dead destinations waste crawl budget while sending soft quality signals.
  • Docs and READMEs are bigger than ever. Every project ships documentation these days, and docs-as-code means repos full of Markdown that link to tools, versions, and APIs that change constantly.
  • AI citations. With search engines and AI assistants now pulling facts from your pages, a dead link your article points to reflects badly on you every time someone follows it.
  • It never happens „just once.“ Sites go down, domains expire, and projects archive — links rot in bulk. Manual checking is a losing battle.

The bottom line: link checking is cheap insurance on content you care about. Do it once automatically, and you never have to babysit a link inventory by hand again.

What Is lychee?

lychee started life as a demo in episode 10 of the Hello Rust podcast and grew into one of the most popular open-source link checkers around, with the latest stable release at v0.24.2. It’s dual-licensed under Apache-2.0 and MIT, so you can use it just about anywhere without license friction.

A nice detail for European readers: lychee is funded in part through the NGI0 Core Fund from NLnet, backed by the European Commission’s Next Generation Internet programme. It’s genuinely open-source infrastructure, not a freemium preview of a paid product.

What lychee checks and how it works

Under the hood, lychee reads files and URLs streamed and concurrently, which is where the speed comes from. Instead of loading everything into memory and checking links one at a time, it processes links in parallel with a configurable number of concurrent checks. On a large documentation site that’s the difference between waiting minutes and waiting seconds.

Out of the box lychee can:

  • Check Markdown, HTML, reStructuredText, plain text, and more
  • Crawl and check a live website (including its sitemap)
  • Validate mailto: addresses
  • Verify anchor fragments if you enable them
  • Output to text, JSON, Markdown and more for CI pipelines
  • Run as a single static binary — no runtime dependencies to install
  • Cache results between runs so repeated checks aren’t wasted

It’s used in production by big projects you’ve definitely heard of — including OpenSearch, HashiCorp’s Consul, Nuxt, Fastify, containerd, and Sindre Sorhus’s execa. It even checks its own repository with itself.

Installing lychee

lychee ships as a single static binary, which makes installation ridiculously easy on every platform. Here are the options, sorted by how I’d actually use them:

macOS via Homebrew

If you’re on a Mac, Homebrew is the fastest route:

Linux package managers

On Arch-based systems it’s in the official repos. Ubuntu and friends get it via Snap, Alpine via apk:

Docker

Prefer containers (like I do in most of my self-hosted setups)? The official image is on Docker Hub:

There are also -alpine variants of every tag if you want a slimmer image, plus nightly builds if you’re feeling adventurous.

Cargo

Rust users can grab it from crates.io. This compiles from source, so it takes a few minutes, but you get the tool integrated with your Rust toolchain:

Prebuilt binaries

No package manager and no Rust installed? Head over to the GitHub releases page, download the archive for your platform, unpack it, and drop the lychee binary anywhere on your PATH. That’s it — the static binary approach means no libraries to chase.

Whichever route you pick, verify it works:

Basic Usage: Checking Links in Seconds

The most basic invocation takes one or more inputs — files, directories, or URLs — and checks everything it finds. The simplest possible start is checking a single Markdown file:

Point it at a whole directory and it’ll discover every supported file inside and check them all:

Against a live website, it crawls pages and checks every link it finds — sitemaps included:

You can even pipe content in via stdin, which is handy in shell pipelines:

Reading the output

Default output uses an emoji-ish status per link, but since we don’t do emoticons around here, the more useful view is the detailed format with status codes:

Each line shows the URL and its result — a 200 means everything’s fine, 404 means the page is gone, and statuses like 429 (rate limited) mean the server couldn’t answer properly even though the link technically works.

Exit codes: the part CI cares about

For automation the important bit is what lychee returns when it finishes. It uses three exit codes:

Exit code
Meaning
Use it for
0
All links valid, nothing broken
Let the pipeline pass
1
At least one link is broken
Fail the build so nobody merges dead links
2
A runtime error occurred (e.g. a file didn’t exist, network problem)
Investigate infrastructure, not links

That clean exit-code contract is the backbone of the whole CI story — it turns „link rot“ into a failing pipeline step, which means a reviewer’s job becomes trivial: fix the link or justify the exclusion.

JSON output for pipelines

For scripted processing, switch to structured output:

JSON output makes it trivial to build your own dashboards, Slack hooks, or Grafana panels on top of the check results.

Configuration: Taming lychee With a Config File

Running lychee with bare defaults works fine, but real projects need a config file. lychee looks for a lychee.toml in the current directory by default (you can point it anywhere with --config), and every option you can pass on the command line has a TOML equivalent. This is where the tool really shines — once your config lives in the repo, everyone gets the same sane setup for free.

Here’s a practical example covering the options I actually reach for, based on the official lychee.example.toml reference:

Priority recap: HIGH — set exclude and exclude_path first, because they stop your pipeline from failing on links you can’t control (like LinkedIn or a flaky third-party site). MEDIUM — turn on cache if you’re running checks frequently, per-host rate limiting if you’ve ever annoyed a server. LOWrequire_https if you’re security-hardening for mixed content.

The exclude list deserves special attention. Regular expressions let you surgically whitelist problematic-but-unavoidable links without disabling checks globally. One expression per entry, and patterns like ^https?://(www\.)?linkedin\.com handle optional subdomains and HTTP/HTTPS at once.

For quick, one-off overrides you can skip the config file entirely and pass flags directly:

You can also keep a simple .lycheeignore file in the repo root — one regex per line — for exclusions that don’t belong in the main config. This is a nice middle ground when you want contributors to add ignores without touching the TOML.

Running lychee in CI: GitHub Action and Cron

Local checking is great, but the real win is automation. lychee has an official GitHub Action, so adding it to your workflow is a five-minute job. Here’s the recommended setup that checks your whole repo once a day and opens an issue when it finds broken links:

Two big quality-of-life additions. First, caching: tell lychee to cache results keyed to the commit, then restore that cache between runs. That keeps daily checks fast and avoids re-checking stable links:

Second, pin your action. GitHub Actions move fast, so pinning to a specific tag (or better, a commit SHA) prevents a surprise update from breaking your CI. Add Dependabot with the github-actions ecosystem and it’ll keep you pinned-but-current automatically.

A note on scheduled checks and false positives

Running checks on a schedule (like the cron above) is the best way to catch link rot in real time. But external sites go down for reasons that have nothing to do with you, and flaky sites will occasionally trip up a perfectly good URL. That’s exactly what the exclude list and the accept status codes are for — treat rate limiting (429) and temporary states sensibly so your pipeline only fails on genuinely dead links.

You can also hit the Wayback Machine via the archive = "wayback" option to suggest archived versions of broken links — handy when a site you depend on disappears entirely.

lychee vs. the Alternatives

lychee isn’t the only game in town, and the „right“ tool depends on your input format and workflow. Here’s how it stacks up against the other open-source link checkers I looked at, all verified:

Tool
Language
Specialty
License
Best for
lychee
Rust
Markdown, HTML, RST, whole sites
MIT / Apache-2.0
Docs, READMEs, CI, speed
Go
Recursive website crawl
MIT
Large live sites, massive speed
Go
Static generated HTML
MIT
Jekyll/Hugo/CMS output dirs
Node
Markdown files
ISC
Already using Node tooling
Node
HTML files and sites
MIT
Node projects, rich options
Python
Recursive site checking, GUI
GPL-2.0
Python shops, feature-heavy

Some honest guidance, since you deserve nuance, not marketing:

  • lychee is the best all-rounder for documentation-driven repos. It handles Markdown, HTML, and RST natively, gives you that full format, checks mailto, and has the cleanest CI story with the official Action.
  • muffet is arguably faster on giant live sites, and it’s a superb „crawl this whole domain“ tool in Go. If you only ever check running websites (not files), it’s a serious contender.
  • htmltest is purpose-built for static site generators — point it at your Jekyll or Hugo output and it verifies alt text, favicons, hashes, and more, not just links.
  • markdown-link-check is the lightweight Node option if you’re already neck-deep in a JavaScript toolchain and just want README coverage.
  • broken-link-checker is the most configurable Node option, great for HTML and sites with granular controls.
  • linkchecker has been around forever and brings a web interface and tons of output formats, at the cost of being heavier.

For my money, lychee wins for the typical portalZINE-style workflow — self-hosted docs, Markdown repositories, CI integration — because it does files and sites well with minimal setup and a single binary.

Which Setup Is Right for You?

Quick decision guide, take what applies:

  • README-only, single project: lychee README.md in a pre-commit hook or one-line CI step. Good enough in minutes.
  • Docs site generated from Markdown (my default): config file with exclude/exclude_path, cache enabled, and the GitHub Action on push + nightly cron.
  • Live site with no static files: look hard at muffet, or use lychee https://your-site.com if you want one tool for everything.
  • Jekyll/Hugo static output: htmltest adds alt-text and favicon checks on top of links.
  • Node-only monorepo: markdown-link-check integrates with your existing package scripts.
  • You value the Wayback Machine fallback and JSON everywhere: lychee’s archive = "wayback" and structured output are tough to beat.

My recommendation, the bottom line: for anyone with a Markdown documentation setup who wants broken links caught automatically before readers hit them, lychee is the tool I’d install first. Add it to CI, set the exclusions once, and forget about link rot for good.

Glossary

Async / stream-based
lychee processes links concurrently as it reads, rather than loading everything and checking one by one. This is the main reason it’s so fast on large codebases.
Link rot
The slow decay of hyperlinks as sites move, expire, or get archived. It’s what lychee is designed to catch.
Static binary
A compiled program with no runtime dependencies, so you can drop it on any machine and run it directly. lychee distributes these for easy install and updates.
HEAD request
An HTTP request that asks only for the response headers, not the body — a cheap way to check whether a URL exists. lychee falls back to GET when servers reject HEAD.
Exit code
A number a command returns on exit. lychee uses 0 (all good), 1 (broken links found), and 2 (runtime error), which CI systems read to pass or fail a step.
Sitemap
A file that lists the pages of a site so crawlers can find them. lychee can check links from sitemaps when crawling a live site.
Wayback Machine archive
A digital archive of web pages. lychee’s archive = "wayback" option can look up archived versions of broken links.
Rate limiting (429)
A server responding 429 Too Many Requests because you hit it too fast. lychee has per-host concurrency and request-interval settings to avoid tripping it.

FAQ

What is lychee?

lychee is a fast, async, stream-based link checker written in Rust. It finds broken hyperlinks and mail addresses inside Markdown, HTML, reStructuredText files, and whole websites. It runs as a CLI, a library, and a GitHub Action, and is published on GitHub by lycheeverse.

Is lychee free and open source?

Yes. lychee is open source and dual-licensed under Apache-2.0 and MIT, so you can use it freely, including commercially. It’s also funded in part by the EU-backed NGI0 Core Fund via NLnet.

How do I install lychee?

On macOS run brew install lychee. On Arch use pacman -S lychee, Ubuntu snap install lychee, Alpine apk add lychee. There’s also a Docker image (docker pull lycheeverse/lychee), a Cargo install, and prebuilt binaries on the GitHub releases page.

What file formats does lychee support?

lychee natively checks Markdown, HTML, reStructuredText, and plain text files, plus mailto addresses. It can also crawl a live website, read from stdin, and use a sitemap.

How do I exclude links from lychee checks?

Use the exclude option in a lychee.toml config file with regular expressions to skip specific URLs, exclude_path to skip directories or file types, or a .lycheeignore file with one regex per line.

How do I run lychee in CI?

Use the official lycheeverse/lychee-action@v2 GitHub Action in a workflow, optionally on a cron schedule, and have it open an issue when it finds broken links. lychee also outputs JSON for custom pipelines.

What exit codes does lychee return?

lychee returns 0 when all links are valid, 1 when at least one link is broken, and 2 for a runtime error. CI systems use these to pass or fail a step automatically.

How fast is lychee compared to other link checkers?

lychee is built in Rust and checks links concurrently, making it extremely fast on large documentation sites and repos. Its single-binary convenience across Markdown, HTML, and sites, plus the official GitHub Action, make it a strong all-rounder.

Can lychee check a live website?

Yes. Running lychee https://example.com crawls the site and checks every link it finds, including sitemaps. For very large sites, muffet can be even faster, but lychee handles sites well and checks files too.

How do I check mailto addresses with lychee?

Mail address checking is on by default via include_mail = true. lychee validates mailto: links found in your files, so you’ll catch typos and dead addresses in documentation.

What is the difference between lychee and muffet?

lychee (Rust) checks both files and live sites with a clean Markdown/HTML focus and an official CI Action. muffet (Go) is a pure recursive website crawler that’s extremely fast on large running sites. Choose muffet for site-only crawls, lychee for docs and repos.

How do I make repeated lychee runs faster?

Turn on caching with cache = true in your lychee.toml and set max_cache_age. lychee stores results and only re-checks links that changed or expired. In CI you can restore the cache between runs to speed up daily checks.

Let’s Talk!

Looking for a reliable partner to bring your project to the next level? Whether it’s development, design, security, or ongoing support—I’d love to chat and see how I can help.

Get in touch,
and let’s create something amazing together!

RELATED POSTS

Markdown is perfect right up until a second person touches it. On your own, a folder of .md files is the best documentation system ever invented. Add four colleagues and suddenly you’ve got three different heading styles, a README that links to a domain that expired in 2023, and Steve, who writes every sentence in […]

The short version: you do not need to pay for icons. Three permissively licensed sets cover nearly every project I touch — Tabler Icons (6,100+ general icons, MIT), Phosphor Icons (a huge family in six weights, MIT), and Lucide (1,808 minimal stroke icons, ISC). Need something specific that is not in those three? SVG Repo […]

Live streaming today means signing up for Twitch, YouTube Live, or another Big Tech platform. You get an audience, sure, but you also get ads you cannot control, algorithmic recommendations pushing viewers toward competing streams, rules that change overnight, and a chat system that ties your community to someone else’s database. For a hobbyist streamer, […]

Alexander

I am a full-stack developer. My expertise include:

  • Server, Network and Hosting Environments
  • Data Modeling / Import / Export
  • Business Logic
  • API Layer / Action layer / MVC
  • User Interfaces
  • User Experience
  • Understand what the customer and the business needs


I have a deep passion for programming, design, and server architecture—each of these fuels my creativity, and I wouldn’t feel complete without them.

With a broad range of interests, I’m always exploring new technologies and expanding my knowledge wherever needed. The tech world evolves rapidly, and I love staying ahead by embracing the latest innovations.

Beyond technology, I value peace and surround myself with like-minded individuals.

I firmly believe in the principle: Help others, and help will find its way back to you when you need it.