Configurer la compression Gzip via PHP sur un hébergement mutualisé

Credit : Logo officiel

Configurer la compression Gzip via PHP sur un hébergement mutualisé

Dylan D. — Agent Support Technique Serveur Web 1901 mots 10 min de lecture

Configurer la compression Gzip via PHP sur un hebergement mutualise

Mardi matin, un client me ping sur Slack : "Mon score PageSpeed est a 38, Google Search Console alerte sur les Core Web Vitals, et la principale recommandation c'est Activer la compression texte. J'ai essaye plein de trucs dans le .htaccess mais rien ne marche. Au secours." Le client est sur un hebergement mutualise IONOS Pro avec Apache. Probleme classique : mod_deflate n'est pas active sur ce vhost particulier, et il n'a pas acces a la config serveur. Solution en 10 minutes via PHP, score qui passe a 87. Le client est sauve, ses Core Web Vitals aussi.

Tu as teste ton site sur PageSpeed Insights et il te dit d'activer la compression texte ? Sur un dedie ou un VPS, c'est trivial -- on active mod_deflate dans Apache ou gzip dans Nginx en deux lignes. Mais sur un mutualise ou tu as pas acces a la config serveur, faut ruser. Heureusement, PHP a tout ce qu'il faut pour faire la compression au niveau applicatif.

Pourquoi compresser en HTTP ?

Quand un navigateur fait une requete a ton site, il envoie un header :

Accept-Encoding: gzip, deflate, br

Il dit au serveur : "je peux decompresser ces formats, envoie-moi du compresse si tu veux". Si le serveur supporte un de ces formats, il compresse la reponse, ajoute un header Content-Encoding: gzip et envoie. Le navigateur decompresse a la volee et affiche.

L'impact est massif :

Une page WordPress de 150 Ko en HTML brut passe a 25-35 Ko compressee. Sur mobile en 4G, c'est plusieurs centaines de millisecondes gagnees, sur des connexions plus lentes c'est plusieurs secondes.

Le probleme sur un mutualise

Sur un mutualise IONOS, tu n'as pas acces au httpd.conf ni au nginx.conf. La methode "officielle" via .htaccess :

<IfModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/html text/css application/javascript application/json text/xml application/xml application/rss+xml
</IfModule>

Dans beaucoup de cas, ca ne fait rien. Pourquoi ? Parce que mod_deflate n'est pas necessairement charge sur ton vhost. Le <IfModule mod_deflate.c> echoue silencieusement, ton site reste non-compresse, et tu te grattes la tete pendant des heures.

Sur certains hebergements LiteSpeed, on peut utiliser :

<IfModule LiteSpeed>
  SetEnv LSCACHE_VARY_VALUE "compress"
</IfModule>

Mais c'est pas garanti non plus.

La solution : ob_gzhandler en PHP

PHP integre nativement un handler de compression appele ob_gzhandler. L'idee c'est d'utiliser le output buffering : tu commences a buffriser la sortie au tout debut du script, PHP la compresse, puis l'envoie au navigateur.

C'est moins efficace qu'une compression au niveau serveur (un peu plus de CPU consomme par PHP) mais ca marche systematiquement, meme sur les hebergements les plus restrictifs.

Methode 1 : auto_prepend_file (recommandee)

C'est ma methode preferee parce qu'elle s'applique automatiquement a toutes les pages PHP du site sans toucher au code.

Cree /home/htdocs/web/gzip_start.php :

<?php
if (extension_loaded('zlib') && !ini_get('zlib.output_compression')) {
    if (!headers_sent() && substr_count($_SERVER['HTTP_ACCEPT_ENCODING'] ?? '', 'gzip')) {
        ob_start('ob_gzhandler');
    } else {
        ob_start();
    }
}

Le script verifie :

  1. Que l'extension zlib est chargee (presente par defaut sur PHP 8.x)
  2. Que zlib.output_compression n'est pas deja active (sinon double compression = sortie corrompue)
  3. Que les headers ne sont pas deja envoyes
  4. Que le client supporte gzip

Cree /home/htdocs/web/gzip_end.php :

<?php
if (ob_get_level() > 0) {
    ob_end_flush();
}

Dans ton .htaccess ou ton .user.ini a la racine :

auto_prepend_file = /home/htdocs/web/gzip_start.php
auto_append_file = /home/htdocs/web/gzip_end.php

