ⓘ 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
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
- Language → Español (Español).
- Licencia → aceptar GPL.
- Instalación → botón "Instalar".
- 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).
- Configurando la conexión a la BD:
- SQL server:
127.0.0.1
- SQL user:
glpi
- SQL password: contenido de
/root/.glpi-db-pass
- Test de conexión → OK → Continuar.
- Selección de la BD → seleccionar
glpi (ya creada en fase 4)
→ Continuar. GLPI aplica el schema (200+ tablas, tarda ~30 seg).
- Colecta de datos → decidir si envías telemetría a Teclib' (recomendado
marcar solo si el cliente lo autoriza).
- 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:
| Usuario | Rol | Password default | Acción |
glpi | Super-admin | glpi | Cambiar password fuerte |
tech | Técnico | tech | Cambiar o deshabilitar |
normal | Usuario normal | normal | Cambiar o deshabilitar |
post-only | Post-only | postonly | Cambiar 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: Sí (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: Sí.
- 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)
| Campo | Valor |
| Cliente | |
| Fecha de entrega | |
| FQDN del server | |
| IP LAN | |
| Versión GLPI | 11.0.8 |
| URL frontend | |
| Cuenta super-admin creada | |
| Cuentas default deshabilitadas (sí/no) | |
| Instalador borrado (install.php) | |
| DB motor + versión | MariaDB 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 GLPI | sudo tail -f /var/log/glpi/*.log |
| Log de errores Nginx | sudo tail -f /var/log/nginx/glpi-error.log /var/log/nginx/glpi-ssl-error.log |
| Log de errores PHP-FPM | sudo tail -f /var/log/php8.3-fpm.log |
| Reiniciar Nginx | sudo systemctl reload nginx |
| Reiniciar PHP-FPM | sudo systemctl restart php8.3-fpm |
| Verificar cron ejecutándose | sudo grep glpi /var/log/syslog | tail |
| Ejecutar cron manualmente | sudo -u www-data php /var/www/glpi/front/cron.php |
| Backup manual on-demand | sudo /usr/local/sbin/glpi-backup.sh |
| Consola MariaDB del schema | sudo mariadb -u glpi -p"$(sudo cat /root/.glpi-db-pass)" glpi |
| Forzar inventario de un agent | sudo glpi-agent --server=https://glpi/.../inventory.php |
| Ver versión GLPI corriendo | En 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:
- Snapshot Proxmox pre-upgrade.
- Ejecutar el backup manual (fase 12).
- Descargar el nuevo tarball desde GitHub Releases.
- Poner GLPI en modo mantenimiento (Configuración → General → Sistema →
Habilitar mantenimiento).
- Reemplazar
/var/www/glpi/ por el nuevo (conservando
inc/downstream.php).
- Navegar a la URL — GLPI ejecuta las migraciones automáticamente y muestra
"Actualización completa".
- 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.