~/ziphub — zsh
ZipHub·~/catalog~/articles~/feed~/sellers~/hire
auth login
[article]category · network

Cómo dejé cinco VPS por un servidor dedicado con Proxmox

@dignezzz · author17 min read2026-05-05free

TL;DR: Resumen: Infraestructura fragmentada de cinco VPS de tres proveedores → un servidor dedicado Ryzen 7 9700X / 64GB ECC / 2× NVMe ZFS-mirror, con Proxmox VE. Dentro: VMs/LXCs por rol, NetBird-mesh en lugar de reenvío de puertos, nodo de salida en un VPS separado para evitar bloqueos y terminación TLS, PBS para copias de seguridad. Más dinero, más control, menos estrés. En este artículo: por qué, cómo y qué errores cometí en el camino para que tú no los cometas.

Cómo dejé cinco VPS por un servidor dedicado con Proxmox y dejé de alimentar a los hosters

Resumen: Infraestructura fragmentada de cinco VPS de tres proveedores → un servidor dedicado Ryzen 7 9700X / 64GB ECC / 2× NVMe ZFS-mirror, con Proxmox VE. Dentro: VMs/LXCs por rol, NetBird-mesh en lugar de reenvío de puertos, nodo de salida en un VPS separado para evitar bloqueos y terminación TLS, PBS para copias de seguridad. Más dinero, más control, menos estrés. En este artículo: por qué, cómo y qué errores cometí en el camino para que tú no los cometas.

¿Por qué me metí en esto?

A finales del año pasado, me di cuenta de que estaba pagando por cinco VPS diferentes a tres hosters. Uno para un foro de Discourse, otro para un foro legacy en PHP con MySQL, otro para facturación y bots de Telegram, un cuarto para VPN/proxy, y el quinto para estática y varias herramientas pequeñas. Y además, por ahí andaba n8n, Vaultwarden y un par de Uptime Kuma que se monitorizaban mutuamente (¿cómo si no?).

Lo malo era:

  • Dinero. En total, salía aproximadamente lo mismo que cuesta un servidor dedicado decente con estas características juntas (pero ahí era compartido, y esto es dedicado).
  • Cero aislamiento. Si la memoria de un cliente de un bot empieza a dispararse, mi foro empieza a ir lento. En el mismo servidor. ¡Genial!.
  • Copias de seguridad, un desastre. Cada VPS tenía su propio cron de rsync a S3, con diferentes retenciones, sin incrementalidad, restaurar era toda una aventura.
  • Red como una ensalada. SSH a través de diferentes puertos, reenviados a través de Cloudflare Tunnels, a través de WireGuard levantado manualmente entre dos VPS, con configuraciones en tres sitios. Cada vez que añadía una nueva máquina, me llevaba un día integrarla.
  • Actualizaciones apocalípticas. En cinco Ubuntu/Debian diferentes con versiones y paquetes distintos, las actualizaciones había que hacerlas a mano y nunca al mismo tiempo. Cada seis meses, heroicamente, pasaba el maratón de "actualizar todo, no romper nada".
  • CPU y RAM alocadas. Cada VPS estaba reservada "para el pico", pero en promedio se usaba un 15-20%. Dinero tirado a la basura.

En resumen, el clásico caso de "oh, necesito otro servicio, ¡compro otra VPS!". Era hora de consolidar todo.

¿Qué elegí y por qué?

Hardware

Elegí un servidor dedicado:

  • CPU: AMD Ryzen 7 9700X (8 núcleos / 16 hilos, Zen 5)
  • RAM: 64 GB DDR5 ECC 5200 MHz
  • Discos: 2× NVMe 512 GB enterprise → ZFS mirror
  • Red: 1 Gbps ilimitado
  • Precio: aproximadamente como dos VPS de gama media en el mismo OVH

Hipervisor

Proxmox VE 9.x. Consideré alternativas, pero brevemente:

  • Docker pelado — sin aislamiento, sin snapshots decentes, backup solo por compose, disco un vertedero único.
  • VMware ESXi — ya no es gratis, la licencia de Broadcom es maravillosa, gracias.
  • XCP-ng — está bien, pero personalmente conozco Proxmox al dedillo y su comunidad está más viva.
  • Kubernetes — no, soy una sola persona, no necesito montar un avión para ir a la tienda.

