Ce message ne décrit pas une panne. Il décrit un refus : la session est valide, l’utilisateur existe, mais la liste de ses capacités ne contient pas celle que la page exige. Cette liste vit dans la table usermeta, sous une clé qui commence par le préfixe des tables, et les rôles qui la remplissent vivent dans la table des options, sous une clé qui commence par le même préfixe. Si l’une des deux manque, ou si le préfixe a changé, un administrateur devient un compte sans rien. Le site public fonctionne, la base est intacte, le mot de passe est bon. Il manque une ligne, ou le bon préfixe devant elle.
Ouvrez wp-config.php et notez $table_prefix. Vérifiez qu’il existe une option {préfixe}user_roles dans la table des options et une ligne {préfixe}capabilities pour votre utilisateur dans usermeta. Préfixe changé : remettez l’ancien, ou renommez l’option et les métadonnées. Ligne absente : rendez-vous le rôle avec wp user add-role votre-login administrator, ou l’insertion SQL donnée plus bas.
Ce que l’écran affiche, mot pour mot
La chaîne source est « Sorry, you are not allowed to access this page. », traduite dans wp-content/languages/admin-fr_FR.po par « Désolé, vous n’avez pas l’autorisation d’accéder à cette page. » Elle est passée à wp_die() avec le code 403 : dans wp-includes/functions.php, wp_die() transforme ce second argument en réponse HTTP, et _default_wp_die_handler() envoie l’en-tête de statut avant d’imprimer la page d’erreur de WordPress, un document minimal dont le corps porte l’identifiant error-page et le texte un bloc de classe wp-die-message. C’est ce rendu qui le distingue d’un 403 d’Apache ou de nginx, qui n’affichent jamais cette phrase.
Deux messages voisins ne sont pas celui-ci. « Désolé, vous n’avez pas l’autorisation de gérer les options de ce site. » vient de wp-admin/options-general.php et des écrans de réglages ; « Vous devez avoir des droits supérieurs. » coiffe les refus de wp-admin/users.php et wp-admin/options.php. Même nature, autre point d’émission, mêmes réparations.
Où WordPress décide
Lu dans le code de WordPress 6.9.4. Chaque écran d’administration charge wp-admin/admin.php, qui commence par auth_redirect() : sans session valide, retour au formulaire de connexion. Ce message ne s’adresse donc qu’à un utilisateur reconnu. admin.php charge ensuite wp-admin/menu.php, qui déclare chaque entrée du menu avec la capacité qu’elle exige : read pour le tableau de bord, manage_options pour les réglages. Ce fichier se termine par wp-admin/includes/menu.php, qui parcourt menu et sous-menu et note dans $_wp_menu_nopriv et $_wp_submenu_nopriv chaque entrée pour laquelle current_user_can() répond faux. Sa dernière instruction :
if ( ! user_can_access_admin_page() ) {
do_action( 'admin_page_access_denied' );
wp_die( __( 'Sorry, you are not allowed to access this page.' ), 403 );
}user_can_access_admin_page(), dans wp-admin/includes/plugin.php, renvoie faux dans trois situations : la page demandée figure dans ces listes ; le paramètre ?page= ne correspond à aucune page enregistrée dans $_registered_pages par add_menu_page() ou add_submenu_page() ; ou l’entrée de menu qui correspond à la page porte une capacité que current_user_can() refuse. Toute l’administration passe par ce point. Les écrans du réseau multisite ajoutent leur propre test, current_user_can( 'manage_network' ) dans wp-admin/network/index.php, avec le même texte.
Reste à savoir ce que current_user_can() consulte. WP_User::for_site(), dans wp-includes/class-wp-user.php, fixe cap_key à $wpdb->get_blog_prefix() . 'capabilities' et lit cette métadonnée avec get_user_meta(). Si la valeur n’est pas un tableau, les capacités sont vides. get_role_caps() ne garde comme rôles que les clés de ce tableau que wp_roles()->is_role() reconnaît, puis fusionne les capacités de chaque rôle. Les rôles viennent de WP_Roles::for_site(), dans wp-includes/class-wp-roles.php : get_option( $wpdb->get_blog_prefix() . 'user_roles', array() ). Si l’option manque, init_roles() s’arrête sans créer un seul rôle, et get_role( 'administrator' ) renvoie null.
Trois choses dépendent donc du même préfixe, celui que wp-settings.php transmet depuis $table_prefix : le nom des tables, le nom de l’option des rôles et la clé usermeta de chaque utilisateur. has_cap() ajoute une règle : en multisite, un super-administrateur, c’est-à-dire un identifiant présent dans l’option réseau site_admins, obtient tout sans passer par les rôles. Hors multisite, cette porte n’existe pas.
Les causes, par ordre de fréquence
1. Le préfixe a changé, ou la migration est restée à moitié
Signature : tous les comptes sont refusés, juste après une migration, une restauration, un déplacement chez un autre hébergeur ou un changement de préfixe « pour la sécurité ». Un outil a renommé les tables sans renommer l’option des rôles ni les métadonnées, ou a modifié wp-config.php sans toucher à la base. Vérification : comparer $table_prefix au nom réel des tables, puis chercher où vit l’option des rôles :
SELECT option_name FROM wp_options WHERE option_name LIKE '%user_roles';
SELECT meta_key FROM wp_usermeta WHERE user_id = 1 AND meta_key LIKE '%capabilities';Le nom de la table est celui que vous voyez dans phpMyAdmin ou Adminer. Si la première requête répond wp_user_roles alors que wp-config.php dit site_, le diagnostic est fait. Réparation : si les tables n’ont pas été renommées, remettre l’ancien préfixe dans wp-config.php. Si elles l’ont été, aligner l’option et les métadonnées :
UPDATE site_options SET option_name = 'site_user_roles' WHERE option_name = 'wp_user_roles';
UPDATE site_usermeta SET meta_key = 'site_capabilities' WHERE meta_key = 'wp_capabilities';
UPDATE site_usermeta SET meta_key = 'site_user_level' WHERE meta_key = 'wp_user_level';Les deux dernières requêtes corrigent tous les utilisateurs d’un coup. D’autres clés portent le préfixe, wp_user-settings par exemple ; elles ne bloquent pas l’accès, et une requête LIKE 'wp\_%' sur meta_key les liste.
2. L’utilisateur n’a plus de rôle
Signature : un seul compte est touché, et la connexion ne mène plus au tableau de bord : wp-login.php envoie vers profile.php un utilisateur sans edit_posts mais avec read, et vers la page d’accueil un utilisateur sans read. Le refus n’apparaît que lorsqu’on tape /wp-admin/ soi-même. Un autre administrateur a retiré le rôle, un import a écrasé la métadonnée, une extension d’adhésion l’a remplacée par un rôle à elle. Vérification : wp user list --fields=ID,user_login,roles : la colonne roles est vide pour ce compte. Réparation : wp user add-role votre-login administrator, ou l’insertion SQL de la section « Se rendre les droits ».
3. Une extension a retiré des capacités
Une extension de sécurité ou de gestion des rôles a deux moyens d’intervenir, et ils ne se réparent pas pareil. WP_Role::remove_cap() réécrit l’option {préfixe}user_roles avec update_option() : la modification reste en base et survit à la désactivation. Le filtre user_has_cap, appliqué dans has_cap() à chaque vérification, agit à l’exécution et disparaît avec l’extension. Signature : quelqu’un a touché l’écran de réglage d’une extension de ce type, et le refus ne frappe que certains écrans. Vérification : wp option get wp_user_roles --format=json, puis chercher la clé administrator et, dedans, manage_options. Réparation : si la capacité manque dans l’option, wp role reset administrator remet le rôle dans l’état d’une installation neuve, par populate_roles(), sans toucher aux rôles personnalisés. Si l’option est intacte, c’est un filtre : wp plugin deactivate le-slug, après sauvegarde.
4. Un rôle personnalisé mal défini, ou l’option des rôles abîmée
Signature : tous les comptes d’un même rôle sont refusés, personne d’autre. init_roles() attend, pour chaque rôle, un tableau avec les clés name et capabilities. Un rôle déclaré par add_role() sans capacités existe, mais ne donne rien ; un rôle dont la clé a disparu de l’option n’est plus reconnu par is_role(), et ses utilisateurs deviennent des comptes sans rôle. Vérification : la même lecture de l’option qu’à la cause 3, en cherchant la clé du rôle attribué. Réparation : wp role reset pour un rôle natif ; pour un rôle personnalisé, le redéclarer avec ses capacités.
5. Multisite : administrateur du site, pas du réseau
Le rôle administrator ne contient pas manage_network, que populate_roles() n’attribue à personne. Seul un super-administrateur, dont l’identifiant figure dans site_admins (table sitemeta), passe le test de wp-admin/network/index.php. Signature : le tableau de bord du site fonctionne, /wp-admin/network/ est refusé. Réparation : un super-administrateur ajoute le compte au réseau ; la fonction qui le fait est grant_super_admin(), dans wp-includes/capabilities.php.
Second cas multisite : les capacités sont lues par site, sous wp_3_capabilities pour le site 3, puisque get_blog_prefix() ajoute l’identifiant du site au préfixe de base. Un rôle sur le site principal ne vaut rien ailleurs. Là, _access_denied_splash(), accroché à admin_page_access_denied par wp-admin/includes/ms-admin-filters.php, remplace le message par « Vous avez tenté d’accéder au tableau de bord de … Cependant, vous ne disposez pas pour le moment des droits nécessaires sur ce site. » Réparation : wp user add-role avec --url= pointant sur le site concerné.
6. Une page de menu qui exige une capacité disparue, ou qui n’existe plus
add_menu_page() enregistre chaque écran d’extension avec la capacité passée en argument. Si une mise à jour de l’extension exige désormais une capacité que votre rôle n’a pas, ou si la capacité qu’elle avait ajoutée à l’activation a été retirée par un wp role reset, cet écran seul est refusé. Et un favori vers ?page=… d’une extension désactivée tombe sur la vérification de $_registered_pages : même message. Signature : un seul écran est refusé, tout le reste de l’administration répond. Réparation : réactiver l’extension, ou ajouter la capacité au rôle : wp cap add administrator la_capacite.
7. Un 403 du serveur, qui n’a rien à voir
Une règle dans .htaccess, un pare-feu applicatif ou une protection de l’hébergeur sur wp-admin renvoient aussi un 403. Signature : la page ne contient pas la phrase de WordPress ; elle dit « Forbidden », porte la signature d’Apache ou de nginx, ou la charte de l’hébergeur. Le code source ne contient ni error-page ni wp-die-message. Réparation : côté serveur, et rien de ce guide ne s’y applique.
Vérifier en une commande
wp db prefix
wp user list --fields=ID,user_login,roles
wp option get wp_user_roles --format=json
wp role listLa première donne le préfixe tel que WordPress le lit ; les suivantes en dépendent, et c’est pourquoi WP-CLI affiche des rôles vides quand le préfixe est faux. Sans WP-CLI, les deux requêtes de la cause 1 disent la même chose, et celle-ci lit le compte :
SELECT meta_key, meta_value FROM wp_usermeta
WHERE user_id = 1 AND meta_key IN ('wp_capabilities', 'wp_user_level');Attendu pour un administrateur : a:1:{s:13:"administrator";b:1;} et 10.
Se rendre les droits
Avec WP-CLI, qui accepte l’identifiant, l’e-mail ou le login :
wp user add-role votre-login administrator
# ou, pour remplacer les rôles existants :
wp user set-role votre-login administratorSans WP-CLI, depuis phpMyAdmin ou Adminer, pour l’utilisateur 1 :
INSERT INTO wp_usermeta (user_id, meta_key, meta_value)
VALUES (1, 'wp_capabilities', 'a:1:{s:13:"administrator";b:1;}'),
(1, 'wp_user_level', '10');Si les deux lignes existent déjà, passez par UPDATE sur meta_value. La clé porte le préfixe parce que WP_User::for_site() la construit ainsi : une même table usermeta peut servir plusieurs installations, ou plusieurs sites d’un réseau, et chacun y lit sa propre ligne. Le 13 est la longueur du mot administrator dans la sérialisation PHP ; le préfixe change la clé, pas la valeur. Quant à user_level, le contrôle d’accès ne le lit pas : update_user_level_from_caps() l’écrit à chaque changement de rôle, à partir des capacités level_0 à level_10 que les rôles natifs portent encore. L’écrire à 10 reproduit ce que set_role() aurait fait. Avec un cache d’objets persistant, terminez par wp cache flush : get_user_meta() passe par ce cache.
Ce qui ne marche pas
Recréer l’utilisateur avec le même e-mail. wp_insert_user() refuse une adresse déjà présente, erreur existing_user_email, « Désolé, cette adresse e-mail est déjà utilisée ! ». Et un compte neuf, avec une autre adresse, reçoit ses capacités sous la même clé préfixée : si la cause est le préfixe ou l’option des rôles, il est refusé exactement comme l’ancien.
Désactiver toutes les extensions à l’aveugle. Ça ne change rien aux causes 1, 2, 4 et 5, qui sont en base, ni à la partie persistante de la cause 3, puisque remove_cap() a déjà écrit l’option. Sans sauvegarde, vous perdez en plus l’état qui permettait de comparer. Les quatre commandes ci-dessus désignent la cause en une minute.
Vérifier que c’est terminé
wp user list --fields=ID,user_login,roles doit afficher administrator sur la ligne du compte. Puis ouvrez /wp-admin/options-general.php : cet écran exige manage_options. S’il s’affiche, le refus est levé pour toute l’administration. S’il reste un message, relisez sa formulation : un texte différent vient d’un autre point du code, et le guide de l’erreur critique couvre le cas où la page ne s’affiche plus du tout.