Anatomía del sistema, interfaz web y herramientas CLI
Anatomía del sistema, interfaz web y herramientas CLI
Tienes el nodo instalado y entras por primera vez a https://<IP>:8006. La interfaz responde, el árbol de la izquierda muestra tu nodo, y a partir de ahí todo parece obvio hasta el día en que algo falla: la web deja de responder, mkdir sobre /etc/pve devuelve Read-only file system, las estadísticas de las VMs se quedan congeladas, o un script que funcionaba en la GUI se comporta distinto por CLI.
Ninguna de esas situaciones se resuelve mirando botones. Se resuelven sabiendo qué proceso hace qué, dónde escribe, de qué depende y con qué comando se interroga. Proxmox VE no es una caja negra: es un Debian 13 Trixie con una decena de demonios Perl y Rust muy bien delimitados, un filesystem FUSE replicado montado en /etc/pve, y una API REST que es la única puerta de entrada real. La interfaz web es un cliente de esa API. Los comandos qm, pct, pvesm y compañía son clientes de esa misma API. Entender eso cambia por completo cómo depuras.
En el capítulo 1 instalaste el nodo y dejaste los repositorios en orden. Este capítulo es el mapa del territorio: al terminarlo vas a poder señalar en un systemctl --failed qué se rompió, leer un fichero de /etc/pve sabiendo quién lo escribe, decidir si abres noVNC o SPICE, y descubrir cualquier endpoint de la API sin salir de la terminal.
La versión de referencia es Proxmox VE 9.2 sobre Debian 13 Trixie, con la documentación 9.2.4 compilada el 4 de agosto de 2026.
Mapa de servicios: quién hace qué en un nodo
Un nodo Proxmox VE no tiene un “servicio Proxmox”. Tiene un conjunto de unidades systemd con responsabilidades separadas y con un modelo de privilegios deliberado: lo que está expuesto a la red corre sin privilegios, y lo que necesita privilegios solo escucha en loopback.
flowchart TB
BR["Navegador o cliente de API"]
SPC["Cliente SPICE virt-viewer"]
subgraph node["Nodo Proxmox VE"]
PXY["pveproxy 8006 HTTPS - usuario www-data"]
SPX["spiceproxy 3128 - usuario www-data"]
PVD["pvedaemon 127.0.0.1:85 - usuario root"]
subgraph locales["Demonios locales"]
PST["pvestatd"]
PSC["pvescheduler"]
PFW["pve-firewall"]
CRM["pve-ha-crm y pve-ha-lrm"]
QEV["qmeventd"]
end
PCL["pve-cluster o pmxcfs - FUSE en /etc/pve"]
COR["corosync 5405-5412 UDP"]
end
BR --> PXY
SPC --> SPX
PXY -->|"operaciones privilegiadas"| PVD
PXY -->|"peticiones a otros nodos"| COR
PVD --> PCL
locales --> PCL
PCL <--> COR
La tabla con la descripción oficial de cada demonio:
| Servicio | Unidad systemd | Usuario | Puerto | Qué hace |
|---|---|---|---|---|
pveproxy | pveproxy.service | www-data | 8006/TCP HTTPS | Expone toda la API. Corre con permisos muy limitados y reenvía a pvedaemon local lo que requiere privilegios. Las peticiones dirigidas a otros nodos se reenvían automáticamente a esos nodos |
pvedaemon | pvedaemon.service | root | 127.0.0.1:85 | Expone la misma API pero solo en loopback. Corre como root y tiene permiso para todas las operaciones privilegiadas |
pvestatd | pvestatd.service | root | — | Consulta a intervalos regulares el estado de VMs, storages y contenedores, y difunde el resultado a todos los nodos del cluster |
pve-cluster (pmxcfs) | pve-cluster.service | root | — | Monta el filesystem FUSE del cluster en /etc/pve |
pvescheduler | pvescheduler.service | root | — | Lanza los jobs programados: replicación y vzdump. Para los de backup lee su configuración de /etc/pve/jobs.cfg |
pve-firewall | pve-firewall.service | root | — | Compila y aplica las reglas del firewall distribuido |
pve-ha-crm | pve-ha-crm.service | root | — | Cluster Resource Manager. Solo el nodo que gana el manager lock actúa como master |
pve-ha-lrm | pve-ha-lrm.service | root | — | Local Resource Manager: ejecuta en cada nodo lo que ordena el CRM |
spiceproxy | spiceproxy.service | www-data | 3128/TCP | Proxy HTTP que reenvía las peticiones CONNECT del cliente SPICE a la VM correcta |
qmeventd | qmeventd.service | root | socket | Escucha el socket QMP de QEMU esperando eventos SHUTDOWN. Al desconectarse el cliente ejecuta /usr/sbin/qm cleanup para limpiar tap devices y vgpus huérfanos |
corosync | corosync.service | root | 5405-5412/UDP | Motor de mensajería y quorum del cluster. Es upstream, no un componente Proxmox |
Hay además unidades auxiliares del paquete pve-manager que conviene reconocer en un log: pve-daily-update.service y su timer (refresco diario del índice de paquetes, de la base de datos de plantillas y renovación ACME, con retardo aleatorio); pve-guests.service, que en el arranque espera a que haya quorum y entonces levanta los guests con onboot; pvenetcommit.service, que aplica /etc/network/interfaces.new; pvebanner.service, que escribe la URL de la web UI en /etc/issue; y pve-firewall-commit.service y pve-sdn-commit.service.
Gotcha de diseño: pvedaemon no aparece en ninguna lista de puertos expuestos porque escucha solo en 127.0.0.1:85. Si intentas atacarlo desde fuera no hay nada que atacar; toda la superficie remota es pveproxy. Por eso subir workers o restringir IPs se hace sobre pveproxy, no sobre pvedaemon.
pmxcfs: el filesystem de cluster montado en /etc/pve
Esta es la pieza que más confunde al llegar desde otros hipervisores. /etc/pve no es un directorio del disco. Es un punto de montaje FUSE servido por pmxcfs, definido oficialmente así:
“The Proxmox Cluster file system (‘pmxcfs’) is a database-driven file system for storing configuration files, replicated in real time to all cluster nodes using corosync.”
Lo que se ve como ficheros de texto son en realidad filas de una base de datos SQLite en /var/lib/pve-cluster/config.db, con una copia completa en RAM. Esa copia en RAM impone el límite duro que hay que conocer: 128 MiB como máximo, suficiente para la configuración de varios miles de VMs, pero no para meter ahí ficheros grandes.
Las propiedades que te importan en el día a día:
Uno. Todo lo que escribas en /etc/pve en un nodo aparece en el resto del cluster en tiempo real, vía corosync. No hay que copiar nada a mano ni montar NFS para compartir configuración.
Dos. Hay comprobaciones de consistencia fuertes. Es pmxcfs quien impide que existan dos VMID iguales en el cluster.
Tres. Provee un mecanismo de bloqueo distribuido (los locks viven en /etc/pve/priv/lock/), que es lo que evita que dos nodos hagan la misma operación sobre el mismo guest.
Cuatro y la más importante: cuando el nodo pierde el quorum, pmxcfs monta /etc/pve en solo lectura. Los guests que ya estaban corriendo siguen corriendo; lo que se bloquea es toda escritura de configuración: no puedes crear, arrancar ni migrar guests, ni editar nada. Si alguna vez ves mkdir: cannot create directory ...: Read-only file system sobre /etc/pve, el problema no es el disco, es el quorum. El diagnóstico es siempre el mismo:
pvecm status # mirar la linea "Quorate:"
systemctl status pve-cluster corosync
journalctl -b -u pve-cluster -u corosync
El detalle completo de quorum, votos y QDevice está en el capítulo 11; aquí basta con saber que el síntoma es un filesystem de solo lectura.
Limitaciones POSIX deliberadas
pmxcfs no pretende ser un filesystem general. Renuncia a cosas a propósito, y conviene saberlo antes de escribir un script que falle de forma rara:
- No soporta enlaces simbólicos.
- No se puede renombrar un directorio no vacío — así se garantiza la unicidad de los VMID.
- Los permisos son por ruta y no se pueden cambiar. Todos los ficheros son de
rootcon lectura para el grupowww-data; lo que cuelga de/etc/pve/priv/es solo root. O_EXCLno es atómico (como en implementaciones antiguas de NFS), y la creación conO_TRUNCtampoco lo es, por restricción de FUSE.
Traducción práctica: no uses /etc/pve como almacén de locks de tus propios scripts, y no des por hecho que un chmod va a funcionar ahí.
Inventario de /etc/pve y los enlaces virtuales del nodo local
Estos son los ficheros que vas a abrir una y otra vez. Los agrupo por dominio.
| Ruta | Contenido |
|---|---|
storage.cfg | Definición de todos los storages del cluster |
user.cfg | Usuarios, grupos y ACL |
domains.cfg | Dominios de autenticación (realms) |
datacenter.cfg | Ajustes cluster-wide: teclado, proxy, consola por defecto, estilo de tags, texto de consentimiento en base64 |
jobs.cfg | Jobs programados de backup que ejecuta pvescheduler |
vzdump.cron | Programación de backups cluster-wide |
corosync.conf | Configuración del cluster (la copia maestra) |
status.cfg | Servidores de métricas externos |
ceph.conf | Configuración de Ceph |
firewall/cluster.fw, firewall/<NAME>.fw, firewall/<VMID>.fw | Reglas de firewall cluster-wide, por nodo y por guest |
ha/resources.cfg y ha/rules.cfg | Recursos gestionados por HA y reglas de afinidad |
ha/manager_status y ha/crm_commands | Estado de los servicios HA en JSON y operaciones en curso |
sdn/* | Configuración de Software Defined Networking |
virtual-guest/cpu-models.conf | Modelos de CPU personalizados |
nodes/<NAME>/config | Config del nodo: wakeonlan, acme, startall-onboot-delay, ballooning-target |
nodes/<NAME>/qemu-server/<VMID>.conf | Configuración de cada VM KVM |
nodes/<NAME>/lxc/<VMID>.conf | Configuración de cada contenedor LXC |
nodes/<NAME>/pve-ssl.pem y .key | Certificado y clave del nodo, firmados por la CA del cluster |
nodes/<NAME>/pveproxy-ssl.pem y .key | Certificado alternativo opcional del proxy |
pve-root-ca.pem, pve-www.key, authkey.pub | CA pública del cluster, clave de tokens CSRF y clave pública del sistema de tickets |
Y el subárbol privado, legible solo por root:
| Ruta | Contenido |
|---|---|
priv/shadow.cfg | Passwords del realm PVE |
priv/tfa.cfg | Configuración de doble factor, en Base64 |
priv/token.cfg | Secretos de los API tokens |
priv/authkey.key y priv/pve-root-ca.key | Clave privada del sistema de tickets y de la CA del cluster |
priv/authorized_keys y priv/known_hosts | Claves SSH y verificación de los miembros del cluster |
priv/lock/* | Locks distribuidos cluster-wide |
priv/ceph* | Claves y capabilities de Ceph |
priv/storage/<STORAGE-ID>.pw | Passwords de storage en texto plano |
Advertencia: priv/storage/<ID>.pw guarda las contraseñas de los backends remotos (CIFS, PBS, iSCSI con CHAP) en texto plano. Cualquier copia de /etc/pve o de config.db que saques del nodo lleva esas credenciales dentro. Trátalas como un secreto de primera categoría, sobre todo cuando prepares el material del capítulo 10.
Los enlaces virtuales al nodo local
pmxcfs no soporta symlinks, pero sí expone tres atajos “virtuales” que apuntan siempre al nodo donde estás:
| Enlace | Apunta a |
|---|---|
/etc/pve/local | nodes/<hostname-local> |
/etc/pve/qemu-server | nodes/<hostname-local>/qemu-server/ |
/etc/pve/lxc | nodes/<hostname-local>/lxc/ |
Por eso qm config 100 funciona leyendo /etc/pve/qemu-server/100.conf mientras el fichero real vive en /etc/pve/nodes/pve1/qemu-server/100.conf. Y por eso, cuando un guest “desaparece” de un nodo, casi siempre es que su .conf está bajo el directorio de otro nodo.
Ficheros de estado para depuración
Son ficheros JSON generados al vuelo, no configuración: /etc/pve/.version lleva la versión de cada fichero para detectar modificaciones, /etc/pve/.members la información de los miembros del cluster, /etc/pve/.vmlist el inventario de todas las VMs y contenedores, /etc/pve/.clusterlog los últimos 50 eventos del cluster y /etc/pve/.rrd las últimas métricas.
Y el interruptor de logging verboso de pmxcfs:
echo "1" > /etc/pve/.debug # activar
journalctl -f -u pve-cluster
echo "0" > /etc/pve/.debug # desactivar
Recuperación con pmxcfs: config.db, modo local y mover guests
Tres escenarios de rescate que hay que tener memorizados.
a) El hardware del nodo murió y quieres su configuración
La base de datos es un fichero. Se puede trasplantar:
- Copia
/var/lib/pve-cluster/config.dbdesde el host averiado al nuevo. - Para el servicio:
systemctl stop pve-cluster. - Reemplaza el fichero
config.dbcon permisos 0600. - Ajusta el hostname y
/etc/hostspara que coincidan con los del host perdido. - Reinicia y comprueba.
Para el caso contrario —quitar la configuración de cluster de un nodo— la documentación recomienda reinstalar el nodo, porque es la única forma de garantizar que se destruyen todos los secretos y claves SSH del cluster.
b) Sin quorum y necesitas escribir en /etc/pve
Este es el salvavidas. El binario pmxcfs acepta cuatro flags —-d/--debug para mensajes de depuración, -f/--foreground para no demonizar, -h/--help y, el que importa aquí, -l/--local, que ignora corosync.conf y fuerza el quorum:
systemctl stop pve-cluster
pmxcfs -l # /etc/pve montado local y escribible, sin cluster
# ... arreglar corosync.conf o lo que toque ...
killall pmxcfs
systemctl start pve-cluster
Recuerda que hay dos copias de corosync.conf en cada nodo: /etc/pve/corosync.conf, que es la maestra y vive en pmxcfs, y /etc/corosync/corosync.conf, que es la copia local que corosync lee realmente al arrancar y que PVE genera a partir de la primera. El procedimiento oficial de edición segura pasa por una copia de trabajo y por subir el config_version:
cp /etc/pve/corosync.conf /etc/pve/corosync.conf.new
# editar el .new y subir config_version
cp /etc/pve/corosync.conf /etc/pve/corosync.conf.bak
mv /etc/pve/corosync.conf.new /etc/pve/corosync.conf
c) Recuperar los guests de un nodo caído
Solo funciona si los guests usan exclusivamente storage compartido (o están gestionados por HA). Como la configuración de un guest es un fichero dentro de un directorio por nodo, “mover” un guest de nodo es literalmente mover el fichero:
mv /etc/pve/nodes/node1/qemu-server/100.conf /etc/pve/nodes/node2/qemu-server/
mv /etc/pve/nodes/node1/lxc/200.conf /etc/pve/nodes/node2/lxc/
qm start 100
pct start 200
Advertencia literal de la documentación: “Ensure that the source node is completely powered off before performing this operation”, porque de lo contrario se producen violaciones de lock. Si el nodo origen vuelve a arrancar con el guest todavía “suyo” y el storage es compartido, tienes doble arranque sobre el mismo disco y corrupción. Los guests con recursos exclusivamente locales no se recuperan así.
Configurar pveproxy: ALLOW_FROM, LISTEN_IP, cifrados y workers
Un solo fichero controla pveproxy y spiceproxy: /etc/default/pveproxy.
Control de acceso por IP, con sintaxis estilo apache2:
ALLOW_FROM="10.0.0.1-10.0.0.5,192.168.0.0/22"
DENY_FROM="all"
POLICY="allow"
all es alias de 0/0 y ::/0, y la sintaxis de direcciones es cualquiera que entienda Net::IP. La política por defecto es allow. La resolución cuando hay coincidencias es esta:
| Coincidencia | POLICY=deny | POLICY=allow |
|---|---|---|
| Solo Allow | allow | allow |
| Solo Deny | deny | deny |
| Ninguna | deny | allow |
| Allow y Deny a la vez | deny | allow |
IP de escucha. LISTEN_IP admite IPv4 (192.0.2.1), IPv6 (2001:db8:85a3::1) y link-local siempre que se indique la interfaz (fe80::c463:8cff:feb9:6a4e%vmbr0).
Advertencia oficial: “It is not recommended to set LISTEN_IP on clustered systems.” Los nodos del cluster necesitan acceso a pveproxy para comunicarse entre sí; si lo atas a una sola IP, te arriesgas a que dejen de verse.
Cifrados TLS. Estos son los valores por defecto reales, no un ejemplo inventado:
CIPHERS="ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"
CIPHERSUITES="TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256"
HONOR_CIPHER_ORDER=0
CIPHERS aplica a TLS 1.2 y anteriores; CIPHERSUITES a TLS 1.3. SSLv2 y SSLv3 están deshabilitados incondicionalmente. Para desactivar versiones concretas: DISABLE_TLS_1_2=1 o DISABLE_TLS_1_3=1.
Otros ajustes útiles del mismo fichero:
DHPARAMS="/path/to/dhparams.pem" # PEM propio; por defecto se usan los built-in skip2048
TLS_KEY_FILE="/secrets/pveproxy.key" # mover la clave privada fuera de /etc/pve
COMPRESSION=0 # desactivar gzip HTTP para mitigar BREACH
PROXY_REAL_IP_HEADER="X-Forwarded-For"
PROXY_REAL_IP_ALLOW_FROM="192.168.0.2"
MAX_WORKERS=5
Sobre los certificados: pveproxy usa /etc/pve/local/pveproxy-ssl.pem y /etc/pve/local/pveproxy-ssl.key si existen, y si no cae a /etc/pve/local/pve-ssl.pem y /etc/pve/local/pve-ssl.key. La clave privada no puede llevar passphrase. Ojo: la integración ACME incluida no respeta TLS_KEY_FILE.
Sobre los workers: el valor por defecto es 3, con rango válido de 1 a 127. El mismo ajuste existe en /etc/default/pvedaemon. Si tienes automatización intensiva contra la API (Terraform, Ansible, exporters) y ves timeouts, es el primer número que hay que subir.
Los cambios se aplican con systemctl restart pveproxy.service spiceproxy.service. Gotcha caro: un restart de pveproxy corta las consolas y shells abiertas, porque mata los workers de larga duración. Usa reload siempre que el cambio lo permita, y programa el restart en ventana de mantenimiento.
Puertos usados por Proxmox VE
Lista oficial del Admin Guide, sección 13.12. Es la que hay que llevar al firewall perimetral:
| Servicio | Puerto | Protocolo |
|---|---|---|
| Interfaz web | 8006 | TCP, HTTP/1.1 sobre TLS |
| Consola web VNC | 5900-5999 | TCP, WebSocket |
| SPICE proxy | 3128 | TCP |
sshd (acciones de cluster) | 22 | TCP |
rpcbind | 111 | UDP |
sendmail | 25 | TCP, saliente |
| Tráfico de cluster corosync | 5405-5412 | UDP |
| Live migration (memoria de VM y discos locales) | 60000-60050 | TCP |
Dos notas que ahorran horas. La primera: pvedaemon no está en la lista porque escucha solo en 127.0.0.1:85. La segunda: el wiki pve.proxmox.com/wiki/Ports está desactualizado desde mayo de 2022 y todavía muestra corosync como 5404, 5405 UDP; desde PVE 6 el rango correcto es 5405-5412 UDP. Si copias reglas de firewall de un tutorial viejo, el cluster parpadeará.
Rutas y directorios que hay que memorizar
| Ruta | Qué es |
|---|---|
/etc/pve/ | pmxcfs: todo lo replicado en el cluster |
/var/lib/pve-cluster/config.db | Base de datos SQLite de pmxcfs |
/var/lib/vz/ | Storage local por defecto, con template/iso/, template/cache/ y dump/ |
/var/log/pve/tasks/ | Logs de tareas: el Task History de la GUI |
/var/log/pveproxy/access.log | Accesos HTTP a la GUI y a la API |
/etc/default/pveproxy | Config de pveproxy y spiceproxy |
/etc/default/pvedaemon | Config de pvedaemon, incluido MAX_WORKERS |
/etc/network/interfaces | Red del host |
/etc/network/interfaces.new | Cambios de red pendientes, los aplica pvenetcommit al reiniciar |
/etc/corosync/corosync.conf | Copia local que lee corosync |
/etc/kernel/cmdline y /etc/default/grub | Línea de comandos del kernel, según se use proxmox-boot-tool o GRUB |
El storage que ya tienes tras instalar
Sin haber configurado nada, /etc/pve/storage.cfg trae esto:
dir: local
path /var/lib/vz
content iso,vztmpl,backup
# default image store on LVM based installation
lvmthin: local-lvm
thinpool data
vgname pve
content rootdir,images
# default image store on ZFS based installation
zfspool: local-zfs
pool rpool/data
sparse
content images,rootdir
Es decir: si instalaste con ext4 o xfs tienes local + local-lvm; si instalaste con ZFS tienes local + local-zfs. Y los storages de tipo fichero siempre usan el mismo layout de subdirectorios: images/<VMID>/ para discos de VM, template/iso/ para ISOs, template/cache/ para plantillas de contenedor, dump/ para backups, snippets/ para snippets y hook scripts, e import/ para OVAs y discos importables.
Una ruta real generada por el sistema tiene esta pinta: /var/lib/vz/images/100/vm-100-disk10.raw. El modelo completo de plugins de storage y el resto de backends están en el capítulo 7.
La interfaz web: cuatro regiones y nada más
La GUI está escrita en ExtJS 7.x y traducida a más de veinte idiomas. Lo relevante es que, gracias a pmxcfs, “you can connect to any node to manage the entire cluster… There is no need for a dedicated manager node.” No hay vCenter que instalar ni appliance de gestión que mantener.
flowchart TB
H["HEADER - logo version buscador y botones Documentation / Create VM / Create CT / User Menu"]
RT["RESOURCE TREE a la izquierda - Server Folder Pool y Tag View"]
CP["CONTENT PANEL al centro - configuracion y estado del objeto seleccionado"]
L["LOG PANEL abajo - tareas recientes de TODOS los nodos"]
H --> RT
H --> CP
RT --> CP
CP --> L
Header. A la izquierda, el logo, la versión actual del nodo y una barra de búsqueda que suele ser más rápida que navegar el árbol. A la derecha, cuatro botones: Documentation (abre la doc de referencia), Create VM, Create CT y el User Menu. Dentro del menú de usuario están My Settings, los atajos self-service a TFA y Password, el idioma, el tema de color y el logout.
My Settings contiene tres cosas útiles: Dashboard Storages (qué storages cuentan para el total del resumen del datacenter; si no marcas ninguno se suman todos), el botón para resetear todos los layouts de la GUI a su valor por defecto, y los ajustes de xterm.js (Font-Family, Font-Size, Letter Spacing, Line Height).
Content panel del Datacenter. Es donde vive todo lo cluster-wide: Search, Summary, Cluster (crear o unirse), Storage, Backup (los jobs son cluster-wide: da igual en qué nodo estén los guests), Replication, Permissions (usuarios, grupos, API tokens, LDAP, MS-AD y 2FA), HA, ACME, Firewall, Metric Server (InfluxDB y Graphite), Notifications, Support y Guest Resources/Hardware, esta última nueva en 9.2 para gestionar modelos de CPU personalizados. En Options están los ajustes por defecto del datacenter, incluidos Tag Style Override, User Tag Access, Registered Tags y Consent Text.
Content panel del Nodo. En la cabecera: Reboot, Shutdown, Shell (despliega noVNC, SPICE y xterm.js), Bulk Actions (Bulk Start, Bulk Shutdown, Bulk Migrate) y Help. En 9.2, todas las acciones bulk exponen el número de workers paralelos, no solo la migración; dejarlo vacío usa el nuevo valor por defecto auto. Las secciones son Summary, Notes (en Markdown), Shell, System (red, DNS, hora, syslog y panel de servicios), Updates con su subpanel Repositories, Firewall, Disks, Ceph (solo si está instalado), Replication, Task History y Subscription.
Content panel de un guest. Casi idéntico para VMs y contenedores: Summary con Notes, Console, Hardware en VMs frente a Resources, Network y DNS en contenedores, Options, Task History, Monitor (solo VMs: la interfaz interactiva QMP/HMP con el proceso KVM), Backup, Replication, Snapshots, Firewall y Permissions.
Log panel. Cada acción larga —crear una VM, hacer un backup— se ejecuta en segundo plano como una task, con su propio fichero de log. Doble click sobre la entrada abre el log y permite abortar la tarea en ejecución. El panel muestra tareas de todos los nodos en tiempo real; las terminadas se retiran para no ensuciar, pero siguen accesibles en Nodo → Task History. Las acciones muy cortas que solo difunden mensajes al cluster aparecen en el panel Cluster log. Novedad de 9.2: hay un botón Download para bajar el log de una tarea sin abrir el visor completo.
Las cuatro vistas del árbol, tags y consent banner
El árbol de recursos no es un solo árbol: es el mismo inventario agrupado de cuatro maneras.
| Vista | Agrupa por |
|---|---|
| Server View (por defecto) | Todos los tipos de objeto, agrupados por nodo |
| Folder View | Todos los tipos de objeto, agrupados por tipo |
| Pool View | VMs y contenedores, agrupados por pool |
| Tag View | VMs y contenedores, agrupados por tag |
Los tipos de objeto que aparecen son Datacenter, Node, Guest (VMs, contenedores y plantillas), Storage y Pool.
Tags. Se definen por guest y hoy solo tienen valor informativo: no cambian comportamiento, solo agrupan y colorean. Se ven en el árbol y en la línea de estado del guest, y por CLI se ponen separados por punto y coma:
qm set 100 --tags 'produccion;web;chile'
El color se deriva del texto de forma determinista, salvo que lo sobrescribas en Datacenter → Options → Tag Style Override, que también tiene equivalente CLI —pvesh set /cluster/options --tag-style color-map=example:000000:FFFFFF pinta el tag example con fondo #000000 y texto #FFFFFF—.
Quién puede poner tags se controla en Datacenter → Options → User Tag Access, con cuatro valores: free (sin restricción, el valor por defecto), list (solo tags de una lista predefinida), existing (como list más los tags ya existentes) y none (no se permiten tags). Por defecto, quien tenga VM.Config.Options sobre /vms/ID puede poner cualquier tag; un usuario con Sys.Modify sobre / siempre puede poner o quitar cualquiera. Existe además una lista de Registered Tags reservada a usuarios con Sys.Modify sobre /.
Consent banner. Si necesitas mostrar un aviso legal antes del login, se configura en Datacenter → Options → Consent Text. Si está vacío no se muestra nada. El texto se guarda como cadena base64 dentro de /etc/pve/datacenter.cfg, lo cual es útil saberlo cuando gestiones ese fichero por automatización.
Las tres consolas: noVNC, xterm.js y SPICE
No son tres nombres para lo mismo. Son tres protocolos con capacidades distintas.
| Tipo | Qué es | Cuándo usarlo |
|---|---|---|
| noVNC | Cliente VNC sobre HTML5/WebSocket embebido en el navegador. Es la consola gráfica por defecto y el reemplazo del viejo applet Java | Ver el arranque y el firmware de una VM, instalar un SO, cualquier cosa gráfica |
| xterm.js | Emulador de terminal puramente de texto en JavaScript, servido vía termproxy. Copiar y pegar nativos del navegador, scrollback y tipografía configurable desde My Settings | Shell del nodo, consola serie de una VM, pct console |
| SPICE | Protocolo remoto completo: vídeo, teclado, ratón, audio, redirección USB y carpetas compartidas. Requiere cliente externo virt-viewer: la GUI descarga un fichero .vv. Pasa por spiceproxy en el puerto 3128/TCP | Escritorios virtuales, multi-monitor, audio, pasar un pendrive al guest |
flowchart TD
A["Que necesitas hacer"] --> B{"Sistema grafico o instalacion de SO"}
B -->|"Si"| C{"Necesitas audio multimonitor o redireccion USB"}
B -->|"No"| D{"Shell del nodo o consola serie"}
C -->|"Si"| E["SPICE con virt-viewer por el puerto 3128"]
C -->|"No"| F["noVNC en el navegador"]
D -->|"Si"| G["xterm.js"]
D -->|"No"| F
El rango 5900-5999/TCP sobre WebSocket es el que usa la consola web VNC. Para que xterm.js sirva como consola serie de una VM hay que añadirle un puerto serie y usarlo como display; y con contenedores tienes además acceso directo desde la terminal del nodo, sin pasar por el navegador:
qm set <vmid> --serial0 socket --vga serial0
pct console 101 # consola del contenedor
pct enter 101 # shell dentro del contenedor
El patrón común del CLI: todo es un cliente de la misma API
Aquí está la idea que hace que el resto del curso encaje: todas las herramientas de línea de comandos son clientes de la misma API REST que usa la web UI. No hay funciones exclusivas de la GUI ni comandos que hagan magia por debajo. Lo que se puede hacer en la interfaz se puede hacer por CLI y por API, y al revés.
flowchart LR
API["API REST /api2/json servida por pvedaemon y pveproxy"]
GUI["Web UI en ExtJS"]
PSH["pvesh - acceso directo a cualquier endpoint"]
subgraph tools["Herramientas CLI por dominio"]
QM["qm - VMs KVM"]
PCT["pct - contenedores LXC"]
PVEAM["pveam - plantillas de contenedor"]
PVESM["pvesm - storage"]
PVECM["pvecm - cluster y quorum"]
PVEUM["pveum - usuarios roles ACL y tokens"]
HAM["ha-manager - alta disponibilidad"]
PVESR["pvesr - replicacion"]
PVECEPH["pveceph - Ceph"]
PVENODE["pvenode - nodo ACME y tareas"]
VZD["vzdump y qmrestore - backup"]
end
GUI --> API
PSH --> API
tools --> API
Este es el índice oficial de man pages, que vale como catálogo cerrado de lo que existe:
Interfaz de línea de comandos: ha-manager(1), pct(1), pveam(1), pveceph(1), pvecm(1), pvenode(1), pveperf(1), pvesh(1), pvesm(1), pvesr(1), pvesubscription(1), pveum(1), qm(1), qmrestore(1), vzdump(1).
Demonios de servicio: pmxcfs(8), pve-firewall(8), pve-ha-crm(8), pve-ha-lrm(8), pvedaemon(8), pveproxy(8), pvescheduler(8), pvestatd(8), qmeventd(8), spiceproxy(8).
Ficheros de configuración: cpu-models.conf(5), datacenter.cfg(5), pct.conf(5), qm.conf(5).
Gotcha documental: pvereport no tiene man page publicada — pve-docs/pvereport.1.html devuelve 404. Solo se menciona dentro del capítulo del firewall. No es que el comando no exista; es que su contrato no está documentado formalmente.
qm, pct, pveam y pvesm: los comandos del día a día
qm — máquinas virtuales KVM
# ciclo de vida
qm list # VMs de este nodo
qm create <vmid> [OPTIONS]
qm start|shutdown|stop|reboot|reset|suspend|resume|status|wait <vmid>
qm destroy <vmid>
qm unlock <vmid> # quitar un lock atascado
# configuracion y diagnostico
qm config <vmid>
qm set <vmid> [OPTIONS]
qm pending <vmid> # cambios pendientes de reinicio
qm showcmd <vmid> --pretty # la linea de comandos QEMU real
qm monitor <vmid> # monitor QEMU interactivo
# discos, snapshots y clonado
qm disk import <vmid> <source> <storage>
qm disk move <vmid> <disk> [<storage>]
qm disk resize <vmid> <disk> <size>
qm disk rescan
qm snapshot|listsnapshot|rollback|delsnapshot <vmid> [<snapname>]
qm template <vmid>
qm clone <vmid> <newid>
qm shutdown es un apagado ACPI ordenado y qm stop un corte duro. La sintaxis moderna de discos es qm disk <verbo>; los antiguos qm importdisk, qm move_disk y qm resize siguen existiendo como alias deprecados.
El detalle de cada opción de hardware está en el capítulo 3, y las plantillas y cloud-init en el capítulo 5.
pct — contenedores LXC
pct list
pct create <vmid> <ostemplate> [OPTIONS]
pct start|shutdown|stop|reboot|suspend|resume <vmid>
pct status <vmid>
pct config <vmid>
pct set <vmid> [OPTIONS]
pct console|enter <vmid>
pct exec <vmid> [<extra-args>]
pct push <vmid> <file> <destination>
pct pull <vmid> <path> <destination>
pct df|fsck|fstrim|mount|unmount <vmid>
pct resize <vmid> <disk> <size>
pct snapshot|listsnapshot|rollback|delsnapshot <vmid> [<snapname>]
pct template <vmid>
pct clone <vmid> <newid>
pct restore <vmid> <ostemplate>
pct migrate <vmid> <target>
pct cpusets # ver el reparto de CPUs entre contenedores
Todo el modelo de contenedores, con unprivileged, features y mount points, está en el capítulo 6.
pveam — plantillas de contenedor
pveam update # refrescar la base de datos de plantillas
pveam available --section system
pveam download local debian-13-standard_13.0-1_amd64.tar.zst
pveam list local
pveam remove <template_path>
Las plantillas caen en /var/lib/vz/template/cache/ dentro del storage local, y la base de datos se refresca a diario por pve-daily-update.
pvesm — storage
pvesm status # estado de todos los storages
pvesm list <storage> [--vmid <VMID>] [--content <tipo>]
pvesm add <type> <storage> [OPTIONS]
pvesm set <storage> [OPTIONS]
pvesm remove <storage>
pvesm alloc <storage> <vmid> <filename> <size>
pvesm free <volume>
pvesm path <volume> # ruta real de un volid
pvesm extractconfig <volume> # sacar la config guardada en un backup
pvesm prune-backups <storage>
pvesm scan nfs|cifs|iscsi|lvm|lvmthin|zfs|pbs <args>
# ejemplos documentados de alta de backends
pvesm add dir <STORAGE_ID> --path <PATH>
pvesm add nfs <STORAGE_ID> --path <PATH> --server <SERVER> --export <EXPORT>
pvesm add lvm <STORAGE_ID> --vgname <VGNAME>
pvesm add iscsi <STORAGE_ID> --portal <HOST[:PORT]> --target <TARGET>
pvesm set <STORAGE_ID> --content iso
pvesm path es el comando que más resuelve discusiones: te dice exactamente en qué fichero o dispositivo de bloque vive un volumen.
pvesh como explorador autodocumentado de la API
pvesh es el atajo directo a la API:
“The Proxmox VE management tool (pvesh) allows to directly invoke API function, without using the REST/HTTPS server. Only root is allowed to do that.”
Los verbos mapean uno a uno con HTTP:
pvesh get <api_path> [FORMAT_OPTIONS] # GET
pvesh set <api_path> [FORMAT_OPTIONS] # PUT
pvesh create <api_path> [FORMAT_OPTIONS] # POST
pvesh delete <api_path> [FORMAT_OPTIONS] # DELETE
pvesh ls <api_path> [FORMAT_OPTIONS] # listar hijos
pvesh usage <api_path> [--command <create|delete|get|set>] [--returns] [--verbose]
Opciones de formato:
| Opción | Default | Efecto |
|---|---|---|
--output-format <json|json-pretty|text|yaml> | text | Formato de salida |
--human-readable <boolean> | 1 | Renderizado humano |
--noborder <boolean> | 0 | Sin bordes, solo en text |
--noheader <boolean> | 0 | Sin cabeceras de columna, solo en text |
--quiet | — | Suprimir la salida |
Con text, pvesh humaniza los valores: los epoch Unix se convierten a ISO 8601, las duraciones a 1d 5h, los bytes a B/KiB/MiB/GiB/TiB/PiB y las fracciones a porcentaje (1.0 pasa a 100%). Para scripting, usa siempre --output-format json.
El patrón de descubrimiento es lo que convierte a pvesh en documentación viva: pvesh ls / para bajar por el árbol y pvesh usage <ruta> -v para leer los parámetros exactos.
pvesh ls /
pvesh ls /nodes
pvesh ls /nodes/pve1/qemu
pvesh usage /cluster/options -v
pvesh get /nodes
pvesh get /nodes/pve1/status
pvesh get /cluster/resources --type vm --output-format json-pretty
pvesh get /cluster/tasks
pvesh set /cluster/options -console html5
El complemento web es el API Viewer oficial en https://pve.proxmox.com/pve-docs/api-viewer/, que muestra el mismo esquema JSON Schema con el que se generan tanto la GUI como las man pages. Sobre esa base se construye toda la automatización del capítulo 14.
pvenode, pvereport, pveperf y proxmox-boot-tool
pvenode
Gestiona lo que es propio del nodo: certificados, ACME, configuración local, tareas y acciones masivas.
pvenode cert set certificate.crt certificate.key -force
pvenode acme account register default [email protected]
pvenode config set --acme domains=example.invalid
pvenode acme cert order
pvenode acme cert renew
pvenode config set -wakeonlan XX:XX:XX:XX:XX:XX
pvenode wakeonlan <node>
pvenode task list --errors --vmid 100
pvenode task log UPID:pve1:00010D94:001CA6EA:6124E1B9:vzdump:100:root@pam:
pvenode task status <upid>
pvenode startall --vms 100,101,102 --force
pvenode stopall
pvenode migrateall pve2 --vms 100,101,102 --with-local-disks
pvenode config set --startall-onboot-delay 10
pvenode config set --ballooning-target 90
Los tres últimos son los que más se usan en mantenimiento: startall/stopall respetan el onboot y el orden de arranque, y migrateall vacía un nodo antes de reiniciarlo.
Ese --ballooning-target es el objetivo del auto-ballooning que ejecuta pvestatd, y se explica en detalle en el capítulo 3.
pvereport
Un solo comando que vuelca a stdout el informe completo del sistema. Equivalente en la GUI: Nodo → Subscription → System Report.
pvereport
pvereport > /root/reporte-$(hostname)-$(date +%F).txt
Lo que recolecta cubre prácticamente todo lo que preguntarías en un ticket: pveversion -v, /etc/hosts, pvesubscription get y los ficheros de /etc/apt/; storage.cfg, pvesm status, df --human -T y proxmox-boot-tool status; qm list, pct list y todos los .conf de guests; ip -details address, /etc/network/interfaces y la configuración de SDN; los .fw del firewall más iptables-save; pvecm nodes, pvecm status, corosync.conf, ha-manager status y datacenter.cfg; y el detalle de bloques y volúmenes con lsblk, pvs, lvs, vgs, zpool status, ceph status y multipath -ll.
Dos recomendaciones operativas. La primera: ejecuta pvereport antes de cualquier upgrade mayor y guarda la salida; es la línea base contra la que comparar si algo cambia. La segunda, en negrita porque cuesta caro: el informe contiene información sensible —IPs, nombres de usuario en storage.cfg, topología de red completa—. Revísalo antes de pegarlo en un foro.
pveperf
Benchmark rápido del nodo, no una prueba de aceptación de I/O. La propia documentación lo describe como “just a very quick and general benchmark”.
pveperf [PATH] # PATH por defecto: /
Umbrales de referencia de la man page:
| Métrica | Referencia oficial |
|---|---|
REGEX/SECOND | > 300000 |
BUFFERED READS | discos modernos >= 40 MB/s |
AVERAGE SEEK TIME | SCSI rápidos < 8 ms; IDE/SATA entre 15 y 20 ms |
FSYNCS/SECOND | > 200 |
El valor que más información da es FSYNCS/SECOND: si está por los suelos, tienes un disco sin caché protegida o un SSD de consumo, y todo lo demás en el nodo irá mal.
proxmox-boot-tool
En instalaciones con / sobre ZFS o BTRFS, el arranque no lo gestiona GRUB directamente sino esta herramienta, que mantiene sincronizadas las particiones EFI:
proxmox-boot-tool status
proxmox-boot-tool format /dev/sda2
proxmox-boot-tool init /dev/sda2 # systemd-boot
proxmox-boot-tool init /dev/sda2 grub # forzar GRUB
proxmox-boot-tool refresh
proxmox-boot-tool kernel list
proxmox-boot-tool kernel pin <version>
proxmox-boot-tool kernel unpin
Gotcha al reemplazar un disco: si proxmox-boot-tool status indica que los discos actuales usan GRUB, hay que pasar grub como modo a init, o el disco nuevo quedará inicializado con systemd-boot y el arranque será inconsistente.
Diagnóstico: journalctl, task history y comandos de cabecera
En Debian 13 todo va a journald por defecto: /var/log/syslog solo existe si instalaste rsyslog. Cada unidad de la tabla de servicios tiene su propio hilo de log, y ese es el primer sitio donde mirar: corosync para membresía, links y retransmits; pve-cluster para quorum y montaje de /etc/pve; pveproxy para peticiones HTTP; pvestatd para la recolección de métricas; pvescheduler para jobs; pve-ha-crm y pve-ha-lrm para decisiones de HA, lock y watchdog.
journalctl -b -u corosync -u pve-cluster
journalctl -f -u pveproxy
journalctl -b -p err # solo errores de este arranque
journalctl -k # kernel: watchdog, ZFS, vfio
journalctl --since "2026-08-09 03:00" --until "2026-08-09 05:00"
journalctl -u pveproxy --grep "authentication failure"
journalctl --disk-usage
journalctl --vacuum-time=14d
Las tareas de Proxmox no van a journald. Tienen su propio almacén, que es lo que alimenta el Task History de la GUI:
ls /var/log/pve/tasks/
cat /var/log/pve/tasks/index
pvesh get /nodes/localhost/tasks --limit 50
pvesh get /cluster/tasks
Y el bloque de comandos de cabecera, el que ejecutas cuando llegas a un nodo que no conoces y algo va mal:
# versiones y servicios
pveversion -v
systemctl status pve-cluster pveproxy pvedaemon pvestatd pvescheduler pve-firewall corosync
systemctl --failed
# cluster
pvecm status; pvecm nodes; corosync-cfgtool -s; ha-manager status
# storage
pvesm status; df -h; zpool status; lvs; vgs; pvs
# red
ip -br a; ip -br link; bridge link
# firewall
pve-firewall status
pve-firewall localnet
pve-firewall simulate --from outside --to host --dport 8006
pve-firewall simulate acepta --from y --to con los valores host, outside, vm<N>, ct<N> o <grupo>/<iface>; el default es outside para el origen y host para el destino. Es la forma de responder “¿por qué no entra el puerto 8006?” sin tocar reglas, y se trabaja a fondo en el capítulo 10.
Errores comunes y diagnóstico
| Síntoma | Causa probable | Diagnóstico y solución |
|---|---|---|
/etc/pve en solo lectura, Read-only file system | Pérdida de quorum | pvecm status y mirar Quorate:. Recuperar nodos; en emergencia pvecm expected 1 en el nodo superviviente |
pvecm status no responde o pmxcfs no monta | pve-cluster caído o corosync.conf inválido | systemctl status pve-cluster corosync; journalctl -u pve-cluster -u corosync. Último recurso: parar el servicio y arrancar pmxcfs -l |
| La web no responde en :8006 | pveproxy caído | systemctl status pveproxy; journalctl -u pveproxy -n 100 |
| Timeouts con automatización intensiva | Solo 3 workers por defecto | Subir MAX_WORKERS en /etc/default/pveproxy y /etc/default/pvedaemon, rango 1-127 |
| Se pierden las consolas abiertas | Se hizo restart de pveproxy en vez de reload | Programar el restart en ventana de mantenimiento |
| Solo conectan clientes IPv6, o ninguno | net.ipv6.bindv6only=1 | Quitar el sysctl o poner LISTEN_IP="0.0.0.0" |
| Los nodos dejan de verse tras tocar la config del proxy | Se puso LISTEN_IP en un sistema en cluster | Quitar LISTEN_IP: no está recomendado en clusters |
| El navegador rechaza el certificado | Certificado autofirmado por la CA del cluster | Importar /etc/pve/pve-root-ca.pem o configurar ACME con pvenode acme |
| Los logs muestran la IP del proxy inverso | Falta el header de IP real | PROXY_REAL_IP_HEADER="X-Forwarded-For" más PROXY_REAL_IP_ALLOW_FROM |
| Estadísticas de VMs y storages congeladas | pvestatd caído | systemctl restart pvestatd |
| Los backups programados no se ejecutan | pvescheduler caído o jobs.cfg erróneo | systemctl status pvescheduler; revisar /etc/pve/jobs.cfg |
| Interfaces tap huérfanas tras apagar una VM | qmeventd caído | systemctl status qmeventd; ejecuta /usr/sbin/qm cleanup al detectar SHUTDOWN |
| Los cambios de red no se aplican | Siguen en /etc/network/interfaces.new | Botón Apply Configuration en la GUI, o reiniciar para que los aplique pvenetcommit |
| Un guest “desaparece” tras cambiar de nodo | Su .conf vive bajo /etc/pve/nodes/<NODO>/ | Mover el fichero al nodo correcto, solo con el nodo origen totalmente apagado |
| Un nodo eliminado sigue apareciendo | Falta borrar su directorio | rm -rf /etc/pve/nodes/<NODENAME> después de pvecm delnode |
| pmxcfs se queda sin espacio | Límite de 128 MiB de la copia en RAM | Buscar ficheros grandes en /etc/pve: snippets, notas en Markdown, dumps pegados por error |
| VMID duplicados | Un mv manual mal hecho saltándose las comprobaciones | Revisar /etc/pve/.vmlist |
| Un nodo aparece gris en la GUI pero responde por SSH | Certificados desincronizados o pvestatd caído | pvecm updatecerts --force && systemctl restart pvedaemon pveproxy pvestatd |
Lo que queda operativo
Ya puedes nombrar el proceso responsable de cada síntoma —web caída es pveproxy, estadísticas congeladas es pvestatd, backups que no arrancan es pvescheduler, /etc/pve de solo lectura es pve-cluster sin quorum—, leer y editar cualquier fichero de /etc/pve sabiendo que se replica solo, rescatar un nodo sin quorum con pmxcfs -l, mover la configuración de un guest a otro nodo con el origen apagado, endurecer pveproxy con ALLOW_FROM y workers, elegir la consola correcta a la primera, descubrir cualquier endpoint con pvesh ls y pvesh usage -v, y generar un pvereport antes de tocar nada importante.
Con el mapa del sistema en la cabeza, lo siguiente es empezar a poner carga encima. En el capítulo 3 desarmamos una máquina virtual KVM pieza a pieza: machine type, firmware, modelo de CPU, memoria con ballooning, buses de disco y modos de caché, red, passthrough PCIe y el formato completo de /etc/pve/qemu-server/<VMID>.conf — incluidas las divergencias entre los valores por defecto del backend y los que pone la GUI, que son las que rompen los scripts.