Proxmox me da todo lo que necesito: virtualizaciones KVM para todo lo que requiere aislamiento (Docker, kernels específicos, diferentes distribuciones); contenedores LXC para ahorrar recursos (bases de datos, monitorización, servicios pequeños); PBS integrado para backups; ZFS de fábrica; snapshots; migración en vivo (si alguna vez tengo un segundo servidor); interfaz web a la que puedes hacer clic cuando no quieres escribir en la terminal.

Arquitectura: diagrama general

Primero mostraré el resultado, luego contaré cómo llegué a él.

La lógica es la siguiente:

  1. En el servidor dedicado vive Proxmox. Maneja la IP pública Y.Y.Y.Y en vmbr0.
  2. Dentro se crea un segundo bridge vmbr1 con una red privada 10.10.10.0/24. Es el análogo de una "LAN interna" — todas las VMs y LXCs están ahí, y salen a internet a través de NAT desde el host, sin puertos públicos de entrada.
  3. En el propio host solo corren tres cosas: Caddy (reverse proxy interno), nftables (firewall) y NetBird (agente VPN). Nada de Docker en el host, ni lógica de aplicaciones en el host. El host es sagrado, su tarea es ser hipervisor y no caerse.
  4. Las máquinas virtuales están divididas por roles: una para el foro, otra para servicios en segundo plano y bots, otra para Dokploy con aplicaciones de usuario, etc. Entre ellas hay visibilidad directa por 10.10.10.x, y hacia afuera, solo a través del Caddy del host.
  5. Los contenedores LXC son para todo lo "ligero": instancias de Postgres de diferentes versiones, Proxmox Backup Server, dashboards de monitorización. También están en vmbr1.
  6. Un nodo de salida separado ("Peer Caddy") es un pequeño VPS de otro proveedor, en una red adecuada para mi audiencia. Tiene Caddy, que termina TLS (Let's Encrypt) y hace proxy HTTP hacia el servidor dedicado a través de la mesh de NetBird. El DNS de todos los dominios públicos apunta a este VPS, no al servidor dedicado.
  7. La mesh de NetBird conecta el servidor dedicado, el nodo de salida y mi portátil (además de un par de servidores antiguos donde aún no he migrado todo). Es WireGuard sobre autenticación SSO, sin puertos abiertos hacia el exterior.

Por qué tal aislamiento a través del nodo de salida es otra historia, a la que volveré más adelante.

Instalación de Proxmox: cómo lo levanté

Pedí el servidor dedicado, y en el primer arranque instalé Proxmox a través del modo rescue/KVM (OVH tiene ambos; KVM me resulta más cómodo, ya que veo todo el proceso). Durante la instalación:

  • ZFS RAID1 en ambos NVMe (ashift=12, compression=lz4 activados desde el principio)
  • particionado por defecto, Proxmox crea rpool/ROOT, rpool/data, swap

A continuación, una limpieza básica tras la instalación. Es imprescindible, sin ella Proxmox lanza errores de suscripción y no funciona de forma óptima.

bash
1# 1. Eliminar el repositorio enterprise (no hay suscripción)
2sed -i 's/^deb/#deb/' /etc/apt/sources.list.d/pve-enterprise.list
3echo "deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription" \
4  > /etc/apt/sources.list.d/pve-no-subscription.list
5apt update && apt full-upgrade -y
6
7# 2. Eliminar la ventana de aviso "No valid subscription"
8sed -Ezi.bak "s/(Ext\.Msg\.show\(\{[^}]+?title: gettext\('No valid sub)/void\(\{ \/\/\1/g" \
9  /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
10systemctl restart pveproxy
11
12# 3. Hostname / zona horaria
13hostnamectl set-hostname dedik-waw
14timedatectl set-timezone Europe/Warsaw

Hardening de SSH

Inmediatamente. No "luego". El "luego" nunca llega.

bash
1cat > /etc/ssh/sshd_config.d/hardening.conf << 'EOF'
2Port 22222
3PermitRootLogin prohibit-password
4PasswordAuthentication no
5MaxAuthTries 3
6ClientAliveInterval 300
7ClientAliveCountMax 2
8EOF
9
10mkdir -p ~/.ssh
11echo "ssh-ed25519 AAAA... tu-clave" >> ~/.ssh/authorized_keys
12chmod 600 ~/.ssh/authorized_keys
13systemctl restart sshd

Cerré el puerto 22 en el firewall, dejando solo el no estándar. Esto no es seguridad por oscuridad, es reducir el ruido de fondo en los logs — en el puerto 22, en la primera hora, llegan 50 mil intentos de brute force; en los logs, no se ve nada. En el 22222 — silencio, y se pueden monitorizar anomalías reales.

ZFS — configuración para NVMe y 64GB RAM

ZFS por defecto quiere consumir la mitad de la RAM para ARC. Esto está bien para un servidor de archivos, pero es totalmente contraindicado para un hipervisor, donde la memoria es necesaria para las máquinas virtuales. Reducimos ARC a unos razonables 16 GB:

bash
1cat > /etc/modprobe.d/zfs.conf << 'EOF'
2options zfs zfs_arc_max=17179869184
3options zfs zfs_arc_min=4294967296
4EOF
5update-initramfs -u

Distribución de mi memoria:

  • ~16 GB — ZFS ARC
  • ~44 GB — VM/LXC
  • ~4 GB — sistema, buffers, overhead

Configuraciones útiles del propio pool:

bash
1zfs set compression=lz4 rpool
2zfs set atime=off rpool
3zfs set xattr=sa rpool
4zfs set dnodesize=auto rpool
5zfs set relatime=on rpool
6
7zfs set recordsize=64K rpool/data    # para discos de VM
8zfs set sync=standard rpool/data
9
10zpool set autotrim=on rpool          # crítico para NVMe

compression=lz4 ahorra un 20-30% de espacio casi gratis en CPU. recordsize=64K para dispositivos de bloque de VM es el mejor compromiso entre rendimiento y amplificación de escritura.

Red interna y firewall

Creé un segundo bridge para la red privada con NAT hacia afuera:

1auto vmbr1
2iface vmbr1 inet static
3    address 10.10.10.1/24
4    bridge-ports none
5    bridge-stp off
6    bridge-fd 0
7    post-up   echo 1 > /proc/sys/net/ipv4/ip_forward
8    post-up   iptables -t nat -A POSTROUTING -s 10.10.10.0/24 -o vmbr0 -j MASQUERADE
9    post-down iptables -t nat -D POSTROUTING -s 10.10.10.0/24 -o vmbr0 -j MASQUERADE

Ahora, cualquier VM/LXC en el bridge vmbr1 obtiene una dirección local 10.10.10.x y sale a internet a través del host, pero no es visible desde afuera.

nftables con policy drop. Solo está abierto lo que debe estarlo, el resto se descarta silenciosamente. Conjunto mínimo:

nft
1table inet filter {
2    chain input {
3        type filter hook input priority 0; policy drop;
4
5        iif lo accept
6        ct state established,related accept
7        ip protocol icmp accept
8        ip6 nexthdr icmpv6 accept
9
10        tcp dport 22222 accept           # SSH
11        tcp dport 8006 accept            # Interfaz Proxmox (solo desde NetBird, ver abajo)
12        tcp dport { 80, 443, 8080 } accept   # Caddy
13        udp dport 29899 accept           # NetBird
14
15        iif vmbr1 accept                 # red interna — todo permitido
16        iif wt0 accept                   # interfaz NetBird — también de confianza
17    }
18
19    chain forward {
20        type filter hook forward priority 0; policy drop;
21        iif vmbr1 oif vmbr0 accept
22        ct state established,related accept
23    }
24}

El acceso a la interfaz de Proxmox (puerto 8006) lo cerré físicamente de internet a través de CF/firewall y solo accedo a ella a través de NetBird. La interfaz pública no responde en absoluto. Lo mismo para la interfaz de PBS, dashboards y todo lo de administración — solo a través de la mesh. Desde afuera, solo están abiertos los puertos realmente públicos de los servicios.

NetBird mesh: ¿por qué no WireGuard clásico?

Usé WireGuard normal durante mucho tiempo, y estuvo bien hasta que tuve más de tres servidores. Después, gestionar las configuraciones se volvió un dolor: cada adición de peer se convertía en "actualiza la configuración en todos los nodos, no olvides allowed_ips, reinicia el demonio, comprueba que no rompiste la ruta a otro peer".

NetBird es un plano de gestión sobre WireGuard. Técnicamente, bajo el capó tienes el mismo wg, pero:

  • los nodos se encuentran a través de un servidor de señalización, NAT-traversal automático
  • autenticación a través de SSO (en mi caso, a través de Authentik), sin compartir claves
  • el acceso se gestiona en la interfaz web a través de políticas, se pueden crear grupos de máquinas y reglas entre ellas
  • proxy SSH integrado: puedes acceder a ssh user@machine.netbird.cloud y obtener autenticación SSO (con aprobación en Authentik). En el nodo ni siquiera necesitas abrir el puerto 22 hacia afuera.

Levanté el agente NetBird:

  • en el host Proxmox
  • en cada VM que debe ser visible para el administrador u otras VMs
  • en el nodo de salida
  • en mi portátil

Como resultado, accedo desde mi portátil a cualquier máquina con un solo comando, sin reenvío de puertos, sin agujeros en el firewall, sin hosts bastion adicionales. Y no hay SSH abierto en ninguna de las VMs privadas — todas solo en vmbr1 más el wt0 de la mesh.

Máquinas virtuales vs contenedores: cómo lo resolví para mí

Una regla simple que funciona casi siempre:

CuándoQué elegir
Cargas de DockerVM (Docker en LXC a veces es problemático, especialmente con overlay2)
Bases de datos sin DockerLXC (más ligero, más rápido, acceso directo al disco a través de ZFS)
Servicios del sistema (PBS, monitorización, herramientas pequeñas)LXC
Aplicaciones que necesitan su propio kernel / trucos de redVM
Nodos VPN (WG, AmneziaWG)LXC (mínimo overhead)

Al final, me quedó algo así:

Máquinas virtuales (KVM):

  • forum — Discourse en el contenedor launcher estándar. Docker dentro.
  • services — un montón de pequeñas cosas: bots webhook, bot de IA, panel de facturación, Vaultwarden, Uptime Kuma. Todo en Docker compose, por carpeta por servicio.
  • legacy — antiguo foro PHP (IPS) con PHP-FPM nativo y Caddy en la VM. No quise empaquetarlo en Docker — demasiado legacy, más fácil dejarlo como estaba.
  • dokploy — Docker Swarm + Dokploy para aplicaciones de usuario (Next.js, Postgres, Redis), despliegue a través de Git.

LXC:

  • pbs — Proxmox Backup Server (ver más abajo).
  • pg16, pg17, pg18 — tres instancias separadas de Postgres de diferentes versiones mayores. Razón: diferentes aplicaciones requieren diferentes versiones mayores, y no quiero moverlas hacia arriba y abajo cada vez.
  • dashboards — un contenedor con Homarr (página de inicio), Uptime Kuma y Beszel hub. Los tres en el mismo compose, porque lógicamente son lo mismo: observar.

Cada VM recibe una IP estática en 10.10.10.x (por rangos, así es más fácil buscar), 2-8 núcleos vCPU y 2-8 GB de RAM dependiendo de la carga. Los núcleos se pueden sobreaprovisionar de forma segura — en total he asignado más núcleos a las VMs que los físicos, y funciona perfectamente, mientras nadie sature la CPU simultáneamente.

Reverse proxy: Caddy de dos pisos

Dentro de Proxmox, instalé Caddy en el host, no en una VM. ¿Por qué? Porque debe escuchar los puertos públicos del servidor dedicado y enrutar por Host-header a la VM correcta, y para eso levantar una VM separada con reenvío de puertos es una capa extra innecesaria. Caddy es ligero, está escrito en Go, es un servicio systemd, configuración en un solo archivo.

Este Caddy del host escucha solo en el puerto 8080 HTTP y enruta a los backends por 10.10.10.x. No tiene TLS.

caddy
1:8080 {
2    @forum host forum.example.com www.forum.example.com
3    handle @forum {
4        reverse_proxy http://10.10.10.111:80 {
5            header_up X-Forwarded-Proto https
6            header_up X-Real-IP {header.X-Real-IP}
7            header_up Host {host}
8        }
9    }
10
11    @panel host panel.example.com api.panel.example.com
12    handle @panel {
13        reverse_proxy http://10.10.10.111:8081 {
14            header_up X-Forwarded-Proto https
15            header_up X-Real-IP {header.X-Real-IP}
16            header_up Host {host}
17        }
18    }
19
20    @dokploy host app1.example.com app2.example.com
21    handle @dokploy {
22        reverse_proxy http://10.10.10.114:80 {
23            header_up X-Forwarded-Proto https
24            header_up X-Real-IP {header.X-Real-IP}
25            header_up Host {host}
26        }
27    }
28}

