GLPI Security: Hardening and Best Practices

A practical GLPI production hardening checklist: web root at public/, Nginx virtual host, security headers, a Secure session cookie behind the proxy, permissions, accounts, and SQL diagnostics - straight from NexTool's environment maintenance.

GLPI hardening rarely fails because of a zero-day. It fails because of the forgotten installer, the web root pointed at the wrong directory, and the session cookie missing the Secure flag behind the proxy. This guide is the checklist we apply while maintaining client environments: the commands, the virtual host, and the diagnostic queries we use to lock down the attack surface of a GLPI in production.

How a GLPI actually gets compromised

Across dozens of maintained environments, the pattern repeats: compromise almost never comes from an exotic core flaw. It comes from configuration. The four vectors we find most, in order, are: (1) install/install.php never removed after deployment; (2) the DocumentRoot pointing at the installation root instead of public/, which leaves files/_log/ and config backups servable as text; (3) default credentials (glpi/glpi) still active; and (4) TLS terminating at the reverse proxy with the backend on plain HTTP and the session cookie without Secure. None of these needs an exploit, just a curl. Start the audit by testing exactly this:

# Installer still live? (expected: 404)
curl -s -o /dev/null -w "%{http_code}\n" https://glpi.client.com/install/install.php

# Error log exposed? (expected: 403/404, NEVER 200 with content)
curl -s https://glpi.client.com/files/_log/php-errors.log | head

# Config backup leaking the database credentials? (expected: nothing)
curl -s https://glpi.client.com/config/config_db.php.bak | head

If any of these returns content, stop everything: the environment is exposed right now, and fixing it is priority zero.

1. Close the surface: web root at public/ and no installation leftovers

  1. Point the DocumentRoot (or root on Nginx) at /var/www/glpi/public, never at /var/www/glpi. Since GLPI 10, only the public/ folder should be the web root.
  2. Remove install/install.php after finishing the install or the upgrade. The upgrade recreates that file: the common mistake is to update GLPI and forget to remove it again.
  3. Make sure config/, files/ and any .bak or .git stay out of the web server's reach.

2. Virtual host: correct root, headers, and only index.php running PHP

The GLPI 10 standard is to route everything through public/index.php and let no other .php execute directly. This is the server block we use as a baseline on Nginx:

# /etc/nginx/sites-available/glpi.conf
server {
    listen 443 ssl;
    http2 on;
    server_name glpi.client.com;

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

    ssl_certificate     /etc/letsencrypt/live/glpi.client.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/glpi.client.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

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

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

    # Only index.php routes PHP
    location ~ ^/index\.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }

    # Any other .php: deny
    location ~ \.php$ {
        return 404;
    }
}

On Apache the equivalent is DocumentRoot /var/www/glpi/public with a restricted <Directory> block and the same headers via Header set. A field note: only enable Strict-Transport-Security (HSTS) after the certificate is fully working. With HSTS on and a broken TLS you lock the entire domain in your users' browsers for the whole max-age.

3. Session and cookies: the silent leak behind the proxy

This is the finding that most often goes unnoticed. When TLS terminates at the proxy (Nginx/HAProxy) and GLPI runs over HTTP on the backend, the session cookie must still be marked Secure and HttpOnly. Under Setup > General, on the security tab, enforce secure cookies and a password policy (minimum length, complexity, expiration). Without it, the session cookie travels on the internal HTTP hop without Secure, and capturing traffic on that segment is enough to hijack a logged-in technician's session. Also set the session lifetime to something matching the environment's risk instead of the long default.

4. Accounts and authentication

Immediately change the passwords for glpi, tech, normal and post-only, or disable the ones you do not use. Run this diagnostic straight against the database to map what still depends on a local password (authtype = 1) and is a candidate for MFA/SSO or deactivation:

-- Default GLPI accounts still active
SELECT name, is_active, last_login
FROM   glpi_users
WHERE  name IN ('glpi','tech','normal','post-only');

