Skip to content
READING [0%]
$ cat redirecting-x-com.md

Making x.com links open xcancel automatically

The network-level version needs your own certificate authority, so here is the per-device version instead

Contents

Typing cancel into the address bar

I don’t have a Twitter account I use and I don’t want the app, but Bluesky never took hold as the place the tech conversation happens, so people I know keep sending me x.com links anyway, from Slack and Signal and everywhere else. My method until now has been to tap the link, go up to the address bar, and add cancel after the x, which turns x.com into xcancel.com and gives me the tweet with no account and no app. It works fine. Doing it by hand a few times a week for a year is also exactly the sort of small friction that eventually annoys you into automating it, so I sent Claude off on a research mission.

What I wanted was the network-level version, one change on the Pi that already runs AdGuard, fixing every device in the house at once. That turned out to be considerably harder than I expected, and the reason it is hard is interesting enough to write down.

If you have not run into it, xcancel is a fork of Nitter, the old open-source Twitter frontend. It renders tweets as plain pages, no login, no app prompt, and it takes the same URL paths X does, which is why swapping the domain in the address bar just works.

The network-level version, and why I gave up on it

The guides for doing this network-wide all follow the same shape, and vgmoose’s gist is the clearest version of it I found. Point your DNS at a box you control, sign a certificate for x.com with your own certificate authority, serve an HTTP redirect to your frontend of choice, then trust that CA on all your devices. The gist has MITM in the title, which is the right word for it.

You cannot skip that last step. DNS maps a name to an address and nothing else, so it can happily send your phone to the Pi instead of to X. But the moment the browser connects it asks the server to prove it is x.com, the Pi cannot, and the connection dies during the TLS handshake before any HTTP request exists. Nothing you run on that box gets a turn. I spent a while thinking about this the wrong way round, imagining some small app on the Pi that would render xcancel in place, and it makes no difference what the app does, because the certificate check happens before the app is ever consulted.

So you either run your own CA or you do not do this at the network layer. And running one means a key sitting on your LAN can impersonate any site to every device that trusts it. That is a big thing to sign up for on the way to reading tweets slightly faster.

Two things rule out the workarounds you might reach for. twitter.com is in the HSTS preload list that ships inside Chrome, and for an HSTS host the browser does not offer you a way to click through a certificate error at all. x.com is not preloaded, which looks like a loophole until you remember that real x.com sends the HSTS header itself, so any browser that has been there once behaves the same way.

The detail that actually settled it for me is Android specific. Since Android 7, apps do not trust user-installed certificate authorities by default, so a locally signed x.com certificate would satisfy Chrome while getting rejected inside Slack’s in-app browser. Covering those in-app browsers is the main thing the network-wide approach is supposed to buy you, and on the platform I care about most it does not buy it.

Android

Android turns out to be the good case, because an app there can register as the handler for links and see them before any browser does. Links tapped inside other apps go through it too, which is coverage a browser extension can never give you.

Sidestep is built for precisely this. AGPLv3, free, Android 7 and up, and its manifest declares intent filters for x.com, www.x.com, twitter.com and www.twitter.com, along with a pile of other platforms. It has an instance picker fed from LibRedirect’s live instance list and an option to open the rewritten URL immediately instead of showing you a chooser.

Install it through Obtainium pointed at the repo, or from IzzyOnDroid as com.blankdev.sidestep. It is not on Play. The steps below are against version 1.2.5, so expect the labels to drift.

Now the part that actually blocks people. If the X app is installed, go to Settings, Apps, X, Open by default, and turn off “Open supported links”. Android 12 and up verifies x.com against a file X publishes at /.well-known/assetlinks.json, and a verified handler outranks anything you approved by hand, so Sidestep will not get a look at the link until that toggle is off. Uninstalling X does the job too.

After that, set Sidestep as the handler for those domains, turn on the Twitter to Nitter redirect, pick xcancel in the instance picker, and enable immediate navigation.

Test it from Slack or Reddit and not from Chrome. Chrome working proves very little, since Chrome was never the hard case.

If you would rather write the rule yourself than rely on a service list, URLCheck is on F-Droid and its Pattern module does regex rewriting:

{
  "X to xcancel": {
    "regex": "^https?://(?:[a-z0-9-]+\.)?(?:twitter|x)\.com/(.*)",
    "replacement": "https://xcancel.com/$1",
    "enabled": "true"
  }
}

It shows a small interstitial before handing off to the browser unless you turn on auto-apply, so it is a bit clunkier than Sidestep. You do know exactly what it will do, though.

macOS

Same idea, different mechanism. On a Mac you can make an app the default browser whose only job is to look at each URL and pass it to your real browser, rewritten on the way through. Finicky does this, free and MIT, and it rewrites rather than only picking a browser, which is the distinction that rules out most of the link-chooser apps.

// ~/.finicky.js
export default {
  defaultBrowser: "Safari",
  rewrite: [
    {
      match: ["x.com/*", "twitter.com/*", "*.x.com/*", "*.twitter.com/*"],
      url: (url) => {
        url.host = "xcancel.com";
        return url;
      },
    },
  ],
};

In v4 the url function gets a real JavaScript URL object, so you mutate the host and hand it back. The older v3 style that destructured ({ url }) still runs but logs a legacy warning, which is worth knowing if you copy a config off an old blog post. Same as on Android, this catches links from every app on the machine rather than from one browser.

Velja is the paid App Store alternative with an actual GUI, worth a look if a JS config file is not your idea of a good time, though I would confirm its rules can do arbitrary host swapping before paying.

iOS

iOS is the bad case, and Apple has shut both doors on purpose. An app can only claim https:// links for domains it can prove it owns, by hosting a file on them, so nobody but X can claim x.com. The other way in is the default browser entitlement, which Apple has to grant you, and a default browser is then not allowed to claim links for specific domains anyway.

What is left is a Safari extension, free for a single rule in the case of Redirect Web for Safari, which covers Safari and not other apps’ in-app browsers. Or Orion, the first browser to run Chrome and Firefox extensions on iOS and therefore the only way to get LibRedirect onto a phone, where support is preliminary by Kagi’s own description and request rewriting is the flakiest category of extension.

Windows and Linux

Both have Finicky equivalents that intercept at the OS level, BrowserTamer and LinkRouter on Windows, handlr or an xdg-open wrapper on Linux. I have not tried any of them, so treat those as search terms and not as a recommendation. The dependable fallback on either is LibRedirect or Redirector inside Firefox, which only covers links you click in that browser.

The instances are the fragile part

Nitter mostly died in early 2024 when X removed guest accounts, which is what the old instances relied on to read anything without logging in. Ars Technica covered it at the time, and the issue thread where it got worked out is still there if you want the unhappy version. The instances still standing run on real authenticated accounts, so whoever operates them is cycling banned accounts on your behalf, continuously. Assume whichever one you pick will eventually go dark, and know where the instance list is.

xcancel is the most active survivor and it is in LibRedirect’s default instance list. twiiit.com is a different thing, a meta-redirector that pings instances every few minutes and forwards you to one that is up, preserving the path, so twiiit.com/user/status/123 lands you on the same tweet wherever it sends you. It is not in the default list, so add it as a custom rule if you would rather have round-robin than a fixed instance.

If you pin xcancel and it goes down, you find out by tapping a link and getting nothing, then you go and edit config. With twiiit you mostly just notice that a page took an extra second.

LAST_MODIFIED: Jul 25, 2026 TAGS:
#selfhosted#privacy#homelab