Y la terminación TLS y los certificados — en un nodo de salida separado, en Docker, bajo el mismo principio:

caddy
1{
2    email me@example.com
3}
4
5forum.example.com {
6    reverse_proxy http://100.x.y.z:8080 {
7        header_up X-Forwarded-Proto {scheme}
8        header_up X-Forwarded-For {remote_host}
9        header_up X-Real-IP {remote_host}
10        header_up Host {host}
11    }
12}

100.x.y.z — es la dirección NetBird del servidor dedicado. La solicitud llega al nodo de salida por HTTPS, se descifra, sale a la mesh de NetBird en HTTP hacia el servidor dedicado, allí el Caddy de Proxmox coincide por Host y enruta a la VM interna. Entre el nodo de salida y el servidor dedicado es HTTP por diseño — confiamos en la mesh, no hay tráfico externo aquí.

¿Por qué un nodo de salida?

Varios motivos:

  1. Evitar bloqueos. Parte de las subredes de OVH están bloqueadas en Rusia. El nodo de salida está en un proveedor cuya IP no está bloqueada. El DNS de los dominios públicos apunta allí.
  2. Terminación TLS en un solo lugar. Los certificados solo los emite él, solo su IP participa en el challenge de Let's Encrypt. Esto simplifica todas las verificaciones DNS y permite que el servidor dedicado sea completamente invisible desde internet por los puertos 80/443.
  3. Capa adicional de aislamiento. Si alguien empieza a hacer DDoS, ataca al nodo de salida, no al servidor dedicado. En el nodo de salida tengo datos mínimos, recrearlo toma 10 minutos.
  4. Separar el frontend público del backend. Puedo actualizar Caddy o experimentar tranquilamente en el servidor dedicado, mientras que la interfaz real a internet está separada y es estable.

