Tutoriales de VPS
Alojar Linkwarden, el gestor de marcadores autoalojado, en un VPS (2026): Docker Compose, PostgreSQL, MeiliSearch, HTTPS con Caddy y configuración de seguridad y copias de seguridad
Este tutorial explica cómo desplegar Linkwarden en un VPS con Docker Compose para crear tu propio gestor de marcadores con archivado de páginas web, capturas de pantalla, búsqueda de texto completo y colaboración en equipo. Cubre PostgreSQL, MeiliSearch, HTTPS con Caddy, NEXTAUTH_SECRET, NEXTAUTH_URL, desactivar el registro, copias de seguridad y actualizaciones, aislamiento del dominio de contenido de usuario y resolución de problemas comunes.

Alojar Linkwarden, el gestor de marcadores autoalojado, en un VPS (2026): Docker Compose, PostgreSQL, MeiliSearch, HTTPS con Caddy y configuración de seguridad y copias de seguridad
El problema de los marcadores del navegador no es que no puedas guardar cosas, sino que una vez guardadas cuesta mucho volver a encontrarlas.
Es probable que hayas guardado muchos artículos, herramientas, papers, páginas de productos, repositorios de GitHub y enlaces a tutoriales. Al cabo de unos meses, la página puede dar 404, el título ha cambiado, el contenido está detrás de un muro de inicio de sesión o simplemente no recuerdas por qué la guardaste.
Linkwarden resuelve este problema: no es solo una lista de marcadores, sino un sistema de colección y archivado web que puedes autoalojar. Puede guardar enlaces, generar capturas de pantalla, archivar el contenido de las páginas web, organizar por colecciones, admitir búsqueda de texto completo y también sirve para que pequeños equipos compartan una base de conocimiento.
Este artículo desplegará Linkwarden en un VPS con Docker Compose y usará Caddy para proporcionar HTTPS. Nos centraremos en explicar PostgreSQL, MeiliSearch, las variables de entorno clave, desactivar el registro público, las copias de seguridad y actualizaciones, y los límites de seguridad, en lugar de copiar los cientos de líneas de configuración del .env.sample oficial.
Linkwarden encaja en estos escenarios:
- Quieres autoalojar un sustituto de Raindrop, Pocket o Pinboard.
- Necesitas conservar a largo plazo enlaces web, capturas de pantalla y contenido archivado.
- Quieres que tus marcadores se puedan buscar a texto completo, no solo por título.
- Quieres mantener una base de conocimiento, una biblioteca de competidores, una biblioteca de tutoriales o una biblioteca de recursos para un equipo.
- No quieres tener todos tus datos de lectura y marcadores en una plataforma de terceros.
Encaja menos en estos escenarios:
- Solo quieres guardar temporalmente unos cuantos enlaces y los marcadores del navegador te bastan.
- Principalmente quieres leer RSS y FreshRSS encaja mejor.
- Solo necesitas leer más tarde y Wallabag puede ser más ligero.
- No quieres mantener una base de datos, copias de seguridad y un proxy inverso.
Las palabras clave de Linkwarden son «coleccionar + archivar + buscar». Si solo necesitas un lector de RSS, no es un sustituto de FreshRSS; si necesitas conservar pruebas de páginas web, enlaces de referencia y un punto de entrada de conocimiento para el equipo, te encajará mejor.
Linkwarden es algo más pesado que FreshRSS, porque por defecto usa PostgreSQL para almacenar datos, MeiliSearch para la búsqueda y Playwright / Chromium para generar capturas de pantalla y archivos de páginas web.
| Caso de uso | Configuración recomendada |
|---|---|
| Uso personal ligero | 1-2 vCPU / 2GB RAM / 20GB SSD |
| Archivado personal a largo plazo, muchas capturas | 2 vCPU / 4GB RAM / 40GB SSD |
| Base de conocimiento compartida de un equipo pequeño | 2-4 vCPU / 4GB+ RAM / 80GB+ SSD |
La documentación oficial de instalación menciona que un VPS con 4GB de RAM funciona con bastante fluidez. Con 1GB de memoria también puede funcionar, pero se queda justo al hacer capturas y archivar páginas web. El consumo de recursos de Linkwarden proviene principalmente de tres partes: la base de datos, el índice de búsqueda y el archivado de páginas web y las capturas de pantalla.
Si tu VPS solo tiene 1GB de memoria libre y ya ejecuta bases de datos, paneles, monitorización, pasarelas de IA y otros servicios, no es recomendable meter Linkwarden a la fuerza. Puede funcionar, pero la experiencia y la estabilidad no serán buenas.
El Docker Compose oficial contiene principalmente tres tipos de servicios:
| Servicio | Función |
|---|---|
| Linkwarden | Aplicación web, API, tareas de archivado, procesamiento de capturas de pantalla |
| PostgreSQL | Almacena datos centrales como usuarios, enlaces, colecciones y configuración |
| MeiliSearch | Proporciona búsqueda de texto completo rápida |
Además, este artículo añade un contenedor Caddy para obtener automáticamente certificados HTTPS y hacer proxy inverso hacia Linkwarden.
La imagen oficial de Linkwarden es:
ghcr.io/linkwarden/linkwarden:latest
El puerto predeterminado de la aplicación es 3000.
Este artículo asume:
- El sistema del VPS es Ubuntu 22.04 / 24.04 o Debian 12.
- Docker y Docker Compose V2 están instalados.
- Tienes un dominio preparado, por ejemplo
links.example.com. - El registro A del dominio apunta a la IP pública del VPS.
- Los puertos 80 y 443 se pueden abrir para Caddy.
Crear los directorios:
sudo mkdir -p /opt/linkwarden
sudo chown -R "$USER":"$USER" /opt/linkwarden
cd /opt/linkwarden
mkdir -p data pgdata meili_data caddy-data caddy-config backups
Propósito de los directorios:
| Directorio | Propósito |
|---|---|
data | Archivos de archivo de Linkwarden, capturas de pantalla, PDF, contenido subido |
pgdata | Archivos de la base de datos PostgreSQL |
meili_data | Índice de búsqueda de MeiliSearch |
caddy-data | Certificados y datos de ejecución de Caddy |
caddy-config | Datos de configuración de Caddy |
backups | Directorio de copias de seguridad locales |
Todos estos directorios deben incluirse en las copias de seguridad. Especialmente pgdata y data: uno son datos estructurados y el otro son archivos de archivo, y sin ninguno de los dos la restauración está incompleta.
Crear .env:
nano .env
Escribe el siguiente contenido, cambiando el dominio y las contraseñas por los tuyos:
LINKWARDEN_DOMAIN=links.example.com
POSTGRES_PASSWORD=replace-with-a-long-random-password
NEXTAUTH_SECRET=replace-with-a-long-random-secret
MEILI_MASTER_KEY=replace-with-a-long-random-meili-key
NEXTAUTH_URL=https://links.example.com/api/v1/auth
BASE_URL=https://links.example.com
NEXT_PUBLIC_DISABLE_REGISTRATION=true
NEXT_PUBLIC_CREDENTIALS_ENABLED=true
Puedes generar claves aleatorias con:
openssl rand -base64 32
Aquí hay varios puntos clave:
NEXTAUTH_SECRETdebe ser un valor aleatorio fuerte, usado para firmar sesiones y JWT.POSTGRES_PASSWORDes la contraseña de PostgreSQL.MEILI_MASTER_KEYes la clave maestra de MeiliSearch; no la dejes vacía.- Los tres valores deben ser cadenas aleatorias largas, únicas y de alta entropía; no reutilices contraseñas antiguas ni uses los marcadores de posición del ejemplo.
NEXTAUTH_URLdebe llevar el sufijo/api/v1/auth; este es un punto muy fácil de equivocar desde Linkwarden v2.BASE_URLusa la dirección de acceso público; los correos y algunos enlaces la usan.- Se recomienda mantener
NEXT_PUBLIC_DISABLE_REGISTRATION=trueactivado tras crear la primera cuenta, para evitar el registro público abierto.
Si aún no tienes cuenta en el primer despliegue, puedes poner primero NEXT_PUBLIC_DISABLE_REGISTRATION=false, crear la cuenta de administrador y luego volver a true y ejecutar:
docker compose down
docker compose up -d
Nota: tras modificar .env, no ejecutes solo docker compose restart. La documentación oficial también recuerda que los cambios en variables de entorno requieren down seguido de up -d para recargarse.
Crear docker-compose.yml:
nano docker-compose.yml
Escribe:
services:
postgres:
image: postgres:16-alpine
container_name: linkwarden-postgres
restart: unless-stopped
env_file:
- .env
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- ./pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
meilisearch:
image: getmeili/meilisearch:v1.12.8
container_name: linkwarden-meilisearch
restart: unless-stopped
env_file:
- .env
environment:
MEILI_MASTER_KEY: ${MEILI_MASTER_KEY}
MEILI_NO_ANALYTICS: "true"
volumes:
- ./meili_data:/meili_data
linkwarden:
image: ghcr.io/linkwarden/linkwarden:latest
container_name: linkwarden
restart: unless-stopped
env_file:
- .env
environment:
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/postgres
MEILI_HOST: http://meilisearch:7700
MEILI_MASTER_KEY: ${MEILI_MASTER_KEY}
ports:
- "127.0.0.1:3000:3000"
volumes:
- ./data:/data/data
depends_on:
postgres:
condition: service_healthy
meilisearch:
condition: service_started
caddy:
image: caddy:2-alpine
container_name: linkwarden-caddy
restart: unless-stopped
env_file:
- .env
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy-data:/data
- ./caddy-config:/config
depends_on:
- linkwarden
Aquí el puerto 3000 de Linkwarden se vincula a 127.0.0.1, por lo que no se puede acceder directamente desde internet. Lo que realmente se expone al exterior es el HTTPS de Caddy.
Si estás seguro de que solo Caddy, dentro del mismo Compose, necesita acceder a Linkwarden, también puedes eliminar por completo la sección ports, dejando Linkwarden expuesto solo en la red interna de Docker. Este artículo mantiene 127.0.0.1:3000:3000, principalmente por comodidad para depurar en local y a la vez evitar el acceso público directo.
PostgreSQL y MeiliSearch no exponen puertos a internet; solo Linkwarden los accede dentro de la red de Docker. No expongas 5432 ni 7700 directamente a internet.
Crear Caddyfile:
nano Caddyfile
Escribe:
{$LINKWARDEN_DOMAIN} {
encode zstd gzip
reverse_proxy linkwarden:3000
}
Abrir el firewall:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw reload
Si tu proveedor de nube tiene grupos de seguridad, permite también 80 y 443 allí. Caddy necesita estos dos puertos para completar la solicitud de certificado HTTP-01 y el acceso HTTPS.
Si usas el proxy de Cloudflare, el modo SSL/TLS debería ser Full (strict), no Flexible. Flexible suele causar problemas con el protocolo de origen, las redirecciones y las devoluciones de llamada de inicio de sesión.
Ejecuta:
cd /opt/linkwarden
docker compose up -d
Ver los contenedores:
docker compose ps
Ver los registros:
docker compose logs -f --tail=100 linkwarden
Abre el navegador y visita:
https://links.example.com
Crea una cuenta tras entrar por primera vez. Una vez creada, confirma que .env contiene:
NEXT_PUBLIC_DISABLE_REGISTRATION=true
Luego recrea los contenedores para que las variables de entorno surtan efecto:
docker compose down
docker compose up -d
Así los usuarios públicos no podrán registrar cuentas libremente.
MeiliSearch es un motor de búsqueda y, si no configuras MEILI_MASTER_KEY, el control de acceso será débil. Aunque este artículo no expone 7700 a internet, los servicios internos también deberían tener una clave.
La forma correcta:
MEILI_MASTER_KEY=replace-with-a-long-random-meili-key
Y configurar ambas en el servicio de Linkwarden:
MEILI_HOST: http://meilisearch:7700
MEILI_MASTER_KEY: ${MEILI_MASTER_KEY}
No elimines MeiliSearch por comodidad. La búsqueda avanzada de Linkwarden depende de él, y la diferencia se nota claramente cuando aumenta el número de marcadores.
Linkwarden guarda instantáneas de páginas web, archivos HTML, capturas de pantalla y PDF. Las páginas web archivadas son esencialmente «contenido no confiable», que puede contener scripts extraños, HTML malicioso o páginas engañosas.
El proyecto oficial ofrece la función User Content Domain, que permite poner el contenido archivado en otro dominio, por ejemplo:
links.example.com
usercontent.example.com
Así la aplicación principal y el contenido archivado no están en el mismo origen, y el límite de seguridad es más claro. La clave aquí es «hostname separado / origen separado»; no sigas reutilizando el dominio de la aplicación principal.
Para un uso personal ligero puedes no configurarlo por ahora, pero si vas a guardar muchas páginas web desconocidas y los miembros del equipo van a abrir contenido archivado de los demás, conviene planificar un dominio de contenido de usuario cuanto antes.
La idea general es:
- El dominio principal
links.example.comaccede a la aplicación Linkwarden. - El dominio de contenido
usercontent.example.comse usa solo para acceder al contenido archivado. - Ambos dominios pueden apuntar al mismo VPS.
- Caddy o Nginx distinguen el reenvío y las reglas de acceso por host.
- Configurar
NEXT_PUBLIC_USER_CONTENT_DOMAINen.env.
Esto es una configuración de seguridad avanzada. Este artículo primero completa el despliegue de la aplicación principal, pero al menos debes saber que los archivos de páginas web no son recursos estáticos totalmente confiables.
Las copias de seguridad de Linkwarden no pueden limitarse a un solo directorio. Como mínimo deben incluir:
- La base de datos PostgreSQL.
- El directorio de archivos de archivo
data. - El directorio del índice de MeiliSearch.
.env,docker-compose.yml,Caddyfile.
Crear un script de copia de seguridad:
nano backup-linkwarden.sh
Escribe:
#!/usr/bin/env bash
set -euo pipefail
APP_DIR="/opt/linkwarden"
BACKUP_DIR="$APP_DIR/backups"
DATE="$(date +%F-%H%M)"
mkdir -p "$BACKUP_DIR/$DATE"
cd "$APP_DIR"
# 先停止会写入数据库和归档文件的服务,避免 SQL 快照和 data 目录时间点不一致。
docker compose stop linkwarden meilisearch
# 备份 PostgreSQL 逻辑数据。PostgreSQL 容器保持运行即可执行 pg_dump。
docker compose exec -T postgres pg_dump -U postgres postgres > "$BACKUP_DIR/$DATE/postgres.sql"
# 备份归档文件、搜索索引和配置文件。
tar czf "$BACKUP_DIR/$DATE/linkwarden-files.tar.gz" data meili_data docker-compose.yml Caddyfile .env
# 重新启动服务。
docker compose start meilisearch linkwarden
# 保留最近 14 天备份。
find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d -mtime +14 -exec rm -rf {} \;
echo "Backup saved to $BACKUP_DIR/$DATE"
Este orden es importante: primero detén Linkwarden y luego haz el pg_dump y el archivado de archivos. De lo contrario, la base de datos puede registrar ya un archivo de archivo, pero el archivo aún no ha terminado de escribirse cuando empaquetas el directorio data, o al revés, y los archivos no coinciden con la base de datos.
Añadir permisos de ejecución y probar:
chmod +x backup-linkwarden.sh
./backup-linkwarden.sh
ls -lh backups
Tarea programada:
crontab -e
Copia de seguridad cada día a las 3 de la madrugada:
0 3 * * * /opt/linkwarden/backup-linkwarden.sh >> /opt/linkwarden/backups/backup.log 2>&1
La copia de seguridad local es solo la primera capa. Se recomienda usar además rclone para sincronizar con almacenamiento de objetos, un NAS u otro VPS.
Si cambias el contenido archivado a S3 / MinIO / almacenamiento de objetos, el foco de las copias de seguridad cambia: la base de datos sigue necesitando copia de seguridad, y el bucket de almacenamiento de objetos también necesita versionado o una estrategia de instantáneas.
Detén los servicios antes de restaurar:
cd /opt/linkwarden
docker compose down
Primero restaura la copia de seguridad a nivel de archivos. Esto restaura los archivos de archivo data, el índice de búsqueda meili_data, .env, docker-compose.yml y Caddyfile:
tar xzf /opt/linkwarden/backups/2026-07-09-0300/linkwarden-files.tar.gz -C /opt/linkwarden
Restaurar solo postgres.sql no es suficiente, porque las capturas de pantalla, los PDF, los archivos HTML y los archivos subidos están en el directorio data. La copia de seguridad de archivos y la de la base de datos deben restaurarse juntas para que Linkwarden quede en un estado completo tras la restauración.
Inicia primero PostgreSQL:
docker compose up -d postgres
Espera a que la base de datos esté sana y restaura el SQL:
docker compose exec -T postgres psql -U postgres postgres < /opt/linkwarden/backups/2026-07-09-0300/postgres.sql
Luego inicia todos los servicios:
docker compose up -d
Comprueba tras restaurar:
- ¿Puedes iniciar sesión?
- ¿Existen las colecciones y los enlaces?
- ¿Se pueden abrir las capturas de pantalla, los PDF y los archivos HTML?
- ¿Funciona la búsqueda?
- ¿El certificado de Caddy está correcto?
Si el índice de MeiliSearch tiene problemas, normalmente se puede reconstruir; pero si se pierden los directorios de PostgreSQL y data, se verán afectados los datos centrales y los archivos de archivo.
Haz copia de seguridad antes de actualizar:
cd /opt/linkwarden
./backup-linkwarden.sh
Actualizar:
docker compose pull
docker compose down
docker compose up -d
¿Por qué no solo restart? Porque cuando cambian las variables de entorno, las imágenes y las definiciones de servicios, restart no recarga la configuración por completo.
Comprueba tras actualizar:
docker compose ps
docker compose logs --tail=100 linkwarden
Si actualizas desde una versión muy antigua de Linkwarden, lee primero las notas oficiales de actualización. Especialmente para la migración de v1 a v2, NEXTAUTH_URL debe llevar /api/v1/auth, y pueden ser necesarios pasos de migración adicionales.
Comprueba primero:
NEXTAUTH_URL=https://links.example.com/api/v1/auth
BASE_URL=https://links.example.com
NEXTAUTH_URL no debe ser solo el dominio raíz; debe llevar /api/v1/auth. Este es uno de los problemas de autenticación más comunes.
Tras modificar .env, ejecuta:
docker compose down
docker compose up -d
Confirma .env:
NEXT_PUBLIC_DISABLE_REGISTRATION=true
Luego recrea los contenedores:
docker compose down
docker compose up -d
Solo reiniciar puede que no cargue las nuevas variables de entorno.
Comprueba MeiliSearch:
docker compose logs --tail=100 meilisearch
Confirma que .env coincide con MEILI_MASTER_KEY en el Compose, y que MEILI_HOST en Linkwarden es:
http://meilisearch:7700
No expongas el puerto de MeiliSearch a internet.
Linkwarden usa un entorno de navegador para procesar páginas web. Las causas de fallo pueden ser:
- El VPS tiene muy poca memoria.
- La página web de destino tiene anti-scraping, muro de inicio de sesión o restricciones regionales.
- Anomalías de acceso por DNS o IPv6.
- El certificado TLS del sitio de destino tiene problemas.
- El contenedor no tiene suficiente tiempo para completar la captura de pantalla.
Mira primero los registros:
docker compose logs --tail=200 linkwarden
Si fallan muchas tareas de archivado, primero amplía el VPS a 2GB o 4GB de memoria y luego investiga la red y las restricciones del sitio de destino.
Comprueba:
- Si el dominio resuelve al VPS.
- Si 80/443 están permitidos en el firewall del sistema.
- Si el grupo de seguridad de la nube los permite.
- Si ya hay Nginx/Apache ocupando 80/443 en el servidor.
- Si Cloudflare está usando un modo SSL/TLS incorrecto.
Ver los registros de Caddy:
docker compose logs --tail=100 caddy
Antes de poner en producción, como mínimo haz esto:
- No exponer el puerto 5432 de PostgreSQL.
- No exponer el puerto 7700 de MeiliSearch.
- Vincular Linkwarden solo a
127.0.0.1:3000, o no publicar ningún puerto del host y dejar que solo Caddy acceda a través de la red de Docker. - Usar Caddy o Nginx para proporcionar HTTPS.
- Usar valores aleatorios fuertes para
NEXTAUTH_SECRET,POSTGRES_PASSWORDyMEILI_MASTER_KEY. - Desactivar el registro público tras crear la primera cuenta.
- No subir
.enva GitHub. - Hacer copias de seguridad periódicas de PostgreSQL y de los archivos de archivo.
- Si lo usa un equipo, considera configurar un dominio de contenido de usuario para aislar el HTML archivado.
- No activar a la ligera opciones que accedan a redes privadas o que omitan la verificación TLS.
Linkwarden visita activamente los enlaces de páginas web que guardas, así que, igual que RSSHub y FreshRSS, debes prestar atención a los límites de las solicitudes del lado del servidor. Por defecto, no dejes que acceda a direcciones de red internas ni lo trates como un proxy de scraping de páginas web arbitrarias.
| Herramienta | Para qué es más adecuada |
|---|---|
| Linkwarden | Coleccionar enlaces, archivar páginas web, base de conocimiento de equipo, búsqueda de texto completo |
| FreshRSS | Suscripciones RSS, flujos de lectura, sincronización de leído |
| Wallabag | Leer más tarde, guardar el cuerpo del artículo |
| Nextcloud Bookmarks | Sincronización sencilla de marcadores, adecuada para usuarios de Nextcloud |
Si cada día lees fuentes, elige FreshRSS. Si quieres conservar «material de páginas web que usarás más adelante», elige Linkwarden. Si solo quieres leer artículos largos más tarde, Wallabag es más ligero.
Comprueba antes del uso formal:
- El dominio resuelve al VPS.
- 80/443 están permitidos en el firewall y el grupo de seguridad.
-
NEXTAUTH_URLestá configurado comohttps://links.example.com/api/v1/auth. -
BASE_URLestá configurado con el dominio HTTPS público. -
NEXTAUTH_SECRETes un valor aleatorio fuerte. -
POSTGRES_PASSWORDes un valor aleatorio fuerte. -
MEILI_MASTER_KEYes un valor aleatorio fuerte. - PostgreSQL no está expuesto a internet.
- MeiliSearch no está expuesto a internet.
- Linkwarden está vinculado solo a
127.0.0.1:3000local, o no se publica ningún puerto del host. - El registro está desactivado tras crear la cuenta.
- El script de copia de seguridad se ha probado.
- Se ha hecho al menos un simulacro de restauración.
- Se ha evaluado el aislamiento del dominio de contenido de usuario para uso en equipo.
Linkwarden es muy adecuado para ejecutarse a largo plazo en un VPS. Es más fiable que los marcadores del navegador, más adecuado para archivar que las herramientas de marcadores simples y más adecuado como base de conocimiento que las herramientas puramente de leer más tarde.
Al desplegar, lo más importante no es solo poner el servicio en marcha, sino acertar en unos pocos puntos clave: no exponer PostgreSQL a internet, configurar la clave maestra de MeiliSearch, que NEXTAUTH_URL lleve /api/v1/auth, desactivar el registro tras crear la cuenta y hacer copias de seguridad periódicas de la base de datos y de los archivos de archivo.
Si ya tienes un VPS con 2GB o 4GB de memoria, Linkwarden es un gestor de marcadores que merece mucho la pena autoalojar. Puede convertir los enlaces de «leer más tarde» en una base de conocimiento realmente buscable, archivable y conservable a largo plazo.