Upgrading from GLPI 11 to 12, step by step (Linux and Docker), with what broke in our test

We ran a real upgrade of a GLPI 11.0.11 copy to 12.0.0, in Docker and on Debian 13. The migration took 5 seconds. The work came afterwards: MariaDB 10.6 rejected, plugins suspended, a knowledge base returning 403 errors and webhooks stopped. Commands and fixes.

In this article8 sections

We upgraded a copy of a GLPI 11.0.11 instance to 12.0.0 twice: once in Docker with the official image and once on Debian 13 with the .tgz package. The database migration took about 5 seconds both times. The real work came before and after it: MariaDB 10.6 was rejected, the plugins were suspended, the knowledge base categories turned into articles that technicians and users cannot open, and the webhook stopped sending, for two different reasons. This guide walks through both paths step by step, with the fix for each problem, in the order they showed up.

Still deciding whether it is time to upgrade? Read GLPI 12 is out: what changes, requirements and when to upgrade first.

What we tested

We set up a throwaway GLPI 11.0.11 with fictitious data for Acme: 120 tickets with followups and 40 tasks, one SLA, 9 knowledge base articles in 4 categories with different permissions, one webhook pointing to an internal server, and three community plugins (Fields, Tag and Data Injection), with data. We took a backup and upgraded to 12.0.0. Then we restored the same backup on Debian 13 and repeated the upgrade with the .tgz package.

Timeline of the GLPI 11.0.11 to 12.0.0 upgrade test, with the result of each step: backup, MariaDB rejected, MariaDB upgrade, migration, plugins, knowledge base, webhook and internal network
The database migration took 5 seconds. The problems came before (MariaDB) and after (plugins, knowledge base and webhooks). NexTool test, Oct 16, 2026.
Item Result
Database migration (455 to 460 tables, from 47 to 49.5 MB) About 5 seconds, no errors, on both paths
Tickets, followups, tasks, Fields values and tags All preserved
Plugins (GLPI 11 versions) Suspended, then deactivated: they need a new version
Knowledge base categories Became articles with no permissions (403 error for technician and user)
Webhook created in GLPI 11 Stopped sending: lowercase "post" method and internal network blocked

Before you start: requirements

Item GLPI 11 GLPI 12
PHP 8.2 to 8.5 8.3 to 8.5
MariaDB 10.6 or newer 10.11 or newer
MySQL 8.0 or newer 8.0 or newer

Two details the test revealed:

  • PHP 8.2 does not give a warning, it gives a syntax error. The GLPI 12 console never even gets to the requirements check: it stops with a "Parse error" in one of GLPI's own files. Debian 12 and Ubuntu 22.04 ship PHP 8.2 or older. Debian 13 (PHP 8.4) and Ubuntu 24.04 (PHP 8.3) meet the requirement with the default packages. The official Docker image already ships PHP 8.5.
  • Check the database before anything else. Run php bin/console system:check_requirements with the new code pointing at your database. On our MariaDB 10.6 the answer was blunt: unsupported version, minimum 10.11.

Step 0: backup (database, files and the key)

Make three copies before touching anything: the database, the files folder and the configuration folder. The configuration folder holds glpicrypt.key. Without it, the passwords GLPI stores (mail collector, LDAP, proxy, webhook secrets) can no longer be decrypted.

mariadb-dump --single-transaction --routines -u root -p glpi | gzip > glpi11-$(date +%F).sql.gz
tar czf glpi11-arquivos-$(date +%F).tgz /etc/glpi /var/lib/glpi/files /var/www/glpi/marketplace

In Docker with the official image, everything lives in the volume mounted at /var/glpi (config, files, marketplace and logs): copy the whole volume. In our test, the dump took 5 seconds and the volume copy 8.5.

Path 1: Docker with the official image

The official image (glpi/glpi) upgrades the database on its own when it starts with a new version. Just change the tag and start the container: there is no confirmation. That is why the backup comes before the tag change, never after. If you would rather run the upgrade by hand, start it with GLPI_SKIP_AUTOUPDATE=true and use the command from path 2.