Una desventaja: se añaden ~5-15 ms a cada solicitud debido al salto adicional. Para la web, esto es imperceptible.

También, para algunos servicios, hay un Caddy directo en el host en 80/443 sin el nodo de salida — para aquellos dominios que no necesito redirigir a través del punto de salida (por ejemplo, endpoints de estado, monitorización, un servicio donde el DNS ya apunta al servidor dedicado). Esto funciona en paralelo: el Caddy en el host escucha tanto en :8080 (interno desde el nodo de salida) como en :80/:443 (directo desde internet), con rutas diferentes.

Migración: cómo me mudé sin lágrimas y casi sin tiempo de inactividad

Esta fue la etapa más estresante. Cinco servidores, unas treinta aplicaciones, foro de producción con usuarios activos, facturación con suscripciones activas, bases de datos que bajo ningún concepto podían perderse.

La estrategia fue migración de un servicio a la vez, en orden "del menos crítico al más crítico":

  1. Primero migraron las herramientas pequeñas y sitios estáticos (si algo fallaba, nadie lo notaría).
  2. Luego los bots y servicios en segundo plano (también soportan un minuto de inactividad).
  3. Luego las bases de datos (con restauración previamente verificada).
  4. Luego las aplicaciones que dependen de esas bases de datos.
  5. Al final, el foro principal con una última sincronización larga y cambio de DNS.

