Harper
PLAN DE FORMACIÓN Departamento de Infraestructura
Tutorial · GLPI
Tutorial avanzado · ITSM / Inventario

GLPI 11 sobre Proxmox — mesa de ayuda + inventario en producción

Guía completa para desplegar GLPI 11 (última rama estable) en una VM Ubuntu 24.04 dentro del Proxmox del lab. Stack: Nginx + PHP 8.3 (FPM) + MariaDB. Al terminar tendrás la GUI de GLPI funcionando bajo HTTPS con config/ y files/ fuera del webroot, crons operativos, GLPI Agent inventariando el propio server, backup nocturno y ficha de handoff. Este es el material base para instalaciones a cliente.

GLPI11.0.8
SOUbuntu 24.04 LTS
WebNginx 1.24
PHP8.3 (PHP-FPM)
DBMariaDB 10.11
Recursos VM2 vCPU · 4 GB · 40 GB
Fases13
Duración~2–3 horas
Arquitectura del stack
flowchart LR subgraph PVE ["Proxmox del lab"] direction TB subgraph VM ["VM 192.168.0.231 - Ubuntu 24.04"] direction TB NGX["nginx :80"] FPM["php8.3-fpm
unix socket"] APP["/var/www/glpi/public"] DB[("MariaDB 10.11
127.0.0.1:3306")] CFG["/etc/glpi
fuera del webroot"] DATA["/var/lib/glpi
files, cache, uploads"] NGX -->|fastcgi| FPM FPM --> APP FPM -.-> CFG FPM -.-> DATA FPM --> DB end end USER["Operador / tecnico
del cliente"] -->|"HTTPS 443
via NPM o Certbot"| NGX AGENT["GLPI Agent
Windows / Linux"] -->|"inventory push"| NGX
Tu progreso 0 / 13 · 0%
ⓘ Contexto arquitectónico — leer antes de la fase 1

GLPI 11 introdujo un cambio importante: la aplicación se sirve desde el directorio public/, no desde la raíz del paquete. Esto permite que solo public/index.php quede expuesto por Nginx — el resto (config, archivos subidos, cache, marketplace) queda por debajo del webroot pero accesible al PHP-worker. Es un cambio de seguridad heredado de los frameworks modernos (Laravel, Symfony), y romperlo es la fuente #1 de bugs. Todo el tutorial respeta este layout.

FQDN de la VM
glpi.lab.harper.local
IP sugerida
192.168.0.231
Ruta app
/var/www/glpi
Solo public/ expuesto por Nginx
Data + config
/var/lib/glpi · /etc/glpi
Mudados fuera del webroot en la fase 6
DB
mariadb · db=glpi · user=glpi
Puertos abiertos (LAN)
22 · 80 · 443
◆ Por qué Nginx + PHP-FPM (no Apache)

La doc oficial de GLPI da ejemplos con Apache, pero en Harper usamos Nginx por tres razones concretas:

  • Consistencia del stack: Zabbix ya corre sobre Nginx (ver el tutorial Zabbix) y NPM (Nginx Proxy Manager) va al frente de todos los servicios en muchos clientes. Tener Apache aquí y Nginx allá multiplica los patrones que hay que memorizar y romper.
  • Performance con PHP-FPM: Nginx + FPM maneja mejor la concurrencia y usa menos memoria que Apache + mod_php en el mismo hardware. En clientes con > 100 técnicos concurrentes la diferencia es notoria.
  • Proxy reverso más limpio: si NPM (o cualquier front) va delante, Nginx interno se lleva bien con headers X-Forwarded-* por defecto.

La única "desventaja" — reescribir rewrite rules — es un párrafo de config una sola vez, copiable desde este tutorial.

01

Clonar la VM desde la plantilla cloud-init en Proxmox

Meta: VM Ubuntu 24.04 arriba con IP 192.168.0.231, hostname glpi-lab, llave SSH del usuario, updates aplicados.

Repetimos el mismo procedimiento del tutorial de Zabbix, cambiando VMID y nombre. Convención Harper: rango 200–299 para servicios core; GLPI queda como 231.

Ejecutar enhost: proxmox (SSH root@pve)
qm clone 9000 231 \
  --name glpi-lab \
  --description "GLPI 11 · Ubuntu 24.04 · lab Harper" \
  --full 0 --pool lab

qm set 231 --cores 2 --sockets 1 --cpu host --memory 4096 --balloon 0
qm resize 231 scsi0 40G

qm set 231 \
  --ciuser rbatista \
  --sshkeys ~/.ssh/dojo_authorized_keys \
  --ipconfig0 ip=192.168.0.231/24,gw=192.168.0.1 \
  --nameserver "192.168.0.1 1.1.1.1" \
  --searchdomain lab.harper.local

qm cloudinit update 231
qm set 231 --onboot 1
qm start 231

until qm agent 231 ping 2>/dev/null; do echo "esperando cloud-init..."; sleep 5; done
qm agent 231 network-get-interfaces | jq -r '.[] | select(.name!="lo").["ip-addresses"][]?."ip-address"'
✓ Verificación
Ejecutar enhost: local
ssh rbatista@192.168.0.231 "hostnamectl; lsb_release -d"
02

Post-instalación: hostname, updates, hardening SSH, firewall base

Meta: la VM cumple con el estándar Harper para servicios de producción.

Ejecutar enhost: vm (ssh a glpi-lab)
sudo hostnamectl set-hostname glpi-lab.lab.harper.local
sudo timedatectl set-timezone America/Santo_Domingo

sudo apt update
sudo DEBIAN_FRONTEND=noninteractive apt -y full-upgrade
sudo apt -y install curl wget gnupg2 lsb-release ca-certificates \
  ufw fail2ban unattended-upgrades chrony jq htop unzip

# Sudo NOPASSWD para el usuario base.
echo "rbatista ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/90-rbatista
sudo chmod 440 /etc/sudoers.d/90-rbatista

# Hardening SSH.
sudo tee /etc/ssh/sshd_config.d/50-harper.conf >/dev/null <<'EOF'
PasswordAuthentication no
PermitRootLogin no
KbdInteractiveAuthentication no
MaxAuthTries 3
LoginGraceTime 30
X11Forwarding no
ClientAliveInterval 300
ClientAliveCountMax 2
EOF
sudo sshd -t && sudo systemctl reload ssh

# UFW: solo SSH por ahora.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw --force enable

[ -f /var/run/reboot-required ] && sudo reboot
✓ Verificación

ssh con llave funciona; SSH con password rechazado; UFW muestra solo 22/tcp abierto.

03

Instalar Nginx 1.24 + PHP 8.3 (PHP-FPM) con todas las extensiones que GLPI 11 exige

Meta: Nginx arriba escuchando en 80, php8.3-fpm corriendo su socket, y PHP 8.3 con las 15 extensiones requeridas por GLPI 11 activas.

3.1 Instalar Nginx + PHP-FPM + extensiones obligatorias

GLPI 11 exige PHP ≥ 8.2 (usamos 8.3 que viene por defecto en Ubuntu 24.04) y una lista específica de extensiones. Si falta una, el instalador se cuelga en el pre-check. Instalamos también apcu como cache de usuario (recomendado por GLPI para acelerar la GUI).

Ejecutar enhost: vm
sudo apt update
sudo apt -y install nginx \
  php8.3-fpm php8.3-cli php8.3-common \
  php8.3-mysql php8.3-curl php8.3-gd php8.3-intl \
  php8.3-mbstring php8.3-xml php8.3-zip php8.3-bcmath \
  php8.3-ldap php8.3-imap php8.3-bz2 php8.3-opcache \
  php8.3-apcu

# Verificar versión y extensiones críticas.
php -v
php -m | grep -E 'bcmath|mbstring|intl|gd|zip|curl|xml|ldap|mysqli|openssl|opcache|apcu'
PHP 8.3.6 (cli) (built: Apr 15 2026 20:13:20) (NTS) Copyright (c) The PHP Group Zend Engine v4.3.6, Copyright (c) Zend Technologies with Zend OPcache v8.3.6, Copyright (c), by Zend Technologies apcu bcmath curl gd intl ldap mbstring mysqli openssl opcache xml zip
⚠ Si Nginx no arranca porque el puerto 80 está ocupado

Ubuntu 24.04 no viene con Apache por defecto, pero si esta VM se reutilizó de otro proyecto puede tener apache2 tomando el 80. Confirma: sudo ss -ltnp | grep ':80\b'. Si es Apache, quítalo con sudo systemctl disable --now apache2 && sudo apt -y purge apache2* y reintenta sudo systemctl restart nginx.

3.2 Ajustar php.ini de FPM (upload, timezone, memory)

En Nginx + PHP-FPM la config de PHP para requests HTTP vive en /etc/php/8.3/fpm/php.ini (no en apache2/ como en el mundo mod_php). El binario CLI usa /etc/php/8.3/cli/php.ini para los crons. Ajustamos los dos.

Ejecutar enhost: vm
# --- FPM (requests HTTP a través de Nginx) ---
sudo sed -i -E \
  -e 's|^;?\s*date.timezone\s*=.*|date.timezone = America/Santo_Domingo|' \
  -e 's|^;?\s*upload_max_filesize\s*=.*|upload_max_filesize = 20M|' \
  -e 's|^;?\s*post_max_size\s*=.*|post_max_size = 20M|' \
  -e 's|^;?\s*max_execution_time\s*=.*|max_execution_time = 600|' \
  -e 's|^;?\s*max_input_vars\s*=.*|max_input_vars = 10000|' \
  -e 's|^;?\s*memory_limit\s*=.*|memory_limit = 512M|' \
  -e 's|^;?\s*session.cookie_httponly\s*=.*|session.cookie_httponly = 1|' \
  -e 's|^;?\s*session.cookie_secure\s*=.*|session.cookie_secure = 0|' \
  /etc/php/8.3/fpm/php.ini

# --- CLI (usado por los crons de GLPI) ---
sudo sed -i -E \
  -e 's|^;?\s*date.timezone\s*=.*|date.timezone = America/Santo_Domingo|' \
  -e 's|^;?\s*memory_limit\s*=.*|memory_limit = 512M|' \
  /etc/php/8.3/cli/php.ini

sudo systemctl restart php8.3-fpm
sudo systemctl enable  php8.3-fpm
sudo systemctl status  php8.3-fpm --no-pager | head -8

3.3 Verificar el socket de FPM

Nginx habla con PHP-FPM por un socket UNIX. Ubuntu 24.04 lo crea en /run/php/php8.3-fpm.sock, con owner www-data:www-data. Confirmar es útil porque el vhost Nginx (fase 7) apunta a esa ruta exacta — si la versión de PHP cambia, la ruta cambia y el vhost falla con 502.

Ejecutar enhost: vm
ls -la /run/php/php8.3-fpm.sock
sudo systemctl is-active nginx php8.3-fpm
srw-rw---- 1 www-data www-data 0 Jul 1 12:34 /run/php/php8.3-fpm.sock active active
💡 session.cookie_secure = 0 por ahora

Lo dejamos en 0 hasta que activemos HTTPS (fase 10). Si lo pusiéramos en 1 antes, el login del wizard no funcionaría (Nginx aún está en HTTP). Al terminar TLS, cambiaremos a 1.

✓ Verificación de la fase

curl -sI http://192.168.0.231/ debe devolver el "Welcome to nginx!" con HTTP/1.1 200 OK. Todavía no configuramos GLPI — eso viene en la fase 7.

04

Instalar MariaDB 10.11, cargar timezone tables, crear DB y usuario

Meta: MariaDB corre, escucha solo en localhost, tiene las tablas de timezone cargadas (requisito GLPI) y hay una DB glpi con usuario glpi.

4.1 Instalar MariaDB

Ubuntu 24.04 empaqueta MariaDB 10.11 LTS — cumple el requisito mínimo de GLPI 11 (10.6+). Es la versión que instalaremos.

Ejecutar enhost: vm
sudo apt -y install mariadb-server mariadb-client

sudo systemctl enable --now mariadb
mariadb --version
mariadb Ver 15.1 Distrib 10.11.14-MariaDB, for debian-linux-gnu (x86_64) using EditLine wrapper

4.2 Hardening (mariadb-secure-installation en modo idempotente)

Ejecutar enhost: vm
# Lo hacemos por SQL directo — es idempotente y no interactivo.
sudo mariadb <<'EOF'
DELETE FROM mysql.user WHERE User='';
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost','127.0.0.1','::1');
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';
FLUSH PRIVILEGES;
EOF

4.3 Cargar las tablas de timezone (crítico en GLPI 11)

GLPI 11 exige que MariaDB tenga las tablas mysql.time_zone* pobladas — si no, el instalador falla con un warning permanente y las fechas quedan mal en reportes. Es un típico gotcha.

Ejecutar enhost: vm
sudo mysql_tzinfo_to_sql /usr/share/zoneinfo | sudo mariadb mysql

# Permitir al usuario glpi consultar las tablas de timezone.
sudo mariadb <<'EOF'
GRANT SELECT ON mysql.time_zone_name TO 'glpi'@'localhost';
FLUSH PRIVILEGES;
EOF

# Verificar (debe haber ~600 filas).
sudo mariadb -e "SELECT COUNT(*) AS zones FROM mysql.time_zone_name;"
+-------+ | zones | +-------+ | 602 | +-------+

4.4 Crear la DB de GLPI y el usuario dedicado

Ejecutar enhost: vm
# Generar password fuerte y guardarlo (solo root).
GLPI_DB_PASS="$(openssl rand -base64 32 | tr -d '=+/' | cut -c1-32)"
echo "$GLPI_DB_PASS" | sudo tee /root/.glpi-db-pass >/dev/null
sudo chmod 600 /root/.glpi-db-pass

sudo mariadb <<EOF
CREATE DATABASE glpi
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;
CREATE USER 'glpi'@'localhost' IDENTIFIED BY '$(sudo cat /root/.glpi-db-pass)';
GRANT ALL PRIVILEGES ON glpi.* TO 'glpi'@'localhost';
GRANT SELECT ON mysql.time_zone_name TO 'glpi'@'localhost';
FLUSH PRIVILEGES;
EOF

# Test de conexión.
mariadb -u glpi -p"$(sudo cat /root/.glpi-db-pass)" -e "SHOW DATABASES;"

4.5 Config mínima de MariaDB

Ejecutar enhost: vm
sudo tee /etc/mysql/mariadb.conf.d/60-harper-glpi.cnf >/dev/null <<'EOF'
[mysqld]
# --- Harper baseline para GLPI 11 (VM 4 GB RAM) ---
bind-address              = 127.0.0.1
innodb_buffer_pool_size   = 1G
innodb_log_file_size      = 128M
innodb_flush_log_at_trx_commit = 2
character-set-server      = utf8mb4
collation-server          = utf8mb4_unicode_ci
default-time-zone         = '-04:00'
max_allowed_packet        = 64M
EOF

sudo systemctl restart mariadb
sudo ss -ltnp | grep 3306
LISTEN 0 80 127.0.0.1:3306 0.0.0.0:* users:(("mariadbd",pid=1234,fd=25))
✓ Verificación

MariaDB escucha SOLO en 127.0.0.1; usuario glpi puede conectarse; hay ≥ 600 zonas en mysql.time_zone_name.

05

Descargar GLPI 11.0.8, verificar checksum y desplegar en /var/www

Meta: código de GLPI 11.0.8 en /var/www/glpi/, permisos correctos.

5.1 Descargar el tarball oficial

GLPI publica en GitHub Releases. Descargamos el tarball estable, no el fuente sin dependencias (necesitamos vendor/ pre-instalado).

Ejecutar enhost: vm
GLPI_VERSION="11.0.8"
cd /tmp
wget "https://github.com/glpi-project/glpi/releases/download/${GLPI_VERSION}/glpi-${GLPI_VERSION}.tgz"

# Verificar tamaño mínimo (~40 MB — si es mucho menor, algo se cortó).
ls -lh "glpi-${GLPI_VERSION}.tgz"
💡 Checksum

GLPI publica hashes SHA-256 en la página de releases. Antes de desplegar en producción, verifica manualmente: sha256sum glpi-11.0.8.tgz y compara con la página oficial.

5.2 Extraer al webroot

Ejecutar enhost: vm
sudo tar xzf /tmp/glpi-${GLPI_VERSION}.tgz -C /var/www/
sudo chown -R www-data:www-data /var/www/glpi
ls /var/www/glpi/ | head -10
ajax CHANGELOG.md config <- LO MOVEREMOS FUERA DEL WEBROOT EN LA FASE 6 COPYING.txt css dependencies_injection.php files <- LO MOVEREMOS FUERA DEL WEBROOT EN LA FASE 6 front inc INSTALL.md public <- SOLO ESTO QUEDA EXPUESTO POR NGINX
06

Mover config/ y files/ fuera del webroot (seguridad)

Meta: layout seguro con datos y configuración en /var/lib/glpi y /etc/glpi, invisibles desde HTTP.

◆ Por qué mover config/ y files/

Con el layout por defecto, si Nginx alguna vez sirve el directorio raíz (ej. por un root mal apuntado en un vhost), config/config_db.php quedaría descargable con el password de la DB en claro. Moverlos hace que la única forma de llegar a ellos sea desde el PHP-worker.

6.1 Crear los directorios destino

Ejecutar enhost: vm
sudo install -d -o www-data -g www-data -m 750 /etc/glpi
sudo install -d -o www-data -g www-data -m 750 /var/lib/glpi
sudo install -d -o www-data -g www-data -m 750 /var/log/glpi

# Mover el contenido, no los directorios (para preservar los .htaccess que quedan).
sudo mv /var/www/glpi/config/* /etc/glpi/
sudo mv /var/www/glpi/files/*  /var/lib/glpi/
sudo rmdir /var/www/glpi/config /var/www/glpi/files

6.2 Indicarle a GLPI dónde están ahora

GLPI 11 detecta rutas custom mediante un archivo inc/downstream.php (nombrado así porque es el mecanismo pensado para "downstream distros" — Debian, Fedora, etc.). Lo creamos apuntando a nuestras rutas fuera del webroot.

Ejecutar enhost: vm
sudo tee /var/www/glpi/inc/downstream.php >/dev/null <<'EOF'
<?php
define('GLPI_CONFIG_DIR', '/etc/glpi/');
if (file_exists(GLPI_CONFIG_DIR . '/local_define.php')) {
    require_once GLPI_CONFIG_DIR . '/local_define.php';
}
EOF

# Y ahora el archivo con las rutas del resto de recursos.
sudo tee /etc/glpi/local_define.php >/dev/null <<'EOF'
<?php
define('GLPI_VAR_DIR',           '/var/lib/glpi');
define('GLPI_DOC_DIR',           GLPI_VAR_DIR);
define('GLPI_CACHE_DIR',         GLPI_VAR_DIR . '/_cache');
define('GLPI_CRON_DIR',          GLPI_VAR_DIR . '/_cron');
define('GLPI_DUMP_DIR',          GLPI_VAR_DIR . '/_dumps');
define('GLPI_GRAPH_DIR',         GLPI_VAR_DIR . '/_graphs');
define('GLPI_LOCAL_I18N_DIR',    GLPI_VAR_DIR . '/_locales');
define('GLPI_LOCK_DIR',          GLPI_VAR_DIR . '/_lock');
define('GLPI_PICTURE_DIR',       GLPI_VAR_DIR . '/_pictures');
define('GLPI_PLUGIN_DOC_DIR',    GLPI_VAR_DIR . '/_plugins');
define('GLPI_RSS_DIR',           GLPI_VAR_DIR . '/_rss');
define('GLPI_SESSION_DIR',       GLPI_VAR_DIR . '/_sessions');
define('GLPI_TMP_DIR',           GLPI_VAR_DIR . '/_tmp');
define('GLPI_UPLOAD_DIR',        GLPI_VAR_DIR . '/_uploads');
define('GLPI_LOG_DIR',           '/var/log/glpi');
EOF

sudo chown www-data:www-data /var/www/glpi/inc/downstream.php /etc/glpi/local_define.php
sudo chmod 640 /var/www/glpi/inc/downstream.php /etc/glpi/local_define.php
✓ Verificación

ls /var/www/glpi/ | grep -E '^(config|files)$' NO debe mostrar nada. Si aún ves esos directorios, no completaste el mv de la sección 6.1.

07

Configurar el vhost de Nginx apuntando SOLO a public/

Meta: http://192.168.0.231/ carga el pre-instalador de GLPI. Ningún otro directorio de GLPI es accesible por HTTP.

7.1 Crear el vhost de Nginx

Este es el corazón del tutorial GLPI + Nginx. La regla clave está en el location /: cualquier URL que no exista como archivo o directorio se reenvía a index.php. Ese es el equivalente al FallbackResource del mundo Apache, y es lo que hace que GLPI 11 con su enrutador moderno funcione.

Ejecutar enhost: vm
sudo tee /etc/nginx/sites-available/glpi.conf >/dev/null <<'EOF'
server {
    listen 80;
    listen [::]:80;
    server_name glpi-lab.lab.harper.local 192.168.0.231;

    root  /var/www/glpi/public;
    index index.php;

    client_max_body_size 20M;

    access_log /var/log/nginx/glpi-access.log;
    error_log  /var/log/nginx/glpi-error.log;

    # --- Ruteo: cualquier request cae al front controller (public/index.php) ---
    location / {
        try_files $uri /index.php$is_args$args;
    }

    # --- PHP-FPM: solo se ejecuta index.php (no otros .php arbitrarios) ---
    location = /index.php {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_param HTTP_PROXY "";             # mitigación httpoxy
        fastcgi_read_timeout 600;
    }

    # --- Cache estático razonable para assets ---
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?|ttf)$ {
        expires 7d;
        access_log off;
        try_files $uri =404;
    }

    # --- Denegar acceso a archivos "peligrosos" ---
    location ~ /\.(?!well-known).* { deny all; }
    location ~ ^/(config|files|inc|vendor|src|tests)(/|$) { deny all; }
}
EOF

# Activar el vhost y desactivar el default.
sudo ln -sf /etc/nginx/sites-available/glpi.conf /etc/nginx/sites-enabled/glpi.conf
sudo rm -f /etc/nginx/sites-enabled/default

# Testear la sintaxis y recargar.
sudo nginx -t
sudo systemctl reload nginx

# Abrir el puerto 80 en UFW.
sudo ufw allow 80/tcp
💡 Por qué location = /index.php exacto (y no ~ \.php$)

El patrón "todo lo que termine en .php" es la fuente #1 de RCEs históricos en stacks LAMP mal configurados (subes un JPG con PHP embebido, luego lo ejecutas por URL). Al restringirlo con = a /index.php exacto, el único PHP que Nginx acepta ejecutar es el front controller de GLPI. Combinado con el try_files $uri /index.php..., GLPI enruta todas las URLs internamente y nada más se ejecuta.

⚠ Si sale "502 Bad Gateway"

Nginx no puede hablar con PHP-FPM. Causas comunes:

  • El socket no existe en la ruta declarada. Verifica ls -la /run/php/php8.3-fpm.sock. Si dice "No such file", falta sudo systemctl start php8.3-fpm.
  • El socket existe pero con owner distinto a www-data. Nginx corre como www-data y necesita permiso de escritura. Verifica ls -la /run/php/php8.3-fpm.sock — debe decir www-data www-data. Si no, edita /etc/php/8.3/fpm/pool.d/www.conf y confirma listen.owner = www-data y listen.group = www-data.
  • Actualizaron PHP a 8.4 y el socket ahora está en /run/php/php8.4-fpm.sock. Ajusta la línea fastcgi_pass del vhost.
✓ Verificación de la fase

Desde tu laptop: curl -I http://192.168.0.231/ debe devolver 200 OK (o 302 con redirect al instalador). En el navegador, la URL abre la pantalla "GLPI SETUP · Select your language". Si ves la página de bienvenida de Nginx todavía, tu ruta /etc/nginx/sites-enabled/default no se borró.

08

Correr el asistente web de instalación

Meta: GLPI instalado, DB poblada, login con user default cambiado inmediatamente.

8.1 Recorrer las pantallas

  1. LanguageEspañol (Español).
  2. Licencia → aceptar GPL.
  3. Instalación → botón "Instalar".
  4. Verificación de pre-requisitos → todos en verde. Si alguno rojo, vuelve a la fase 3 (extensiones PHP faltantes) o a la fase 6 (permisos de directorios).
  5. Configurando la conexión a la BD:
    • SQL server: 127.0.0.1
    • SQL user: glpi
    • SQL password: contenido de /root/.glpi-db-pass
  6. Test de conexión → OK → Continuar.
  7. Selección de la BD → seleccionar glpi (ya creada en fase 4) → Continuar. GLPI aplica el schema (200+ tablas, tarda ~30 seg).
  8. Colecta de datos → decidir si envías telemetría a Teclib' (recomendado marcar solo si el cliente lo autoriza).
  9. Instalación completada → botón "Utilizar GLPI".

8.2 Cuentas por defecto — cambiarlas TODAS

El wizard crea 4 cuentas con passwords conocidas públicamente. Cambiar de inmediato:

UsuarioRolPassword defaultAcción
glpiSuper-adminglpiCambiar password fuerte
techTécnicotechCambiar o deshabilitar
normalUsuario normalnormalCambiar o deshabilitar
post-onlyPost-onlypostonlyCambiar o deshabilitar

8.3 Borrar el instalador

Es un requisito de seguridad — mientras public/install.php exista, GLPI muestra un warning permanente. Se borra el archivo específicamente (no el directorio).

Ejecutar enhost: vm
sudo rm /var/www/glpi/install/install.php
# El directorio /install/ se conserva porque tiene otros helpers (upgrade.php, etc.).
✓ Verificación

Administración → Notificaciones → Configuración: no debe aparecer el banner rojo "El archivo install.php aún existe". Login glpi/glpi ya no funciona; solo el password nuevo entra.

09

Cron de glpi y hardening post-install

Meta: tareas automáticas de GLPI (notificaciones, indexado, limpieza) corriendo cada minuto vía cron real, no vía web-cron.

9.1 Por qué reemplazar el web-cron

Por defecto GLPI dispara sus tareas al final de cada request HTTP — funciona en sitios chicos, pero: (a) hace más lento cada request del usuario, (b) si nadie navega, nada corre, (c) no se pueden ejecutar tareas largas. Un cron real (crontab del sistema) es el estándar producción.

Ejecutar enhost: vm
# Cron cada minuto ejecutando el runner CLI de GLPI.
echo "* * * * * www-data /usr/bin/php /var/www/glpi/front/cron.php" | \
  sudo tee /etc/cron.d/glpi >/dev/null

sudo chmod 644 /etc/cron.d/glpi
sudo systemctl restart cron

Ahora en la GUI: Configuración → Acciones automáticas → botón "Modo CLI" global. Esto le dice a GLPI: "no dispares al final de request, deja que el cron externo lo haga".

9.2 Ajustes de sesión y seguridad

En la GUI: Configuración → General → Sistema:

  • Uso de sesión HTTPS: (lo activaremos ahora que activaremos HTTPS en la fase 10; si aún estás en HTTP, déjalo en No y ajústalo después).
  • Duración de sesión: 480 minutos (8h de jornada).
  • Fuerza HTTP para cookies: .
  • Complejidad de password mínima: al menos "Media", con longitud ≥ 10.
💡 Modo debug OFF en producción

Configuración → General → General: Modo debug desactivado. En debug ON, se muestran queries SQL en la interfaz — útil en dev, riesgo de information-disclosure en prod.

10

TLS con Certbot (Let's Encrypt) o cert interno para el lab

Meta: https://glpi.…/ operativo con redirect desde HTTP; header Strict-Transport-Security.

◆ Tres rutas de TLS · elegir según el escenario

Ruta A · Certbot directo: cliente con FQDN público, GLPI expuesto a internet, puerto 80 accesible. Certbot toca el vhost Nginx directamente.
Ruta B · Cert auto-firmado: cliente 100% LAN, ni FQDN público ni intención de tener uno. Cert generado con OpenSSL para 825 días.
Ruta C · NPM (recomendado si hay ≥ 2 servicios web): Nginx Proxy Manager al frente maneja TLS por UI (Let's Encrypt DNS/HTTP + wildcards). GLPI se queda en HTTP interno solo escuchando de NPM. Ver el tutorial de NPM. Es lo que usamos por defecto en clientes nuevos.

10.1 Ruta A · Certbot directo sobre Nginx

Ejecutar enhost: vm
sudo apt -y install certbot python3-certbot-nginx

sudo certbot --nginx \
  -d glpi.cliente-ejemplo.com \
  --agree-tos \
  --email ramces.batista@har-per.com \
  --no-eff-email \
  --redirect

# Verificar la renovación automática (systemd timer).
sudo systemctl list-timers | grep certbot

Certbot edita /etc/nginx/sites-available/glpi.conf añadiendo un bloque server { listen 443 ssl; ... } y modifica el original para redirigir 80 → 443. Recarga Nginx automáticamente. Renovación cada ~60 días vía certbot.timer.

10.2 Ruta B · Cert auto-firmado para lab

Ejecutar enhost: vm
sudo mkdir -p /etc/nginx/ssl
sudo openssl req -x509 -nodes -days 825 -newkey rsa:2048 \
  -keyout /etc/nginx/ssl/glpi-lab.key \
  -out    /etc/nginx/ssl/glpi-lab.crt \
  -subj "/C=DO/ST=SDO/L=SantoDomingo/O=Harper/OU=Infra/CN=glpi-lab.lab.harper.local"

# Reemplazar el vhost HTTP por uno que redirige a HTTPS + otro que sirve HTTPS.
sudo tee /etc/nginx/sites-available/glpi.conf >/dev/null <<'EOF'
# --- HTTP → HTTPS ---
server {
    listen 80;
    listen [::]:80;
    server_name glpi-lab.lab.harper.local 192.168.0.231;
    return 301 https://$host$request_uri;
}

# --- HTTPS ---
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name glpi-lab.lab.harper.local 192.168.0.231;

    ssl_certificate     /etc/nginx/ssl/glpi-lab.crt;
    ssl_certificate_key /etc/nginx/ssl/glpi-lab.key;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options    "nosniff" always;
    add_header X-Frame-Options           "SAMEORIGIN" always;
    add_header Referrer-Policy           "strict-origin-when-cross-origin" always;

    root  /var/www/glpi/public;
    index index.php;

    client_max_body_size 20M;

    access_log /var/log/nginx/glpi-ssl-access.log;
    error_log  /var/log/nginx/glpi-ssl-error.log;

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location = /index.php {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_param HTTP_PROXY "";
        fastcgi_read_timeout 600;
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?|ttf)$ {
        expires 7d;
        access_log off;
        try_files $uri =404;
    }

    location ~ /\.(?!well-known).* { deny all; }
    location ~ ^/(config|files|inc|vendor|src|tests)(/|$) { deny all; }
}
EOF

sudo nginx -t && sudo systemctl reload nginx
sudo ufw allow 443/tcp

10.3 Ruta C · Nginx Proxy Manager al frente (opción recomendada)

Si ya montaste NPM (ver tutorial NPM): dejas GLPI en HTTP puro (fase 7 tal cual) y en NPM creas un Proxy Host con Forward Hostname/IP = 192.168.0.231, Forward Port = 80, y en la pestaña SSL pides el cert de Let's Encrypt. NPM se encarga de todo (renovación, HTTP→HTTPS, HSTS). No hay que tocar más Nginx ni PHP en la VM de GLPI.

10.4 Activar cookies seguras en PHP (rutas A y B)

Ahora que HTTPS está activo, subimos session.cookie_secure.

Ejecutar enhost: vm
sudo sed -i -E 's|^;?\s*session.cookie_secure\s*=.*|session.cookie_secure = 1|' \
  /etc/php/8.3/fpm/php.ini
sudo systemctl restart php8.3-fpm
💡 Si vas a usar NPM (Ruta C), NO hagas 10.4 aún

Con NPM al frente, GLPI recibe requests HTTP internos y si activas session.cookie_secure=1 el login del wizard rompe. La activación se hace después de configurar NPM con X-Forwarded-Proto: https y ajustar el $_SERVER['HTTPS'] vía local_define.php. Se documenta al final del tutorial de NPM.

✓ Verificación

curl -kI https://192.168.0.231/ devuelve 200 y el header Strict-Transport-Security. curl -I http://192.168.0.231/ devuelve 301 hacia HTTPS.

11

Instalar GLPI Agent en el propio server (y en el primer cliente)

Meta: el server GLPI se inventaría a sí mismo, y hay un segundo host cliente inventariado automáticamente cada 24h.

GLPI Agent (sucesor de FusionInventory Agent) es el binario que corre en cada host, recolecta el inventario (hardware, software, red) y lo empuja al servidor GLPI vía HTTPS.

11.1 Habilitar la recepción de inventarios en GLPI

En la GUI: Administración → Inventario → marcar "Habilitar el inventario nativo". GLPI 11 ya trae el módulo integrado (en 10.x era plugin aparte).

11.2 Instalar GLPI Agent (Debian/Ubuntu)

No hay paquete en repo Ubuntu; se descarga el .deb de GitHub Releases del proyecto glpi-agent. La versión estable actual (jul 2026) es 1.15.

Ejecutar enhost: vm (o cualquier cliente Linux a inventariar)
AGENT_VER="1.15"
cd /tmp
wget "https://github.com/glpi-project/glpi-agent/releases/download/${AGENT_VER}/glpi-agent_${AGENT_VER}-1_all.deb"
sudo apt -y install "./glpi-agent_${AGENT_VER}-1_all.deb"

# Configurar target = GLPI server.
sudo sed -i -E \
  -e 's|^\s*#?\s*server\s*=.*|server = https://glpi-lab.lab.harper.local/front/inventory.php|' \
  -e 's|^\s*#?\s*tag\s*=.*|tag = lab-harper|' \
  -e 's|^\s*#?\s*no-ssl-check\s*=.*|no-ssl-check = 1|' \
  /etc/glpi-agent/agent.cfg

# El "no-ssl-check = 1" es SOLO porque el lab usa cert auto-firmado.
# En cliente con Certbot, quitarlo o dejarlo en 0.

sudo systemctl enable --now glpi-agent
sudo systemctl status glpi-agent --no-pager | head -8

# Forzar un inventario manual la primera vez.
sudo glpi-agent --server=https://glpi-lab.lab.harper.local/front/inventory.php --no-ssl-check
✓ Verificación

En la GUI GLPI: Activos → Ordenadores — debe aparecer un item con el hostname del server (y del cliente cuando repitas 11.2 ahí). El detalle muestra RAM, CPU, discos, IP, software instalado y usuario último conectado.

💡 Agents Windows

Para hosts Windows del cliente, GLPI Agent tiene MSI e instalador silencioso — ideal para deploy por GPO. La misma URL de target funciona. msiexec /i GLPI-Agent-1.15-x64.msi /qn SERVER="https://glpi.cliente.com/front/inventory.php".

12

Backup nocturno: DB + /var/lib/glpi + /etc/glpi

Meta: dump de la DB + tar de datos, retención de 14 días, timer de systemd a las 03:00.

12.1 Script de backup

Ejecutar enhost: vm
sudo tee /usr/local/sbin/glpi-backup.sh >/dev/null <<'EOF'
#!/usr/bin/env bash
# Backup nocturno de GLPI — Harper.
# - Dump comprimido de la DB MariaDB.
# - Tar de /var/lib/glpi (files, cache, uploads).
# - Tar de /etc/glpi + /var/www/glpi/inc/downstream.php (config).
# - Retención: 14 más recientes.
set -euo pipefail

BACKUP_DIR=/var/backups/glpi
STAMP=$(date +%F_%H%M)
mkdir -p "$BACKUP_DIR"

GLPI_DB_PASS="$(cat /root/.glpi-db-pass)"

# 1) DB
mariadb-dump -u glpi -p"$GLPI_DB_PASS" \
  --single-transaction --routines --triggers --events \
  --default-character-set=utf8mb4 \
  glpi | gzip -9 > "$BACKUP_DIR/glpi-db-$STAMP.sql.gz"

# 2) Files (excluye caches efímeros)
tar -C / --exclude='var/lib/glpi/_cache' --exclude='var/lib/glpi/_tmp' \
  -czf "$BACKUP_DIR/glpi-files-$STAMP.tar.gz" var/lib/glpi 2>/dev/null

# 3) Configs (chicos, no vale la pena excluir nada)
tar -C / -czf "$BACKUP_DIR/glpi-etc-$STAMP.tar.gz" \
  etc/glpi \
  etc/nginx/sites-available/glpi.conf \
  etc/nginx/ssl 2>/dev/null || true

# 4) Retención
for pattern in glpi-db glpi-files glpi-etc; do
  ls -1t "$BACKUP_DIR"/${pattern}-*.gz 2>/dev/null | tail -n +15 | xargs -r rm --
done

logger -t glpi-backup "backup OK · $STAMP · $(du -sh $BACKUP_DIR | cut -f1)"
EOF

sudo chmod 700 /usr/local/sbin/glpi-backup.sh
sudo /usr/local/sbin/glpi-backup.sh    # test manual
ls -lh /var/backups/glpi/

12.2 Timer systemd

Ejecutar enhost: vm
sudo tee /etc/systemd/system/glpi-backup.service >/dev/null <<'EOF'
[Unit]
Description=GLPI nightly backup (DB + files + configs)
After=mariadb.service

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/glpi-backup.sh
EOF

sudo tee /etc/systemd/system/glpi-backup.timer >/dev/null <<'EOF'
[Unit]
Description=Trigger glpi-backup.service cada noche a las 03:00

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=5m

[Install]
WantedBy=timers.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now glpi-backup.timer
sudo systemctl list-timers glpi-backup.timer
💡 Restore de prueba

Un backup no probado no es un backup. Cada trimestre, en una VM aparte, restaurar el último dump y verificar que GLPI arranca. Documentar el resultado. Los backups que "seguro funcionan" son los que suelen fallar el día del incidente.

13

Documentación de handoff al cliente

Meta: información mínima para operar, actualizar y recuperar el servicio.

13.1 Ficha de servicio (rellenar y entregar)

CampoValor
Cliente
Fecha de entrega
FQDN del server
IP LAN
Versión GLPI11.0.8
URL frontend
Cuenta super-admin creada
Cuentas default deshabilitadas (sí/no)
Instalador borrado (install.php)
DB motor + versiónMariaDB 10.11
Ruta app / config / data/var/www/glpi · /etc/glpi · /var/lib/glpi
Ruta de backups/var/backups/glpi
Off-site backup destino
Snapshots Proxmox activados (sí/no)
Cert TLS: Certbot / interno
Cron real activo (no web-cron)
GLPI Agents desplegados (número)
Plugins instalados
Contacto técnico Harper
Notas / Excepciones

13.2 Checklist de entrega

  • La VM boots limpia, Ubuntu 24.04 actualizado, UFW activo, SSH sin password.
  • MariaDB escucha solo en 127.0.0.1, timezone tables cargadas (≥ 600).
  • PHP 8.3 con las 12 extensiones críticas presentes.
  • config/ y files/ movidos fuera del webroot (a /etc/glpi y /var/lib/glpi).
  • Nginx vhost sirve solo public/; los otros directorios bloqueados con location deny.
  • Wizard corrido, DB poblada (~200 tablas), install.php eliminado.
  • Passwords default de glpi/tech/normal/post-only cambiadas o deshabilitadas.
  • HTTPS activo con redirect desde HTTP; header Strict-Transport-Security presente.
  • Modo debug OFF en Configuración → General.
  • Cron real (/etc/cron.d/glpi) activo; GLPI en modo CLI.
  • GLPI Agent instalado en al menos el propio server, y aparece en Activos → Ordenadores.
  • Timer glpi-backup.timer activo, primer backup manual OK en /var/backups/glpi/.
  • Ficha 13.1 rellena y compartida con el cliente. Snapshot Proxmox "post-handoff" tomado.

13.3 Cheatsheet operativo

Diagnóstico y operación diaria

QuéComando
Log de errores GLPIsudo tail -f /var/log/glpi/*.log
Log de errores Nginxsudo tail -f /var/log/nginx/glpi-error.log /var/log/nginx/glpi-ssl-error.log
Log de errores PHP-FPMsudo tail -f /var/log/php8.3-fpm.log
Reiniciar Nginxsudo systemctl reload nginx
Reiniciar PHP-FPMsudo systemctl restart php8.3-fpm
Verificar cron ejecutándosesudo grep glpi /var/log/syslog | tail
Ejecutar cron manualmentesudo -u www-data php /var/www/glpi/front/cron.php
Backup manual on-demandsudo /usr/local/sbin/glpi-backup.sh
Consola MariaDB del schemasudo mariadb -u glpi -p"$(sudo cat /root/.glpi-db-pass)" glpi
Forzar inventario de un agentsudo glpi-agent --server=https://glpi/.../inventory.php
Ver versión GLPI corriendoEn la GUI: Configuración → General → Sistema (o cat /var/www/glpi/version)

13.4 Path de upgrade (11.0.8 → futuras 11.0.x)

Las minor updates 11.0.x traen fixes de seguridad — importantes de aplicar. Flow:

  1. Snapshot Proxmox pre-upgrade.
  2. Ejecutar el backup manual (fase 12).
  3. Descargar el nuevo tarball desde GitHub Releases.
  4. Poner GLPI en modo mantenimiento (Configuración → General → Sistema → Habilitar mantenimiento).
  5. Reemplazar /var/www/glpi/ por el nuevo (conservando inc/downstream.php).
  6. Navegar a la URL — GLPI ejecuta las migraciones automáticamente y muestra "Actualización completa".
  7. Quitar mantenimiento, validar login, correr un inventario.
✓ Fin del tutorial

Si todos los ítems del checklist 13.2 están marcados, el servidor GLPI está listo para producción. Copia esta página como base de la documentación interna del cliente. El próximo tutorial (a añadir en versiones futuras) será "Integrar Zabbix con GLPI" — para que las alertas Zabbix generen tickets automáticos.

Departamento de Infraestructura · Harper · 2026 Tutorial · GLPI 11 sobre Proxmox