Le auto_prepend_file est inclus avant chaque script PHP, le auto_append_file apres. Ca s'applique transparently a WordPress, PrestaShop, ou ton script custom.

Sur les serveurs IONOS, cette methode marche de maniere fiable sur tous les types d'hebergement. C'est la methode qu'on recommande aux clients qui n'ont pas acces a mod_deflate.

Methode 2 : zlib.output_compression dans .user.ini

Alternative plus simple, sans script PHP, juste de la configuration :

zlib.output_compression = On
zlib.output_compression_level = 6
output_buffering = 4096

A placer dans /home/htdocs/web/.user.ini. PHP fait tout, tu n'as rien d'autre a faire.

Le niveau de compression va de 1 (minimal, rapide) a 9 (maximum, lent). 6 c'est un bon compromis. On voit souvent des clients qui mettent 9 en pensant que c'est mieux. La difference de taille entre 6 et 9 est de quelques pourcents (genre 27 Ko au lieu de 28 Ko), mais la charge CPU augmente significativement (30-50% en plus). Reste sur 6.

Attention : sur certains hebergements PHP-FPM, le .user.ini n'est pas relu immediatement (cache de 5 minutes par defaut). Pour forcer :

touch /home/htdocs/web/.user.ini

Et attends 5 minutes ou demande au support de redemarrer PHP-FPM.

Methode 3 : directement dans le code

Pour un site sur mesure (pas WordPress/PrestaShop), tu peux ajouter en debut de ton index.php :

<?php
if (extension_loaded('zlib')) {
    ob_start('ob_gzhandler');
}

Moins propre car ca s'applique qu'a un fichier point d'entree. Si tu as plusieurs entry points (admin.php, api.php, etc.), faut le mettre partout.

Configuration WordPress specifique

Sur WordPress, evite la methode auto_prepend_file parce qu'elle peut interferer avec certains plugins de cache. Privilegie zlib.output_compression dans .user.ini.

Alternative WordPress-specifique dans wp-config.php :

if (extension_loaded('zlib') && !ob_start('ob_gzhandler')) {
    ob_start();
}

A placer juste avant require_once(ABSPATH . 'wp-settings.php'); a la fin du wp-config.php.

Si tu utilises WP Rocket, LiteSpeed Cache, ou un autre plugin de cache, ils ont generalement leur propre option de compression. Active-la dans le plugin et n'active pas en plus la compression PHP, sinon double compression et site casse.

Verifier que ca marche

La methode la plus fiable, depuis ton terminal :

curl -H "Accept-Encoding: gzip,deflate" -I https://monsite.fr/

Dans la reponse, tu dois voir :

Content-Encoding: gzip
Vary: Accept-Encoding

Si Content-Encoding: gzip est absent, la compression n'est pas active.

Pour mesurer le gain reel :

curl -H "Accept-Encoding: gzip" -s -o /dev/null -w "%{size_download}\n" https://monsite.fr/
curl -H "Accept-Encoding: identity" -s -o /dev/null -w "%{size_download}\n" https://monsite.fr/

La premiere commande te donne la taille avec gzip, la seconde sans. Le ratio te donne ton taux de compression.

Dans le navigateur (Chrome DevTools) : ouvre le Network tab, recharge la page, clique sur le document HTML principal, regarde dans Headers. Tu dois voir content-encoding: gzip dans les Response Headers.

La colonne Size du Network tab affiche generalement deux valeurs : la taille transferee (compressee) et la taille apres decompression (entre parentheses).

Ne pas combiner les methodes

C'est l'erreur que je vois le plus souvent : combiner mod_deflate (htaccess), ob_gzhandler (PHP) et zlib.output_compression (php.ini). Resultat : double ou triple compression, le navigateur recoit du gzip dans du gzip dans du gzip et ne sait plus quoi en faire. Affichage casse, page blanche, erreurs cryptiques.

Une seule methode active a la fois. Si tu testes plusieurs approches, desactive systematiquement la precedente avant d'en activer une nouvelle.

Compresser uniquement certains types

Par defaut, ob_gzhandler compresse tout le contenu. Mais c'est inutile (et contre-productif) de compresser les images, les videos, ou les PDF qui sont deja compresses nativement. Pour exclure ces types :

<?php
$content_type = '';
foreach (headers_list() as $header) {
    if (stripos($header, 'Content-Type:') === 0) {
        $content_type = strtolower($header);
        break;
    }
}