Canal de migración

Primero, simplemente levanté NetBird en los servidores antiguos. Esto resolvió dos cosas de inmediato: puedo acceder de forma segura por SSH a las redes internas, y rsync va por WireGuard a través de NetBird, sin exponer datos a internet abierta.

El comando de batalla para cada servicio era más o menos así:

bash
1# Desde el nuevo servidor dedicado, a través de la IP de NetBird del servidor antiguo:
2rsync -avzP --delete \
3  -e "ssh -p 5322" \
4  root@100.76.108.210:/var/discourse/shared/standalone/ \
5  /target/discourse/shared/standalone/

Para las bases de datos — dump y restore, no copia de archivos. Nunca copies archivos de Postgres/MySQL en caliente, es un billete de ida.

bash
1# En el antiguo
2docker exec -it shm-vsem-mysql mysqldump -u root -p shm-vsem | \
3  gzip > /tmp/shm-vsem.sql.gz
4
5# En el nuevo, a través de NetBird
6scp -P 5322 root@100.76.108.210:/tmp/shm-vsem.sql.gz /tmp/
7zcat /tmp/shm-vsem.sql.gz | docker exec -i shm-vsem-mysql mysql -u root -p shm-vsem

Para Discourse — el comando nativo discourse backup / discourse restore a través de ./launcher enter app. Dentro, él mismo compila correctamente el dump de Postgres + uploads + configuraciones, y lo restaura exactamente igual.