-- All accounts with an active LOCAL password (authtype = 1)
-- authtype: 1=local  2=mail  3=LDAP  4=external  5=CAS
SELECT id, name, is_active, last_login
FROM   glpi_users
WHERE  authtype = 1 AND is_active = 1
ORDER  BY last_login;

The recommendation for production: SSO (Azure AD/Entra ID or another provider) with MFA, and local login restricted to a single break-glass emergency account with a strong password kept in a vault. GLPI core has no native MFA nor lockout by attempts: for MFA use a suitable plugin or the SSO itself; for brute force, put GLPI behind fail2ban reading the proxy access log. See also our guide to SSO with Azure AD/Entra ID.

5. Permissions: read-only code, write only where needed

The principle is simple: the PHP-FPM user (www-data) must read the code but never write it. The only writable tree is GLPI_VAR_DIR. That way, even an application flaw cannot persist malicious code in the served directory.

DirectoryRoleOwner:groupwww-data write
/var/www/glpiCode + public/ web rootroot:www-dataNo (750 / 640)
/etc/glpiConfig (config_db.php)root:www-dataNo (750 / 640)
/var/lib/glpiGLPI_VAR_DIR (files, cache, sessions)www-data:www-dataYes (750)
/var/log/glpiApplication logswww-data:www-dataYes (750)

Splitting config/ and files/ out of the web root (via GLPI_CONFIG_DIR and GLPI_VAR_DIR) is the layout we adopt by default: if the web root is ever exposed by mistake, config and data do not go with it.

6. Updates and continuous monitoring

Keep the version current. Most GLPI CVEs already have a patch by the time they make the news; the real risk is the environment stuck three versions behind. Monitor authentication attempts (spikes in failed logins suggest brute force) in Zabbix/Grafana and track the CVEs for your installed version. For that we use the CVE Scan module, which cross-references the GLPI version against known CVEs and audits environment points. And do not treat hardening without a recovery plan: see the backup and disaster recovery guide.

Common mistakes we see in maintenance

  • Config backup in the web root: someone saves config_db.php.bak before editing; since .bak is not executed by PHP, the server hands the database credentials over as plain text.
  • Legacy alias surviving: the new vhost points at public/, but an old Alias /glpi still exposes the root.
  • HSTS before the certificate: enabling HSTS with an incomplete TLS locks the domain in users' browsers.
  • Local login disabled without a break-glass: local login is cut relying 100% on SSO and, when SSO goes down, nobody can log in as admin.

Doing this once is easy; keeping it across dozens of client environments, with recurring upgrades recreating install/, is maintenance work. That is exactly what NexTool does: NexTool's GLPI maintenance validates the web root, hardens the virtual host, reviews permissions, and runs this verification battery as part of the maintenance cycle.


Este conteúdo foi produzido com auxílio de inteligência artificial e revisado pela equipe Nextool Solutions.

Frequently Asked Questions

Run a curl against /install/install.php, /files/_log/php-errors.log and /config/config_db.php.bak. The expected response is 403 or 404; any 200 with content means exposure. The cause is almost always the DocumentRoot pointing at the installation root instead of public/.

It works, but it is insecure. With the wrong root, files such as files/_log/ and config backups become servable text from the web server. Since GLPI 10, public/ is the only correct web root because it isolates index.php from the rest of the installation.

Not in the core. Put GLPI behind fail2ban reading the reverse proxy access log, or use SSO/MFA to shrink the surface. In parallel, monitor spikes in failed logins in Zabbix or Grafana, which are the typical sign of a brute-force attack.

Under Setup > General, security tab, enable secure cookies; make sure the proxy forwards X-Forwarded-Proto and that GLPI recognizes it is under HTTPS. Without the Secure flag, the session cookie travels on the internal HTTP hop and can be captured on that segment.

Not natively in the core. Use an MFA (TOTP) plugin or SSO with Azure AD/Entra ID or Okta, which already provide MFA. Always keep a local emergency (break-glass) account with a strong password so you do not lose admin access if SSO goes down.

Need help?