1. Upgrade MariaDB, if needed. With MariaDB 10.6, the GLPI 12 container exited with code 128. The log warned about missing requirements and, right after that, printed an alarming line: "GLPI is not installed... Starting installation". It did not install anything. The database stayed intact, on version 11.0.11, with all 120 tickets. It simply failed the requirements check. Moving MariaDB from 10.6 to 10.11 took 22 seconds with the MariaDB image's auto-upgrade variable. Start only the database first (docker compose up -d db) and check the log before moving on:

services:
  db:
    image: mariadb:10.11          # was 10.6
    environment:
      MARIADB_AUTO_UPGRADE: "1"   # runs mariadb-upgrade on startup
  glpi:
    image: glpi/glpi:12.0.0       # was 11.0.11 (step 2)
    volumes:
      - glpi-data:/var/glpi

2. Change the tag and start. docker compose up -d. The log shows "GLPI is installed, and auto-update is enabled. Starting update..." followed by the welcome message. In the test it took 17 seconds from up to the welcome message in the log.

3. Verify. docker compose exec glpi bin/console db:check should answer "Database schema is OK".

Path 2: Linux with the .tgz package

The commands below follow the layout from our GLPI 11 installation guide for Debian and Ubuntu: code in /var/www/glpi, configuration in /etc/glpi and files in /var/lib/glpi/files.

cd /var/www/glpi && sudo -u www-data php bin/console maintenance:enable
cd /var/www && mv glpi glpi-11-antigo
wget https://github.com/glpi-project/glpi/releases/download/12.0.0/glpi-12.0.0.tgz
tar xzf glpi-12.0.0.tgz
cp glpi-11-antigo/inc/downstream.php glpi/inc/                  # if present
cp glpi-11-antigo/config/local_define.php glpi/config/ 2>/dev/null  # if present
cp -a glpi-11-antigo/marketplace/. glpi/marketplace/
chown -R www-data:www-data glpi
cd glpi
sudo -u www-data php bin/console system:check_requirements
sudo -u www-data php bin/console database:update
sudo -u www-data php bin/console maintenance:disable

The file everyone forgets: inc/downstream.php. This is the file that tells GLPI the configuration lives in /etc/glpi. It sits inside the code folder, so it disappears when you swap the folder. In the test, without it, database:update answered "Unable to connect to database". The message does not give the real reason: GLPI looked for the configuration in the wrong place. Once the file was copied back, the upgrade ran right away. The same goes for config/local_define.php, if you use it.

If your GLPI keeps config and files inside /var/www/glpi, also copy config/ and files/ from the old folder. That was the case on Debian 13 (PHP 8.4): the migration took 5 seconds and db:check came back clean.

If the database update fails

On November 3, a GLPI forum thread described an 11.0.8 to 12.0.1 upgrade on Debian 13 that got stuck: the files were copied, owner and permissions adjusted, then maintenance:disable returned an error and the database update failed. We did not reproduce this case. A reply in the thread pointed to a likely cause, and the migration code confirms it is possible: in 11, the table that links articles to knowledge base categories accepts the same article/category pair more than once; the table that replaces it in 12 does not. One duplicate link is enough to stop the migration. Restore the 11 backup (the migration may have stopped halfway) and look for duplicates:

SELECT knowbaseitems_id, knowbaseitemcategories_id, COUNT(*) AS n
FROM glpi_knowbaseitems_knowbaseitemcategories
GROUP BY knowbaseitems_id, knowbaseitemcategories_id
HAVING n > 1;

If any row comes back, delete the copies and keep one link per pair. The article stays in its category:

DELETE l1 FROM glpi_knowbaseitems_knowbaseitemcategories l1
JOIN glpi_knowbaseitems_knowbaseitemcategories l2
  ON l1.knowbaseitems_id = l2.knowbaseitems_id
 AND l1.knowbaseitemcategories_id = l2.knowbaseitemcategories_id
 AND l1.id > l2.id;