Cambio de DNS

Después de levantar el servicio en el nuevo lugar y verificar que funcionaba por la dirección interna, ejecuté ambas instalaciones en paralelo durante 5-15 minutos para observar la sincronización de datos, y luego cambié el DNS al nuevo servidor. Había reducido previamente el TTL de los registros a 60-300 segundos un día antes del cambio.

Para Discourse, donde los usuarios activos publican constantemente, lo hice así:

  1. Levanté todo en el nuevo lugar.
  2. Ejecuté la validación (login, publicación, carga de archivos a S3, búsqueda).
  3. En la instalación antigua, activé el read_only_mode (Discourse lo soporta de fábrica).
  4. Hice un último rsync de uploads + un último dump de la base de datos.
  5. Levanté en el nuevo, desactivé read-only.
  6. Cambié el DNS.
  7. En el antiguo no toqué nada durante otra semana — por si acaso.

El tiempo de inactividad real para los usuarios fue de unos dos minutos por servicio.

Sorpresas en el camino

  • Permisos al hacer pct restore. LXC bajo unprivileged 1 mapea los UID con un desplazamiento de 100000. Si en el servidor antiguo tienes archivos bajo UID 1000, en el nuevo estarán bajo UID 101000, y la aplicación no los verá. Se soluciona con chown después del restore, o con --unprivileged 0 si entiendes los riesgos.
  • Docker ipv6 en las VMs de OVH a veces se levantaba mal — lo solucioné deshabilitando ipv6 en el daemon de Docker ("ipv6": false).
  • Hora en las VM. Varias veces detecté discrepancias en la hora del sistema entre el host y la VM, lo que rompía TLS y las firmas. Solución: systemd-timesyncd o chrony en cada VM, y host.use-time en el agente de Proxmox.
  • Migración de Discourse entre dos versiones. Si en el servidor antiguo Discourse es más reciente que el que levantaste en el nuevo — el restore no funcionará. La versión debe coincidir o ser superior en la nueva instalación.
  • Registro de imágenes para Dokploy. Cuando transferí los metadatos de Dokploy a través del dump de dokploy-postgres, en la base de datos quedaron enlaces al antiguo registro local en la IP antigua. Las aplicaciones al intentar iniciarse accedían a 100.76.117.115:5000 y fallaban con No such image. Solución: reconstruir cada aplicación en la interfaz de Dokploy de nuevo; la compilación local coloca la imagen ya en el nuevo servidor.

Copias de seguridad: PBS dentro del mismo servidor dedicado + offsite

Aquí tuve una discusión filosófica aparte conmigo mismo: ¿hacer backups en el mismo hardware o no? La respuesta: sí y no.

Nivel 1 — Snapshots de ZFS en el propio pool. Son casi gratuitos (copy-on-write), se hacen instantáneamente, se restauran instantáneamente. Mantengo snapshots automáticos de rpool/data cada 15 minutos con retención de 24 horas, y diarios con retención de una semana. Se usa zfs-auto-snapshot. Protección contra "ay, acabo de borrar la BD de producción":

