Linkwarden: Self-Hosted Bookmarking for the Tab Hoarder

Save the page, not just the link — before it 404s forever

I have a confession that will surprise nobody who has ever borrowed my laptop: my browser at any given moment is holding somewhere north of two hundred open tabs. Each one is a promise to myself — “I’ll read this later” — and each one is a lie. Worse, the tabs I do eventually bookmark have a nasty habit of rotting. I click through six months later and find a 404, a parked domain, or a “this article has been removed” notice. The thing I wanted to keep is gone, and all my bookmark preserved was the gravestone.

Link rot is the quiet tax on a life lived online. The fix I landed on is Linkwarden, and it has done more for my digital hoarding than any number of New Year’s resolutions to “tab less”.

Advertisement

Most bookmark managers store a URL and a title. That is the bit that rots. Linkwarden stores the URL and a copy of the page — it archives whatever you save as a screenshot, a PDF, a full HTML capture, and a stripped-down readable version. So when the original inevitably vanishes, you still have the content. The link points at a tombstone; Linkwarden hands you the body.

On top of the archiving it does all the sensible organisational things you’d expect: collections (nested folders, basically), tags, and full-text search across the archived content — not just titles, but the actual words inside the saved pages. There’s a browser extension for one-click saving, and you can share collections or individual links publicly if you want to.

It’s open source, written by a small team, and — crucially for me — runs perfectly well on a modest home server.

Linkwarden ships as a Docker image and wants a PostgreSQL database alongside it. A docker-compose.yml is the path of least resistance. Here’s a trimmed-down version of what I run:

services:
  postgres:
    image: postgres:16-alpine
    restart: always
    environment:
      POSTGRES_USER: linkwarden
      POSTGRES_PASSWORD: change-me-to-something-long
      POSTGRES_DB: linkwarden
    volumes:
      - ./pgdata:/var/lib/postgresql/data

  linkwarden:
    image: ghcr.io/linkwarden/linkwarden:latest
    restart: always
    depends_on:
      - postgres
    environment:
      # Generate with: openssl rand -base64 32
      NEXTAUTH_SECRET: paste-a-real-random-secret-here
      NEXTAUTH_URL: https://links.example.com/api/v1/auth
      DATABASE_URL: postgresql://linkwarden:change-me-to-something-long@postgres:5432/linkwarden
    ports:
      - "3000:3000"
    volumes:
      - ./data:/data/data

A few things worth saying out loud, because the docs assume you already know them. NEXTAUTH_SECRET is not optional and not cosmetic — generate a real random value with openssl rand -base64 32 and never reuse it elsewhere. NEXTAUTH_URL must match the address you actually reach the app on; if you put it behind a reverse proxy with TLS (which you should), use the https:// hostname, not localhost. And the ./data volume is where the archives live, so put it somewhere with room to grow.

Bring it up with docker compose up -d, wait for Postgres to settle, hit the port, and create your admin account on first load.

Once it’s up the loop is genuinely pleasant. You paste a URL (or hit the browser extension), optionally drop it into a collection and slap a tag or two on it, and save. In the background Linkwarden goes off and renders the page, grabbing the screenshot, PDF, and readable copy. After a few seconds the entry shows little icons telling you which formats are ready.

I organise by collection — “Homelab”, “Recipes”, “Things to read on a train”, “Articles I’ll cite in an argument later” — and lean hard on tags for the cross-cutting stuff. The search is the part I underrated at first. Because it indexes the archived text, I can find a half-remembered article by a phrase from its third paragraph, even if I gave it a useless title at save time. That alone has retired about forty browser tabs.

This is not magic, and I’d be doing you a disservice to pretend otherwise.

The biggest one is storage. Every archived page is a screenshot plus a PDF plus HTML, and that adds up faster than you’d think — a few hundred saves and you’re into gigabytes. If you’re the kind of person Linkwarden is for, you will fill a disk. Plan for it, and consider whether you really need full PDF archiving on every single link.

Second, some sites resist archiving. Pages hidden behind logins, aggressive paywalls, or heavy anti-bot measures will save as a screenshot of a cookie banner or a “please verify you are human” wall. The readable copy is your friend here, but it isn’t infallible. Sites that render everything client-side can also produce patchy captures.

And as ever with self-hosting: this is now a thing you own. Backups of the Postgres database and the data volume are your responsibility. An archive you forgot to back up is just link rot with extra steps.

Linkwarden is for the chronic tab-hoarder, the researcher, the person who has been burned one too many times by a dead bookmark. If your bookmarking needs end at “save this URL”, a browser already does that for free and you should stop reading. But if you actually want to keep the things you save — to outlive the websites they came from — this is the tool. It’s been the rare piece of self-hosted software that made me change a bad habit rather than just enabling it. My tab count is down, my archive is up, and the next time a site 404s on me, I’ll be the one who still has the page.

Advertisement

Related Content

Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.