A CDN-backed CTAN Mirror: tlnet.yihui.org

Yihui Xie 2026-03-28

I set up a partial CTAN (Comprehensive TeX Archive Network) mirror this month at https://tlnet.yihui.org. This post explains why it exists, what’s in it, and how to use it. The short version: TinyTeX now uses it by default, so many of you are already benefiting from it without knowing.

The problem with CTAN mirrors

CTAN has no CDN (Content Delivery Network). There is a mirror multiplexor service at mirror.ctan.org that automatically redirects you to a nearby HTTPS mirror—at the core sites, mirrors.ctan.org sends you to a randomly-selected official mirror in your region, and there are 90+ mirror sites worldwide. While this gives you a CDN-like experience of automatic geographic routing, the underlying infrastructure is a traditional volunteer-run mirror network, not a commercial CDN like Cloudflare or Fastly with edge caching. The mirrors are mostly university and institutional servers that pull updates on a daily schedule rather than serving cached content on demand.

Some mirrors are quick, others are slow, and they are never all in sync at the same moment. In practice this usually doesn’t matter much—but it does create one genuinely painful situation that recurs every single year.

When TeX Live cuts its annual release and it’s synced to your mirror, your old tlmgr essentially breaks when you want to install or update packages, and you have to reinstall TeX Live from scratch. Here is the catch: after the new TeX Live is out, the various CTAN mirrors pick it up at different times. If you bite the bullet and install the new TeX Live while your (default randomly selected) mirror is still serving the old snapshot, you now have the opposite problem: a new tlmgr staring at an old repository, equally confused. The window can last days. Every year (around late March and early April when a new release of TeX Live is made), predictably, we see TinyTeX users bitten by this problem.

Most of CTAN is irrelevant (for our purposes)

I looked into setting up a proper mirror to solve this. The first thing I learned is that the full CTAN tree is enormous—tens of gigabytes. Most of it appears to be historical material that, while potentially of interest to someone somewhere, I doubt people would ever download. The only folder that matters for actually installing and updating TeX Live packages is systems/texlive/tlnet/, which I have to admit embarrassingly that I never knew before.

So I focused on tlnet. Even there, a lot can be thrown away:

After all the filtering, the entire mirror comes to roughly 8,000 files and 2.3 GB. That is a very manageable size.

The setup

The mirror lives at https://tlnet.yihui.org and is backed by Cloudflare Pages (CP) with Cloudflare’s CDN in front, so access should be fast regardless of where you are in the world—no more lottery on which mirror your tlmgr happens to hit.

The sync pipeline is straightforward:

  1. A GitHub Actions workflow runs daily at midnight UTC.
  2. It uses rsync to pull the tlnet directory from rsync://rsync.dante.ctan.org/CTAN/systems/texlive/tlnet/, applying the exclusion rules above, into a local staging directory (cached between runs so only the daily delta is transferred).
  3. Symlinks in the staging directory (TeX Live uses versioned symlinks like foo.tar.xz -> foo.r12345.tar.xz) are resolved into plain files, because CP doesn’t understand symlinks.
  4. Directory index pages are regenerated for you can view files in your browser. That’s not necessary to tlmgr at all, but just for your convenience.
  5. All files under 25MB (CP’s limit) are published directly to CP, and those over 25MB are uploaded to GitHub releases with proper redirects from tlnet.yihui.org, e.g., biber.universal-darwin.tar.xz. Since GitHub doesn’t have this 25MB limit for releases and tlmgr works with redirects, this approach should work perfectly.

The whole thing is in the public repo yihui/ctan-tlnet if you are curious about the details.

One side note: this was almost entirely worked out through conversations with Claude. I went in knowing what I wanted and what to throw away; Claude filled in the gaps on rsync flags, rclone configuration, R2 quirks, and the rest. I’m not sure I would have had the patience to piece it all together from documentation alone.

How to use it

TinyTeX already uses this mirror by default as of the recent builds—you don’t need to do anything if you have installed TinyTeX recently.

If you use vanilla TeX Live and want to point tlmgr at this mirror:

tlmgr option repository https://tlnet.yihui.org/

Or set it for a single command:

tlmgr install <package> --repository https://tlnet.yihui.org/

Since the mirror is updated daily and served from a single CDN origin, all users should see the same state at any given time, which finally eliminates the inter-mirror inconsistency problem. When TeX Live releases its next annual version, the new repository will be available from this URL within 24 hours of the canonical CTAN source updating, and everyone pointing here will flip over at the same moment.

Everything is free

CP is free with a limit of 20,000 files per site, and tlnet has about 8000 files at the moment, which is far below the limit and I don’t think we’ll reach the limit anytime soon. The limit of 25MB per file is circumvented by hosting them in GitHub releases and using CP’s redirects. The really nice thing about CP is that it has no limit on bandwidth.

GitHub Actions provides the compute for the daily sync at no cost as well.

A call for help

Here’s the thing I’m slightly uneasy about: this whole setup lives under my personal Cloudflare account. If I get hit by a bus, the tlnet.yihui.org subdomain may disappear with me someday. I’ve automated everything and my domain yihui.org is set to auto-renew, so this service shouldn’t go away immediately even if I’m gone, but still, it’s not a good idea to be that Nebraskan in this case. The code in yihui/ctan-tlnet is public and anyone could reconstruct the setup: you’d only need to connect the GitHub repo to Cloudflare Pages, and specify the build command to be ./deploy.sh there.

I’d love for an organization—anyone with a stable institutional presence—to take this over. The operational burden is essentially zero: the GitHub Actions workflow handles everything automatically, and the Cloudflare cost is zero. What I’m asking for is just a more permanent home for this CP site and the domain, so that it can outlive my involvement without interruption. I’m happy to transfer this repo to any organization interested in taking it over.

If your organization is interested, or if you have ideas about how to make this more robust, please feel free to reach out or open an issue in the repo. Thanks!