$compress_types = ['text/html', 'text/css', 'text/javascript', 'application/javascript', 'application/json', 'text/xml', 'application/xml'];
$should_compress = false;
foreach ($compress_types as $type) {
    if (stripos($content_type, $type) !== false) {
        $should_compress = true;
        break;
    }
}

if ($should_compress && extension_loaded('zlib')) {
    ob_start('ob_gzhandler');
}

En pratique, sur un site WordPress classique, seul le HTML passe par PHP donc cette distinction est moins critique. Les CSS/JS/images sont servis directement par le serveur web.

Cas specifique : les API REST

Si ton site expose une API JSON (REST API WordPress, custom endpoints), la compression est encore plus importante. Une reponse API JSON typique se compresse a 85-90% de moins.

WordPress active la compression sur l'API REST automatiquement si elle est configuree au niveau serveur. Verifie :

curl -H "Accept-Encoding: gzip" -I https://monsite.fr/wp-json/wp/v2/posts

Si Content-Encoding: gzip n'apparait pas, ajoute dans ton functions.php :

add_action('rest_api_init', function () {
    if (!ini_get('zlib.output_compression') && extension_loaded('zlib')) {
        ob_start('ob_gzhandler');
    }
});

Brotli, l'alternative moderne

Depuis 2016, les navigateurs supportent Brotli, un algorithme de compression plus efficace que gzip (5-15% de mieux sur du HTML). PHP 8.x supporte Brotli si l'extension est installee :

if (extension_loaded('brotli') && in_array('br', explode(',', $_SERVER['HTTP_ACCEPT_ENCODING'] ?? ''))) {
    ob_start(function ($buffer) {
        return brotli_compress($buffer, 6);
    });
    header('Content-Encoding: br');
}

Sur les serveurs IONOS recents, l'extension PHP brotli n'est pas toujours dispo. Verifie :

php -m | grep -i brotli

Si pas disponible, reste sur gzip qui est universellement supporte.

Erreurs courantes et leur fix

Page blanche apres activation

Double compression. Verifie qu'il n'y a pas de mod_deflate active dans .htaccess en plus du PHP, ou que zlib.output_compression n'est pas deja On au niveau PHP global.

"Cannot modify header information"

Des headers ont deja ete envoyes avant ob_start. Verifie qu'il n'y a pas d'output (espace, BOM, echo) avant ton ob_start('ob_gzhandler').

Compression active mais score PageSpeed inchange

Verifie que tous les types MIME sont compresses, pas juste le HTML :

curl -H "Accept-Encoding: gzip" -I https://monsite.fr/wp-content/themes/montheme/style.css

Si le CSS n'est pas compresse, c'est que le serveur sert les assets statiques sans passer par PHP. Faut alors necessairement activer mod_deflate ou utiliser un CDN qui compresse.

Compression OK mais Brotli demande

PageSpeed te dit "Use Brotli". Si ton mutualise ne supporte pas Brotli, mets-toi derriere Cloudflare (gratuit) qui fait du Brotli automatiquement sur tous les assets.

Erreur 500 apres modification du .user.ini

Syntaxe du .user.ini invalide. Verifie qu'il n'y a pas de directive interdite. Certaines directives ne sont autorisees qu'au niveau system ou per-directory, pas user.

Impact sur les performances

Sur un WordPress classique de 150 Ko :

Gain de 1.5 a 1.6 secondes sur la premiere requete. Sur les Core Web Vitals (LCP), ca peut faire la difference entre "poor" et "good". Sur le SEO, c'est un facteur de classement direct depuis 2021.

Pour aller plus loin

Compression : la victoire facile

La compression HTTP c'est la technique d'optimisation web la plus rentable qui existe. Quelques minutes de configuration, aucun changement de code, et tes pages se chargent 3 a 4 fois plus vite. Si tu n'as pas active la compression sur ton site, c'est la premiere chose a faire avant meme de penser au cache, au CDN, ou a l'optimisation des images.

Sur un mutualise, la methode PHP est plus fiable que mod_deflate parce qu'elle ne depend pas de la config serveur. Choisis une seule methode, teste avec curl, verifie sur PageSpeed, et garde l'oeil sur le score. Si tu as encore des problemes, regarde aussi du cote Cloudflare gratuit qui ajoute une couche de compression au-dessus de la tienne sans rien casser. Et la prochaine fois qu'un client te dit "mon site est trop lent", tu commenceras par verifier ce header Content-Encoding.

# Articles similaires

Sur les memes sujets et plus loin