Why self-host Linkwarden
The most common objection is operational overhead: one more service to update, back up and secure — better to stay on a SaaS. It is a fair concern, but it rests on an incorrect premise: a well-containerised tool like Linkwarden requires no more maintenance than a standard Docker image. Updating means pulling the new image and restarting the service. The backup targets a single database volume. TLS is handled by a reverse proxy already running on the VPS.
What self-hosting does solve is dependence on a SaaS whose access terms can change overnight. Several teams have lost access to shared research libraries after a third-party tool shut down or was acquired. On your VPS, the data belongs to your organisation, collections remain accessible regardless of the vendor's commercial situation, and you control the retention policy.
What Linkwarden brings to an agency
- Shared collections — each project or client gets its own collection, accessible to the right team members without exposing other folders.
- Per-member permissions — rights are managed at the collection level: reader, contributor or manager, depending on each person's role.
- Shared annotations — members of a collection can leave notes on each link, avoiding duplicate research and building institutional knowledge.
- Multi-user browser extension — each collaborator installs the extension and saves directly into the target collection, without opening the web interface.
- Page archiving — Linkwarden keeps a local copy of saved pages, so resources that disappear from the web are not lost.
- REST API — links integrate with other internal tools via the API, without any dependency on the graphical interface.
- Full data ownership — no behavioural analytics, no external syndication of bookmarks to third parties.
Requirements before installation
A VPS with 2 vCPU and 2 GB of RAM is sufficient for an agency of 5 to 15 people at normal usage: collections load quickly and page archiving runs in the background without affecting navigation. Below 1 GB of RAM, the Next.js Node.js process can be killed by the OOM killer under load.
Plan for 10 to 20 GB of storage depending on the volume of archived pages: each screenshot and page copy weighs between 100 KB and 2 MB. A {{vps.power.name}} VPS with NVMe SSD suits this workload.
On the network side, port 443 must be open and a domain name must point to the VPS IP before generating the TLS certificate. Linkwarden requires Docker and Docker Compose, available on all major Linux distributions. A PostgreSQL database is required — it is included in the official Compose file.
Step-by-step deployment
Prepare the working directory
Connect via SSH to your VPS as root or a sudoer, then create the directory that will hold the configuration files:
mkdir -p /opt/linkwarden && cd /opt/linkwardenCreate the Compose file
Create a docker-compose.yml file with the following content. Values in angle brackets must be replaced before starting:
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: linkwarden
POSTGRES_USER: linkwarden
POSTGRES_PASSWORD: <db-password>
volumes:
- postgres_data:/var/lib/postgresql/data
linkwarden:
image: ghcr.io/linkwarden/linkwarden:latest
restart: unless-stopped
depends_on:
- postgres
environment:
DATABASE_URL: postgresql://linkwarden:<db-password>@postgres:5432/linkwarden
NEXTAUTH_SECRET: <random-32-character-string>
NEXTAUTH_URL: https://your-domain.com
volumes:
- linkwarden_data:/data/data
ports:
- "127.0.0.1:3000:3000"
volumes:
postgres_data:
linkwarden_data:To generate the NEXTAUTH_SECRET value, run on the server: openssl rand -base64 32.
Set NEXTAUTH_URL carefully
The NEXTAUTH_URL variable must exactly match the public URL of your instance, including the protocol. If your instance is behind a TLS reverse proxy, the value must start with https:// and not http://.
A malformed NEXTAUTH_URL produces the following error on first load: Error: NEXTAUTH_URL is not configured. Please set NEXTAUTH_URL in your .env. If the value uses http:// while the proxy returns HTTPS, the session is never created and the login page reloads in a loop with no visible error message.
Start the containers
From /opt/linkwarden, run:
docker compose up -dImages are downloaded, then PostgreSQL starts first. Linkwarden waits for the database to be ready before initialising the schema. Check that both containers are running with docker compose ps.
Configure the reverse proxy (nginx)
Linkwarden listens on 127.0.0.1:3000. Add an nginx server block to proxy HTTPS traffic:
server {
listen 443 ssl;
server_name your-domain.com;
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Reload nginx with nginx -t && systemctl reload nginx.
Generate the TLS certificate
If not already done, generate a Let's Encrypt certificate with Certbot:
certbot --nginx -d your-domain.comCertbot automatically updates the nginx block to add certificate paths and the HTTP-to-HTTPS redirect. Renewal is automatic via the systemd timer installed by the package.
Create the first administrator account
Open https://your-domain.com in a browser. Linkwarden prompts you to create the first account on first access. This account becomes the instance administrator. Invite each collaborator from Settings → Members by entering their email address.
Distribute the browser extension
The Linkwarden extension is available on the Chrome Web Store and Firefox Add-ons. During initial setup, each member enters the instance URL (https://your-domain.com) and generates an API token from Settings → Access Tokens. Bookmarks saved via the extension go directly into the collection chosen by the user.
Post-installation hardening
Restrict direct access to port 3000 from outside — it should only respond on the local interface: verify that the ports line in your Compose file binds to 127.0.0.1:3000:3000 and not 0.0.0.0:3000:3000.
Enable automatic image updates with Watchtower or schedule a weekly docker compose pull && docker compose up -d cron job.
Back up the postgres_data volume with a daily dump: docker compose exec postgres pg_dump -U linkwarden linkwarden > /opt/backups/linkwarden-$(date +%F).sql. Store dumps off-server (S3, remote storage) to survive a disk failure.
Linkwarden or Karakeep: two distinct use cases
Karakeep (formerly Hoarder) and Linkwarden are often mentioned together in self-hosted bookmark manager comparisons, but they do not address the same need.
Karakeep is focused on intelligent capture: it automatically extracts and summarises page content using a local or remote language model. It is designed for individual use, where the value comes from AI sorting and classifying. It does not expose the concept of shared collections or per-member permission management.
Linkwarden is designed for collaboration: the central concept is the shared collection, accessible to multiple members with differentiated rights. Annotations are shared, not personal. The browser extension connects to the organisation's instance, not a personal account. For an agency that wants to centralise research across project teams, Linkwarden is the right tool. For someone who wants a personal library enriched by AI, Karakeep is a better fit.
Linkwarden vs Karakeep — summary table
| Criterion | Linkwarden | Karakeep |
|---|---|---|
| Primary use | Team collaborative research | AI-assisted personal capture |
| Shared collections | Yes, with per-member rights | No (solo use) |
| Shared annotations | Yes | No |
| Multi-user browser extension | Yes | Yes (single account) |
| AI content summarisation | No | Yes (local model or API) |
| Minimum resources | 2 vCPU / 2 GB RAM | 2 vCPU / 4 GB RAM (local model) |
| Licence | AGPL-3.0 | AGPL-3.0 |
Common errors during installation
The login page reloads in a loop with no error message.
Likely cause: NEXTAUTH_URL is set to http:// while the reverse proxy serves HTTPS. NextAuth generates a secure cookie that the browser refuses to send back over an unencrypted connection. Fix the value in the Compose file, then restart with docker compose up -d --force-recreate.
The interface loads but collection images do not.
The linkwarden_data volume path is not mounted correctly. Verify that the volume is declared in the volumes section of the Compose file and that no previous bind mount left the folder empty.
ECONNREFUSED error in Linkwarden logs at startup.
Linkwarden starts before PostgreSQL is ready to accept connections. Add a healthcheck to the postgres service and a depends_on: postgres: condition: service_healthy condition to the linkwarden service to enforce the order.
The browser extension shows "Cannot reach server".
Check that the URL entered in the extension settings has no trailing slash (https://your-domain.com not https://your-domain.com/). Some extension versions are sensitive to this.
Page archiving does not work.
The archiving process calls Chromium in headless mode from inside the container. On VPS instances with less than 1.5 GB of RAM, the process is terminated before capture completes. Increase VPS resources or disable automatic archiving in Settings → Archiving if RAM is constrained.
The next step: centralise all agency services
An agency that self-hosts its research with Linkwarden has already crossed the line: it manages its own data, its own updates, its own backups. That is the posture of a team that knows what it is running.
The next logical step is to centralise client domains, hosting and VPS under your own brand — without multiplying interfaces or losing visibility over renewals. That is exactly what the ServOrbit agency plan enables.
If you want to go further on self-hosting team tools, the articles on Outline (collaborative wiki) and server automation with Ansible complement this guide.