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.

| 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_requirementswith 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'.
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