If the query comes back empty, the cause is something else. Check that the files folder and the configuration folder (in our layout, /var/lib/glpi/files and /etc/glpi) belong to www-data, run system:check_requirements, and read php-errors.log in the _log folder of the files directory and the web server error log. Run database:update from the command line, which prints the error on screen, and only then maintenance:disable.

After the migration: what broke and how to fix it

1. Plugins are suspended (and the list command does not tell you)

After the upgrade, GLPI 12 suspends the execution of all plugins. plugin:list kept showing all three as "Enabled". But the plugins_execution_mode setting was at "suspended_by_update", and no plugin was running. To release them:

php bin/console plugin:resume_execution

On release, GLPI checked the versions and deactivated all three ("Installed / not activated"). The GLPI 11 versions accept GLPI 11.0.99 at most, and the GLPI 12 versions require 12.0.0 at least. In other words: you cannot update these plugins before GLPI. GLPI first, then the plugins:

Plugin Version on GLPI 11 Version on GLPI 12
Fields (additional fields) 1.24.6 1.25.0
Tag 2.14.8 2.15.0
Data Injection 2.15.12 2.16.0

For each one: replace the folder in marketplace/ with the new version and run plugin:install and plugin:activate. The data stayed: the 5 values of the Fields "Cost center" field and the 10 Tag tags were still there. Before you set a date, check that every plugin you use already has a version for 12 (how to choose plugins without blocking the upgrade).

2. Knowledge base categories become articles with no visibility

GLPI 12 no longer has knowledge base categories: the 4 categories became 4 "parent" articles, and a root article ("Home") was created above them. The 9 articles kept the permissions they had. But the 4 articles that used to be categories ended up with no visibility rule at all. We tested through the API with a technician and a Self-Service user: both got a 403 error when opening "Procedures", "Network", "Wi-Fi" and "User FAQ". Each of them could still open only the articles they could see before.

Fix: open each former category and give it the audience the folder had (entity, profile or group). In 12, an article inherits the visibility of the articles above it. In the test, opening the former "User FAQ" category to the Acme entity let the technician open the folder and the two articles inside it, which had returned 403 before. For the Self-Service user there is one more step: they only see articles flagged as FAQ, and the former categories arrive without that flag. With visibility and the FAQ flag, the folder showed up for them too. Do this before opening the system to users.

3. The webhook stopped: lowercase "post" method

The webhook created in GLPI 11 was queued and failed. The webhook.log file showed the cause: Invalid HTTP method "post", only uppercase letters are accepted. GLPI stores the method in lowercase, and the HTTP client in 12 only accepts uppercase. This affects every webhook, including those created in 12. The problem is tracked on GLPI's GitHub (issue 25883), with a proposed fix. When we tested, on October 16, the fix was not yet in 12.0.0 or in the 12.0.1 development version.

Until the fix ships, the workaround is to switch the method to uppercase directly in the database (with a backup):

UPDATE glpi_webhooks SET http_method = UPPER(http_method);
UPDATE glpi_queuedwebhooks SET http_method = UPPER(http_method);

If someone saves the webhook again through the UI, the field goes back to lowercase. Run the command again in that case, and check the 12.0.x release notes: once the fix is in, the workaround is no longer needed.

4. Next, the internal network block

With the method fixed, the error changed: Host "glpiup-hook" is blocked. GLPI 12 blocks server-side calls to private networks (10.x, 172.16-31.x, 192.168.x) by default. This applies to webhooks, external calendars, RSS feeds and the connection to GLPI Network. By default, only calls to the inventory agent and to external authentication are allowed. If your webhook points to n8n, an ERP or an API on the internal network, it will stop.

To allow only what you need, create the config/local_define.php file in the GLPI code folder:

<?php
define('GLPI_SERVERSIDE_URL_ALLOWED_PRIVATE_NETWORKS_CONTEXTS', [
    \Agent::class,    // default
    \Auth::class,     // default
    \Webhook::class,  // allows webhooks to the internal network
]);

Docker gotcha: GLPI reads this file from /var/www/glpi/config/, which is inside the image, not from the /var/glpi/config volume. Placed in the volume, the file had no effect. Mounted as a file, it worked:

    volumes:
      - glpi-data:/var/glpi
      - ./local_define.php:/var/www/glpi/config/local_define.php:ro

After that, the two queued webhooks were delivered with status 200. To set up a webhook from scratch, see native webhooks in GLPI 11.

5. The warning about documents

The migration left a single warning in the warning_during_migration_to_12.0.0.log log: "Rights have been given for document, you should review ACLs after update". GLPI granted new rights on documents. Review them in Administration > Profiles, Management > Documents tab, especially on end-user profiles.

Upgrade checklist

When What to do
Before Check PHP (8.3+) and the database (MariaDB 10.11+ or MySQL 8.0+) with system:check_requirements
Before List plugins and confirm each one has a version for 12
Before List webhooks and integrations that point to internal addresses
Before Look for duplicate links between articles and knowledge base categories
Before Back up the database, the files and the configuration folder (with glpicrypt.key)
During Maintenance mode, code swap (or tag change), database:update
During With the .tgz: copy inc/downstream.php and config/local_define.php from the old folder
After db:check, plugin:resume_execution and update each plugin
After Give visibility to the former knowledge base categories (and the FAQ flag, for Self-Service)
After Fix the webhook method and allow the internal network, if needed
After Review document rights on the profiles
After Open a test ticket, trigger a webhook, log in as an end user

What about NexTool modules?

The NexTool plugin and its 37 modules run on GLPI 11 and 12 with the same version. So, unlike the plugins above, you can update them first, while still on 11, in NexTool > Modules > Update all. After the migration, they are suspended like the other plugins: run plugin:resume_execution and check the list in NexTool > Modules. The module-by-module table is in NexTool on GLPI 12: compatibility. If you want help planning or running the upgrade, talk to us.


NexTool test on October 16, 2026, on a throwaway copy with fictitious data: official glpi/glpi image 11.0.11 to 12.0.0 (PHP 8.5), MariaDB 10.6 to 10.11, and .tgz package on Debian 13 (PHP 8.4). The environment was deleted at the end. GLPI is a registered trademark of Teclib'. NexTool is not affiliated with Teclib'.

Frequently Asked Questions

On our test copy (120 tickets, 47 MB database), the database migration took about 5 seconds, in Docker and on Linux. Larger databases take longer. The time that matters goes into the backup, the plugin updates and the checks after the migration.

If it is below 10.11, yes. GLPI 12 requires MariaDB 10.11 or MySQL 8.0, and PHP 8.3 to 8.5. In the test, GLPI 12 rejected MariaDB 10.6, which GLPI 11 accepted, without touching the database.

Not with the same version. After the migration, GLPI suspends the plugins and, on release, deactivates those that do not declare support for 12. Install the version for 12 of each plugin after migrating. Plugin data was preserved in the test. NexTool and its modules run on 11 and 12 with the same version.

In GLPI 12, knowledge base categories become articles, and those articles arrive with no visibility rule at all. Give each former category the audience it had and, for Self-Service, also flag it as FAQ.

In the test there were two reasons. In 12.0.0, the "post" method stored in lowercase is rejected by the HTTP client (issue 25883 on GLPI's GitHub). And GLPI 12 blocks calls to internal networks by default. The fix for each one is in the post.

Yes. When it starts with a new version, it runs the database upgrade without asking for confirmation. Take the backup before changing the tag, or start it with GLPI_SKIP_AUTOUPDATE=true and run the upgrade by hand.

Comments

Comments on this post come from GitHub (giscus). When you load them, your browser connects to GitHub, which receives your IP address and may set its own cookies.

To load them automatically, enable functional cookies

Need help?