bash
1apt install zfs-auto-snapshot
2# Luego, esto se encarga solo a través de cron de crear frequent/hourly/daily/weekly/monthly

Nivel 2 — Proxmox Backup Server en un LXC separado en el mismo host. Son backups incrementales completos de VM/LXC a través de vzdump, deduplicados a nivel de chunks. PBS almacena las instantáneas en un dataset ZFS separado, las ve como un repositorio, cada VM tiene su propia cadena de incrementos.

Configuración en Proxmox: Datacenter → Backup → Add. Horario: cada día a las 4 AM, retención keep-daily=7 keep-weekly=4 keep-monthly=3. Todo esto con el ratón.

Colocar PBS en LXC en el mismo host es un compromiso. Ventaja: no se necesita hardware separado, el backup va por localhost, la velocidad es como la de un SSD local, la retención/deduplicación funciona perfectamente. Desventaja: si muere el servidor dedicado por completo, mueren también los backups. Por eso existe...

Nivel 3 — sincronización del repo de PBS a S3 offsite. PBS tiene un sync job a almacenamiento compatible con S3. Cada noche, subo las instantáneas a un bucket separado en S3 de Selectel (puede ser cualquiera: Backblaze, Wasabi, Cloudflare R2). La retención allí es de dos semanas, porque no necesito más: para el almacenamiento a largo plazo está el PBS local, y el offsite es un seguro contra "todo el centro de datos se quemó".

Nivel 4 — datos de aplicaciones por separado. Discourse por sí mismo hace sus backups y los guarda en S3 (esta es su característica nativa). Así que, incluso si pierdo PBS y todo el servidor dedicado, todavía tengo dumps consistentes de Discourse en su nube.

bash
1# Restaurar una VM completa desde PBS — literalmente un comando:
2pvesm list backup-pbs    # para ver qué hay
3qmrestore backup-pbs:backup/vzdump-qemu-201-2026_04_06-04_00_03.vma.zst 999 \
4  --storage local-zfs
5# y en un minuto tienes una copia de la VM del foro del momento de las 4 AM en VMID 999.

Esto es crucial: los backups que no has intentado restaurar, no son backups. Una vez al mes hago un restore de prueba de una VM aleatoria en un VMID vacío, compruebo que se levanta, que la aplicación funciona dentro de ella, y la elimino. Aburrido, pero una vez precisamente así encontré que uno de los crons escribía en /tmp/..., que al hacer backup se omitía, y la aplicación tras el restore requería un paso manual.

Monitorización

No me gusta juntar combinaciones como Prometheus + Grafana + Alertmanager + Loki cuando tengo 15 máquinas. Por eso me decidí por tres herramientas ligeras en un solo LXC:

  • Beszel — recopila métricas de agentes en cada VM (CPU, RAM, discos, interfaces de red, contenedores Docker). El hub vive en LXC, los agentes en cada VM/LXC a través de una unidad systemd. Beszel tiene su propia autenticación, a través de PocketBase, lo que a veces es inconveniente (ver más abajo sobre el escollo), pero en general funciona.
  • Uptime Kuma — pruebas por HTTP/HTTPS/Ping/TCP. Tengo ahí todo lo público (dominios), todos los servicios internos (a través de NetBird), y pings a todos los nodos de la mesh. Alertas — en Telegram.
  • Homarr — página de inicio con enlaces a todas las interfaces de administración. Para no tener que recordar en qué puerto está la interfaz de PBS, en cuál Dokploy, en cuál Vaultwarden. Simplemente abro Homarr y hago clic.

Los tres en el mismo Docker compose, en el mismo LXC, accesibles solo a través de NetBird.

El escollo con Beszel, para que no te pase: Beszel tiene dos tablas de usuarios en PocketBase — _superusers (para CLI/API) y users (para la interfaz web). Si restableces la contraseña a través de la CLI superuser upsert — solo restableces _superusers, pero te logueas en la UI a través de users y la contraseña no funciona. Se soluciona con una petición PATCH a /api/collections/users/records/<id> a través de la API REST.

¿Qué obtuve al final?

Después de aproximadamente tres semanas de migración, cuando los últimos servicios se asentaron, hice un balance:

ParámetroAntesAhora
Servidores / facturas5 de 3 hosters1 servidor dedicado + 1 micro-VPS
Pagos mensuales~$X~$X/2
Recursos libres"parece que es suficiente"8 vCPU y 30 GB de RAM de reserva
Backupscron de rsync a S3 en cada VPSPBS + offsite + snapshots ZFS
Restauración"bueno, un día más o menos"2-5 minutos por VM desde PBS
Acceso SSH5 puertos y claves diferentesuna mesh NetBird con SSO
Aislamiento de serviciosvertedero comúncada uno en su VM/LXC
Snapshot antes de actualizar"reza"qm snapshot 201 pre-update
Interfaz web para tareas rutinarias¿qué interfaz web?Interfaz de Proxmox

Lo principal que me dio emocionalmente — dejé de tener miedo de tocar algo. Cualquier acción peligrosa (actualización, migración, experimento) ahora empieza con un snapshot y termina con un commit o un rollback en 30 segundos. Empecé a probar cosas nuevas con más frecuencia, porque el precio del error se redujo drásticamente.

Checklist, si vas a repetir

Si te encuentras con los mismos síntomas que yo — aquí tienes un breve checklist, en qué orden tiene sentido avanzar:

  1. Calcula cuánto pagas por todas las VPS juntas, y compáralo con el precio de servidores dedicados en OVH/Hetzner/LeaseWeb. Te sorprenderás.
  2. Pide RAM ECC, si planeas usar ZFS. No escatimes.
  3. Pide al menos dos discos en espejo. Un solo disco no es opción para producción.
  4. Instala Proxmox a través de KVM/IPMI, no a través de rescue + debootstrap. Menos dolor.
  5. Inmediatamente configura claves SSH, un puerto no estándar, deshabilita contraseñas, instala fail2ban.
  6. Inmediatamente limita el ZFS ARC. Por defecto se comerá la mitad de la RAM.
  7. Crea un segundo bridge vmbr1 para la red privada con NAT. Todas las VMs/LXCs allí. Ninguna IP pública en las virtualizaciones.
  8. Levanta NetBird (o Tailscale, o Headscale) antes de empezar a migrar. Es tu canal de migración y acceso de administración.
  9. No abras la interfaz de Proxmox hacia afuera. Solo a través de la mesh VPN.
  10. Un pequeño VPS de salida en otro proveedor para terminación TLS y evadir bloqueos — no es obligatorio, pero muy útil. El precio es mínimo, los beneficios muchos.
  11. PBS en un LXC separado + sincronización offsite a S3. Los backups deben ser automáticos.
  12. Al menos una vez al mes haz un restore de prueba. De lo contrario, no tienes backups, sino archivos para autoconvencerte.
  13. Migra un servicio a la vez, en orden de criticidad creciente. No intentes "mover todo en un fin de semana".
  14. Reduce el TTL del DNS un día antes del cambio, no una hora antes.
  15. Lleva documentación para cada servicio: dónde están las configuraciones, cómo iniciarlo, cómo hacer backup, cómo restaurar. En seis meses te darás las gracias a ti mismo.

Lo que queda por hacer

Para ser completos: la migración aún no está terminada al 100%. Queda:

  • Un par de paneles Remnawave del servidor antiguo (planificado para el próximo fin de semana).
  • Migración de Caddy con plugins (es una compilación personalizada), ahora funciona temporalmente.
  • CrowdSec antiguo — lo instalo por separado desde cero, no lo migro.

Pero todo lo crítico (foros, facturación, bots, bases de datos) — ya está en el servidor dedicado, funciona de forma estable, se hace backup y se monitoriza.

Si tienes preguntas sobre pasos concretos, configuraciones o los escollos en los que tropecé con más detalle — escríbelos en los comentarios, intentaré ampliar. Especialmente me interesa escuchar cómo resolvéis tareas similares en otros hipervisores (XCP-ng, ESXi, o en Docker Swarm pelado) — quizás me estoy perdiendo algo.

Suerte con la consolidación de vuestras infraestructuras. Menos facturas, más control — vale la pena.

~17 min read · scroll to continue ↓

## discussion

$ topics --entity=article0
sign in to start or join a discussion
No discussions yet — start one to break the ice.
↑↓ nav open⌘K palettei install? help