Aller au contenu
medium

Apache HTTP Server : guide pratique (vhosts, TLS, performances)

42 min de lecture

Logo Apache HTTP Server

Apache HTTP Server est le serveur web historique du Web. Si vous devez héberger plusieurs sites sur un serveur, activer HTTPS, configurer un reverse proxy ou diagnostiquer une erreur 403, ce guide vous donne les recettes essentielles en quelques minutes.

Niveau : Débutant → Intermédiaire · Durée : 35-50 min

À la fin de ce guide, vous saurez :

  • Créer un Virtual Host pour héberger un site
  • Activer HTTPS avec Let's Encrypt (Certbot)
  • Configurer un reverse proxy vers une application
  • Choisir le bon MPM (prefork, worker, event)
  • Diagnostiquer les erreurs courantes (403, 404, 500)

Ce guide se lit dans l'ordre, mais chaque bloc est autonome. Si vous partez d'un serveur vierge, commencez par le Virtual Host : tout le reste (TLS, reverse proxy, réglages de performance) se greffe dessus. Si Apache tourne déjà et qu'une page renvoie une erreur, sautez directement au dépannage, la méthode de diagnostic y est donnée avant les causes.

Le rôle d'Apache tient en une phrase : il écoute sur un port TCP, interprète les requêtes HTTP qui arrivent, et décide pour chacune s'il renvoie un fichier du disque ou s'il transmet la demande à un autre programme. Tout le reste (Virtual Hosts, TLS, réécriture d'URL, compression) n'est que du réglage autour de cette boucle.

Apache (prononcé « a-pa-tchi ») est un serveur web qui :

  • Sert des fichiers statiques (HTML, CSS, JS, images)
  • Héberge plusieurs sites sur une seule machine (Virtual Hosts)
  • Proxyfie des applications backend (Node.js, Python, PHP)
  • Gère les modules dynamiquement (TLS, réécriture, cache)

Scénario : que se passe-t-il quand vous tapez une URL ?

Section intitulée « Scénario : que se passe-t-il quand vous tapez une URL ? »

Le schéma ci-dessous décompose le trajet d'une requête. Retenez surtout l'ordre des trois décisions qu'Apache prend, parce que c'est exactement l'ordre dans lequel vous devrez chercher une panne : il sélectionne d'abord le Virtual Host à partir de l'en-tête Host:, puis il résout le chemin demandé dans le DocumentRoot de ce vhost, puis il applique les blocs <Directory> qui décident d'autoriser ou de refuser la lecture.

Flux de traitement d'une requête HTTP par Apache

Une erreur 404 signale un échec à la deuxième étape, une 403 un échec à la troisième. Cette distinction évite de perdre du temps à corriger un DocumentRoot qui était correct depuis le début.

Pour la culture : historique d'Apache

Apache a été créé en 1995 par un groupe de développeurs qui ont pris le code du serveur NCSA HTTPd et l'ont "patché" (d'où le nom "A Patchy Server" → Apache). Il est rapidement devenu le serveur web le plus utilisé au monde.

En 2019, Nginx l'a dépassé en parts de marché, mais Apache reste très présent, notamment dans les environnements PHP traditionnels et les hébergements mutualisés.

L'architecture modulaire d'Apache et sa documentation exhaustive en font toujours un choix pertinent pour de nombreux cas d'usage.

Avant de plonger dans les commandes, assurez-vous de comprendre ces 3 concepts. Si vous les maîtrisez, tout le reste devient logique.

Question qu'on se pose tous : "Comment Apache sait quel site afficher quand j'ai plusieurs domaines ?"

Réponse : Grâce aux Virtual Hosts. Chaque VHost est un fichier de configuration qui dit :

  • ServerName : "Si le visiteur demande CE domaine..."
  • DocumentRoot : "...alors sers les fichiers de CE dossier"
# Traduction humaine de ce VHost :
# "Si quelqu'un demande www.boutique.com, envoie-lui le contenu de /var/www/boutique"
<VirtualHost *:80>
ServerName www.boutique.com # ← Le domaine
DocumentRoot /var/www/boutique # ← Le dossier des fichiers
</VirtualHost>

Question : "Pourquoi je dois 'activer' des trucs comme SSL ou rewrite ?"

Réponse : Apache est modulaire. Par défaut, il ne charge que le minimum pour économiser la RAM. Vous activez les fonctionnalités dont vous avez besoin :

ModuleCe qu'il faitQuand l'activer
sslHTTPSDès que vous voulez du chiffrement
rewriteRéécriture d'URLsWordPress, redirections, URLs propres
proxyReverse proxyQuand Apache est devant une app Node/Python
headersModifier les headers HTTPSécurité, cache, CORS
deflateCompression gzipToujours (gain de bande passante)

Question : "Pourquoi les tutos marchent jamais du premier coup ?"

Réponse : Parce que Debian et RHEL ont des conventions différentes :

AspectDebian/UbuntuRHEL/Rocky/Fedora
Nom du paquetapache2httpd
Nom du serviceapache2httpd
Config principale/etc/apache2/apache2.conf/etc/httpd/conf/httpd.conf
Activer un sitea2ensite monsite.confMettre le fichier dans /etc/httpd/conf.d/
Activer un modulea2enmod sslDécommenter dans les fichiers .conf
User qui exécute Apachewww-dataapache

Le paquet et le service ne portent pas le même nom selon la famille de distribution : apache2 côté Debian, httpd côté RHEL. Choisissez l'onglet correspondant à votre système, sinon chaque commande échouera avec un « Unit not found » déroutant. L'installation par le gestionnaire de paquets de la distribution est la bonne méthode : elle apporte les unités systemd, l'utilisateur de service et l'arborescence de configuration déjà en place.

Fenêtre de terminal
# Installer Apache
sudo apt update && sudo apt install apache2
# Démarrer et activer au boot
sudo systemctl enable --now apache2
# Vérifier le statut
sudo systemctl status apache2
# doit afficher : active (running)

Vérification : ouvrez http://votre-ip dans un navigateur, vous devez voir la page par défaut d'Apache.

Apache écoute sur le port 80 dès son démarrage, mais le pare-feu local filtre le trafic avant qu'il atteigne le processus. C'est la cause numéro un des « le service tourne mais le navigateur ne répond pas » depuis une autre machine : le test curl -I http://localhost passe (la boucle locale n'est pas filtrée) alors que l'accès distant échoue en timeout. Ouvrez les deux ports d'un coup, 80 pour le HTTP et 443 pour le HTTPS que vous activerez plus bas.

Fenêtre de terminal
sudo ufw allow 'Apache Full'
sudo ufw status

Les quatre étapes suivantes produisent un site accessible sur un domaine dédié, sans toucher au vhost par défaut. L'ordre compte : le répertoire et son propriétaire d'abord, la configuration ensuite, l'activation en dernier. Si vous écrivez le Virtual Host avant de créer le DocumentRoot, apache2ctl configtest répondra tout de même « Syntax OK » puisqu'il ne vérifie pas l'existence des chemins, et vous récolterez une 403 à la première requête.

  1. Créer le répertoire et les fichiers

    Fenêtre de terminal
    sudo mkdir -p /var/www/monsite
    echo '<h1>Mon site Apache</h1>' | sudo tee /var/www/monsite/index.html
    # Permissions : propriétaire = www-data (Debian) ou apache (RHEL)
    sudo chown -R www-data:www-data /var/www/monsite # Debian
    # sudo chown -R apache:apache /var/www/monsite # RHEL
  2. Créer le Virtual Host

    Fenêtre de terminal
    sudo tee /etc/apache2/sites-available/monsite.conf << 'EOF'
    <VirtualHost *:80>
    ServerName monsite.local
    DocumentRoot /var/www/monsite
    <Directory /var/www/monsite>
    Options -Indexes +FollowSymLinks
    AllowOverride None
    Require all granted
    </Directory>
    ErrorLog ${APACHE_LOG_DIR}/monsite-error.log
    CustomLog ${APACHE_LOG_DIR}/monsite-access.log combined
    </VirtualHost>
    EOF
  3. Activer le site et tester

    Fenêtre de terminal
    # Activer le site
    sudo a2ensite monsite.conf
    # Désactiver le site par défaut (optionnel)
    sudo a2dissite 000-default.conf
    # Tester la configuration
    sudo apache2ctl configtest
    # Syntax OK
    # Recharger Apache
    sudo systemctl reload apache2
  4. Vérifier

    Fenêtre de terminal
    curl -I http://localhost
    # HTTP/1.1 200 OK

Apache peut héberger plusieurs sites sur la même machine grâce aux Name-based Virtual Hosts.

Quand votre navigateur envoie une requête HTTP, il inclut un header Host: qui contient le domaine demandé :

GET /index.html HTTP/1.1
Host: www.site1.com ← C'est ça qu'Apache utilise
User-Agent: Mozilla/5.0...

Apache lit cet en-tête et cherche le Virtual Host correspondant. Le protocole HTTP/1.1 rend le champ Host: obligatoire précisément pour cette raison : la couche TCP ne transporte qu'une adresse IP et un port, ce qui ne suffit pas à distinguer deux sites hébergés au même endroit. Le nom du domaine ne circule donc que dans l'en-tête applicatif, et c'est la seule information dont Apache dispose pour trancher.

Imaginons que vous avez :

  • www.boutique.com → votre e-commerce
  • www.blog.com → votre blog

Les deux domaines pointent vers la même IP (votre serveur). Comment Apache fait la différence ?

Architecture Virtual Hosts Apache : plusieurs sites sur un serveur

Un site égale un fichier. Cette séparation n'est pas une coquetterie : elle permet d'activer ou de désactiver un site avec a2ensite / a2dissite sans toucher aux autres, et de repérer immédiatement quel fichier recharger après une modification. Dans les deux exemples ci-dessous, notez le couple ServerName / ServerAlias : le premier est le nom canonique, le second capte la variante sans www. Sans lui, site1.com tomberait sur le vhost par défaut au lieu du bon site. Les logs sont séparés par site, ce qui rend le diagnostic d'un site donné exploitable.

# /etc/apache2/sites-available/site1.conf (Debian)
# ou /etc/httpd/conf.d/site1.conf (RHEL)
<VirtualHost *:80>
ServerName www.site1.com
ServerAlias site1.com
DocumentRoot /var/www/site1
<Directory /var/www/site1>
Options -Indexes +FollowSymLinks
AllowOverride None
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/site1-error.log
CustomLog ${APACHE_LOG_DIR}/site1-access.log combined
</VirtualHost>
/etc/apache2/sites-available/site2.conf
<VirtualHost *:80>
ServerName www.site2.com
ServerAlias site2.com
DocumentRoot /var/www/site2
<Directory /var/www/site2>
Options -Indexes +FollowSymLinks
AllowOverride None
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/site2-error.log
CustomLog ${APACHE_LOG_DIR}/site2-access.log combined
</VirtualHost>

La ligne qui compte vraiment dans le tableau est la première : sans TLS, tout ce qui transite (mots de passe, cookies de session, contenu des formulaires) est lisible par n'importe quel équipement traversé. Les autres lignes sont des conséquences. Google traite le HTTPS comme un signal de classement depuis 2014, et les navigateurs marquent désormais les pages en clair comme non sécurisées dès qu'un champ de saisie est présent. Let's Encrypt rend l'argument du coût caduc : le certificat est gratuit, valable 90 jours, et Certbot automatise le renouvellement.

Sans HTTPSAvec HTTPS
Données en clair sur le réseauTout est chiffré
Google pénalise votre SEOBonus de classement
Navigateurs affichent "Non sécurisé"Cadenas vert
Vulnérable au man-in-the-middleCommunication sécurisée
  1. Installer Certbot

    Fenêtre de terminal
    sudo apt install certbot python3-certbot-apache
  2. Obtenir le certificat

    Fenêtre de terminal
    sudo certbot --apache -d exemple.com -d www.exemple.com

    Certbot :

    • Vérifie que vous contrôlez le domaine
    • Obtient un certificat Let's Encrypt
    • Modifie automatiquement votre Virtual Host
    • Configure la redirection HTTP → HTTPS
  3. Vérifier le renouvellement automatique

    Fenêtre de terminal
    sudo certbot renew --dry-run

    Le timer systemd gère le renouvellement automatique (environ 30 jours avant expiration).

Certbot produit une configuration fonctionnelle, pas une configuration durcie. Les directives ci-dessous ferment les protocoles obsolètes et ajoutent HSTS. Deux points méritent votre attention avant de copier ce bloc. D'abord SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 : la syntaxe soustractive part de tout ce que la bibliothèque OpenSSL propose et retire les versions cassées, ce qui laisse TLS 1.2 et 1.3. Ensuite Strict-Transport-Security avec un max-age d'un an : cet en-tête indique au navigateur de ne plus jamais tenter le HTTP en clair sur ce domaine, et il est irréversible côté client pendant toute la durée annoncée. Ne l'activez qu'une fois le HTTPS confirmé fonctionnel, sinon vous rendez le site inaccessible aux visiteurs déjà venus.

<VirtualHost *:443>
ServerName exemple.com
DocumentRoot /var/www/exemple
SSLEngine On
SSLCertificateFile /etc/letsencrypt/live/exemple.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/exemple.com/privkey.pem
# TLS moderne uniquement
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
SSLHonorCipherOrder off
# HSTS (activer seulement quand HTTPS fonctionne)
Header always set Strict-Transport-Security "max-age=31536000"
<Directory /var/www/exemple>
Options -Indexes +FollowSymLinks
AllowOverride None
Require all granted
</Directory>
</VirtualHost>
# Redirection HTTP → HTTPS
<VirtualHost *:80>
ServerName exemple.com
Redirect permanent / https://exemple.com/
</VirtualHost>

Un reverse proxy est un intermédiaire entre les visiteurs et vos applications. Au lieu que votre app Node.js ou Python écoute directement sur le port 80, c'est Apache qui reçoit les requêtes et les transmet à votre app.

Apache reverse proxy : comparaison avec et sans

Le tableau suivant part des questions que se posent réellement les développeurs au moment de mettre une application en ligne. Le fil conducteur est toujours le même : le port 80 et le port 443 sont des ports privilégiés, qu'un processus non root ne peut pas ouvrir sous Linux. Faire tourner une application Node.js ou Python en root pour y accéder est un risque inutile ; Apache démarre en root, ouvre les ports, puis abandonne ses privilèges vers www-data ou apache, et parle à votre application sur un port haut en loopback.

QuestionRéponse
"Mon app Node écoute sur 3000, comment la mettre sur le port 80 ?"Le reverse proxy redirige 80 → 3000
"Je veux du HTTPS mais mon framework ne le gère pas bien"Apache gère TLS, votre app reste en HTTP local
"J'ai 3 apps sur un serveur, comment les exposer ?"Un VHost par app, chacun proxyfie vers un port différent
"Je veux des logs Apache standardisés"Le proxy loggue tout, votre app n'a pas à s'en occuper

Résumé des trois différences structurantes. La deuxième est celle qui a le plus d'impact sur votre travail quotidien : en déportant le TLS vers Apache, vous n'avez plus qu'un seul endroit où gérer les certificats, quel que soit le nombre d'applications derrière. Ce schéma porte le nom de terminaison TLS, et il implique que le trafic entre Apache et l'application circule en clair. C'est acceptable sur 127.0.0.1, pas entre deux machines distinctes.

Sans reverse proxyAvec reverse proxy (Apache)
Votre app écoute sur le port 80Apache écoute sur 80/443, votre app sur 3000
Pas de HTTPS (ou config manuelle)Apache gère TLS
Une seule app par serveurPlusieurs apps sur le même serveur

Le proxy d'Apache se découpe en plusieurs modules qui se complètent. proxy apporte le socle, proxy_http ajoute le dialogue HTTP avec le backend, proxy_balancer et lbmethod_byrequests ne servent qu'à répartir la charge sur plusieurs backends. Pour une seule application, les deux premiers suffisent. Après activation, un httpd -M | grep proxy doit lister les modules chargés : si la sortie est vide, inutile de chercher plus loin dans la configuration, la directive ProxyPass sera simplement inconnue et configtest échouera.

Fenêtre de terminal
sudo a2enmod proxy proxy_http proxy_balancer lbmethod_byrequests
sudo systemctl restart apache2

Trois directives portent tout le comportement. ProxyPass envoie les requêtes vers le backend ; ProxyPassReverse fait le trajet inverse en réécrivant les en-têtes Location, Content-Location et URI des réponses, sans quoi une redirection émise par l'application renverrait le navigateur vers http://127.0.0.1:3000/, une adresse qui n'existe pas chez le visiteur. ProxyPreserveHost On transmet le Host: d'origine au lieu de le remplacer par celui de la cible, ce dont dépendent la plupart des frameworks pour construire leurs URL absolues.

<VirtualHost *:80>
ServerName api.exemple.com
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
# mod_proxy_http ajoute déjà X-Forwarded-For, X-Forwarded-Host
# et X-Forwarded-Server (ProxyAddHeaders est On par défaut).
# Seul le protocole d'origine reste à déclarer :
RequestHeader set X-Forwarded-Proto "http"
ErrorLog ${APACHE_LOG_DIR}/api-error.log
CustomLog ${APACHE_LOG_DIR}/api-access.log combined
</VirtualHost>

N'écrivez pas de X-Real-IP à la main : mod_proxy_http renseigne déjà X-Forwarded-For avec l'IP du client, ainsi que X-Forwarded-Host et X-Forwarded-Server, tant que ProxyAddHeaders reste à sa valeur par défaut On. En revanche X-Forwarded-Proto n'est pas ajouté automatiquement : c'est à vous de le poser, avec la valeur http dans un vhost sur le port 80 et https dans le vhost sur le port 443. Une valeur figée à http recopiée dans le vhost TLS fait générer des liens en clair par l'application et déclenche des boucles de redirection.

Le cas le plus fréquent en production : un front statique servi par Apache et une API proxyfiée sous un préfixe. Surveillez les barres obliques finales, elles ne sont pas décoratives. Avec ProxyPass /api/ http://127.0.0.1:3000/, une requête sur /api/clients arrive au backend en /clients : le préfixe est retiré. Si votre application attend au contraire de recevoir /api/clients, la cible doit s'écrire http://127.0.0.1:3000/api/. Mélanger une source avec barre finale et une cible sans barre produit des chemins concaténés de travers et des 404 côté backend alors qu'Apache, lui, répond correctement.

<VirtualHost *:80>
ServerName exemple.com
DocumentRoot /var/www/frontend
# L'API sur /api/*
ProxyPass /api/ http://127.0.0.1:3000/
ProxyPassReverse /api/ http://127.0.0.1:3000/
# Le reste en statique
<Directory /var/www/frontend>
Options -Indexes +FollowSymLinks
AllowOverride None
Require all granted
</Directory>
</VirtualHost>

MPM signifie Multi-Processing Module. C'est la façon dont Apache gère les requêtes en parallèle.

Question fréquente : "Pourquoi Apache est lent / consomme trop de RAM ?"

Réponse courte : Vous utilisez probablement le mauvais MPM.

Un seul MPM est actif à la fois, il se choisit au chargement des modules et impose un redémarrage complet pour en changer (systemctl reload ne suffit pas). La colonne de droite décrit le modèle d'exécution, et c'est lui qui détermine la consommation mémoire : prefork duplique un processus entier par connexion simultanée, là où worker et event partagent un processus entre plusieurs threads. Sur les distributions récentes, event est le MPM installé par défaut.

MPMComment ça marche
prefork1 processus par requête. Simple mais gourmand en RAM.
workerProcessus + threads. Plus efficace.
eventComme worker, mais gère mieux les connexions keepalive.

Le schéma compare les deux extrêmes. À gauche, prefork : chaque connexion mobilise un processus complet, avec sa propre copie de la mémoire, ce qui plafonne vite le nombre de clients simultanés sur une machine modeste. À droite, event : un petit nombre de processus héberge des threads, et un thread dédié conserve les connexions keep-alive inactives sans immobiliser de worker. C'est ce dernier point qui fait la différence sur un site réel, où la majorité des connexions ouvertes ne transportent aucune donnée à un instant donné.

Comparaison des MPM Apache : prefork vs event

La décision se prend en une question : est-ce que quelque chose dans votre pile impose des processus séparés ? Une seule réponse le fait vraiment, mod_php, dont les extensions ne sont pas toutes sûres en environnement multithread. Dans tous les autres cas, y compris PHP-FPM qui exécute PHP hors d'Apache, event est le bon choix. La colonne « Pourquoi » donne le critère technique à vérifier chez vous plutôt qu'une règle à appliquer aveuglément.

SituationMPM recommandéPourquoi
PHP avec mod_phppreforkmod_php n'est pas thread-safe
PHP-FPM / proxy_fcgieventMeilleure gestion des connexions
Reverse proxyeventGère mieux les connexions keepalive
Beaucoup de connexionseventPlus efficace en mémoire
Compatibilité maximalepreforkFonctionne avec tout

Sur Debian 12 et Ubuntu récents, mpm_event est déjà le MPM par défaut : apache2ctl -V | grep MPM le confirme avant toute manipulation. La bascule ci-dessous ne concerne donc que les serveurs passés à prefork par une installation de libapache2-mod-php. Un point de vigilance : les deux MPM sont mutuellement exclusifs, il faut désactiver l'ancien avant d'activer le nouveau, sinon a2enmod refuse l'opération. Et le changement exige un restart, pas un reload.

Fenêtre de terminal
# Désactiver prefork, activer event
sudo a2dismod mpm_prefork
sudo a2enmod mpm_event
sudo systemctl restart apache2

La compression gzip s'applique à la volée aux réponses textuelles et réduit typiquement leur taille de 60 à 80 %, ce qui se voit surtout sur les gros fichiers CSS et JavaScript. La sélection se fait par type MIME et pas par extension : c'est l'en-tête Content-Type de la réponse qui décide. N'ajoutez jamais les images JPEG, PNG ou WebP à cette liste, elles sont déjà compressées et le passage par mod_deflate consomme du CPU pour un gain nul, voire négatif. Vérification après rechargement : curl -sI -H 'Accept-Encoding: gzip' https://exemple.com/style.css doit afficher Content-Encoding: gzip.

<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml
AddOutputFilterByType DEFLATE text/css application/javascript
AddOutputFilterByType DEFLATE application/json application/xml
</IfModule>
Fenêtre de terminal
sudo a2enmod deflate
sudo systemctl restart apache2

mod_expires ajoute un en-tête Cache-Control: max-age aux réponses, ce qui autorise le navigateur à réutiliser un fichier sans réinterroger le serveur. Les durées ci-dessous sont volontairement asymétriques : un an pour les images, qui changent rarement, un mois pour le CSS et le JavaScript, plus souvent retouchés. Le piège arrive à la mise en production suivante : un visiteur qui a déjà le fichier en cache ne verra pas votre correction avant l'expiration. La parade standard consiste à faire varier le nom du fichier à chaque build (app.4f2c1a.css), technique que la plupart des outils de packaging appliquent d'office.

<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
</IfModule>

Ce bloc regroupe le strict nécessaire, à poser une fois pour toutes au niveau global. ServerTokens Prod réduit l'en-tête Server: au seul mot Apache, sans numéro de version : cela ne bloque aucune attaque mais retire de la liste des cibles faciles des scanners qui filtrent par version. Les trois en-têtes de sécurité protègent les visiteurs, pas le serveur : nosniff empêche le navigateur de deviner un type MIME et d'exécuter comme script un fichier téléversé, SAMEORIGIN bloque l'inclusion de vos pages dans une iframe tierce, Referrer-Policy limite la fuite d'URL vers les sites externes. Le mot-clé always est indispensable : sans lui, les en-têtes disparaissent des réponses d'erreur, précisément celles qu'un attaquant cherche à provoquer.

# Dans /etc/apache2/conf-available/security.conf (Debian)
# ou /etc/httpd/conf.d/security.conf (RHEL)
# Masquer la version d'Apache
ServerTokens Prod
ServerSignature Off
# Headers de sécurité
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>
# Désactiver le listing des répertoires (global)
<Directory />
Options -Indexes
</Directory>
Fenêtre de terminal
sudo a2enmod headers
sudo a2enconf security
sudo systemctl restart apache2

Un site statique n'a besoin que de trois méthodes HTTP. Tout le reste (PUT, DELETE, TRACE, les verbes WebDAV) n'a aucune raison d'être accepté et constitue une surface d'attaque gratuite. La directive <LimitExcept> applique la restriction à toutes les méthodes sauf celles qu'elle énumère, ce qui la rend robuste : une méthode exotique ajoutée par un futur module reste refusée sans que vous ayez à mettre la liste à jour. C'est l'inverse de <Limit>, qui n'agit que sur les verbes explicitement nommés et laisse passer tous les autres : une liste noire oubliée reste ouverte, une liste blanche non.

<Directory /var/www/monsite>
<LimitExcept GET POST HEAD>
Require all denied
</LimitExcept>
</Directory>

Quand Apache ne fonctionne pas, suivez toujours cet ordre. 90% des problèmes se résolvent en moins de 2 minutes :

Méthode de diagnostic Apache en 4 étapes

Toujours tester la configuration avant de recharger :

Fenêtre de terminal
# Debian/Ubuntu
sudo apache2ctl configtest
# RHEL/Rocky
sudo httpd -t

Pourquoi ? Contrairement à une idée répandue, un systemctl reload sur une configuration cassée ne tue pas le serveur : la commande se traduit par apachectl graceful, qui lance un contrôle de syntaxe avant d'envoyer le signal et refuse le rechargement si la configuration est invalide. Le processus continue de tourner avec l'ancienne configuration, et vous ne voyez rien passer côté visiteurs.

Le vrai danger est le systemctl restart, qui arrête le processus avant de le relancer : si la configuration est fautive, le redémarrage échoue et le site est hors ligne jusqu'à correction. D'où le réflexe : configtest d'abord, action ensuite. Le test rend en prime la ligne et le fichier exacts de l'erreur, ce que le journal systemd ne montre pas toujours.

Enchaînez ces cinq commandes dans l'ordre, elles vont du plus général au plus précis. La cinquième, apache2ctl -S, est la plus sous-estimée : elle affiche la table complète des Virtual Hosts telle qu'Apache l'a réellement construite, avec pour chaque domaine le fichier et la ligne d'origine. C'est le seul moyen fiable de voir qu'un vhost oublié capte le trafic à la place du vôtre, ou qu'un ServerName est en double. Gardez la sortie sous les yeux avant de modifier quoi que ce soit.

Fenêtre de terminal
# 1. Apache tourne-t-il ?
systemctl status apache2 # ou httpd
# 2. La config est-elle valide ?
sudo apache2ctl configtest # ou httpd -t
# 3. Quels modules sont actifs ?
apache2ctl -M # ou httpd -M
# 4. Logs d'erreur récents
sudo tail -20 /var/log/apache2/error.log # Debian
sudo tail -20 /var/log/httpd/error_log # RHEL
# 5. Quel Virtual Host répond ?
sudo apache2ctl -S # ou httpd -S

Ce que ça veut dire : Apache a trouvé le fichier, mais refuse de le servir.

Causes possibles (dans l'ordre de fréquence) :

  1. Permissions fichiers : Apache (user www-data ou apache) ne peut pas lire le fichier

    Fenêtre de terminal
    # Vérifier
    ls -la /var/www/monsite/
    # Corriger
    sudo chown -R www-data:www-data /var/www/monsite # Debian
    sudo chmod -R 755 /var/www/monsite
  2. Directive Require all denied dans la config (ou absence de Require all granted)

  3. SELinux (RHEL/Rocky) bloque l'accès

    Fenêtre de terminal
    # Vérifier
    getenforce
    # Si "Enforcing", corriger les contextes :
    sudo restorecon -Rv /var/www/monsite

Ce que ça veut dire : Apache ne trouve pas le fichier demandé.

Causes possibles :

  • DocumentRoot incorrect : le dossier dans le VHost ne correspond pas à la réalité
  • Fichier absent : index.html n'existe pas
  • Typo dans l'URL : /inex.html au lieu de /index.html
Fenêtre de terminal
# Vérifier que le fichier existe vraiment
ls -la /var/www/monsite/index.html

Ce que ça veut dire : Apache a planté en essayant de répondre.

Causes possibles :

  • Erreur dans .htaccess : syntaxe invalide, module manquant
  • Script PHP/Python qui plante

Solution : Toujours regarder le log d'erreur :

Fenêtre de terminal
sudo tail -30 /var/log/apache2/error.log

Ce que ça veut dire : Apache affiche la page par défaut au lieu de votre site.

Causes possibles :

  • VHost non activé : a2ensite monsite.conf oublié
  • ServerName incorrect : ne correspond pas au domaine demandé
Fenêtre de terminal
# Voir quel VHost répond pour quel domaine
apache2ctl -S

À garder sous la main pendant une astreinte. La dernière ligne mérite une précision : AH00558 n'est pas une erreur mais un avertissement au démarrage, émis quand aucun ServerName global n'est défini. Apache démarre malgré tout en devinant un nom à partir de l'adresse IP. Il vaut mieux le faire taire en posant un ServerName explicite, sinon ce message parasite chaque lecture du journal et masque les vraies erreurs.

ErreurCause probableSolution
403 ForbiddenPermissions ou Require all deniedVoir ci-dessus
404 Not FoundDocumentRoot ou fichier absentVérifier le chemin
500 Internal Server Error.htaccess ou script casséRegarder error.log
"It works"VHost non activéa2ensite + apache2ctl -S
AH00558: Could not reliably...ServerName global absentAjouter ServerName localhost

Une 403 vient de l'une de trois couches, à éliminer dans cet ordre parce que la première est la plus fréquente et la plus rapide à vérifier. Détail que beaucoup ratent : Apache a besoin du droit de traversée (x) sur tous les répertoires parents, pas seulement sur le fichier. Un /var/www/monsite en 750 appartenant à votre compte utilisateur renverra une 403 même si index.html est en 644.

Fenêtre de terminal
# 1. Vérifier les permissions, y compris celles des dossiers parents
namei -l /var/www/monsite/index.html
# Le user www-data (ou apache) doit pouvoir traverser chaque niveau
# 2. Vérifier la config Directory
grep -A5 "Directory /var/www/monsite" /etc/apache2/sites-enabled/*
# 3. SELinux (RHEL) ?
getenforce
# Si "Enforcing", relire les refus plutôt que désactiver :
sudo ausearch -m avc -ts recent
# Puis corriger les contextes :
sudo restorecon -Rv /var/www/monsite

Les quatre tableaux qui suivent sont faits pour être consultés en cours de manipulation, pas lus d'affilée. La séparation par famille de distribution est volontaire : les commandes a2* sont des scripts fournis par Debian, absents de RHEL, où l'activation d'un site ou d'un module passe par le placement d'un fichier ou l'édition d'un LoadModule.

Ces sept commandes couvrent l'essentiel du quotidien. Toutes les commandes a2* se contentent de gérer des liens symboliques entre sites-available et sites-enabled (idem pour les modules), ce qui explique qu'elles réclament ensuite un reload pour prendre effet : elles ne parlent jamais au processus Apache.

CommandeDescription
a2ensite monsite.confActiver un site
a2dissite monsite.confDésactiver un site
a2enmod rewriteActiver un module
a2dismod rewriteDésactiver un module
apache2ctl configtestTester la configuration
apache2ctl -MLister les modules actifs
apache2ctl -SLister les Virtual Hosts

Côté RHEL, il n'y a pas d'outillage d'activation : c'est le binaire httpd lui-même qui rend ces services, avec les mêmes options que apache2ctl. Notez qu'il n'existe pas d'équivalent à a2ensite : tout fichier .conf déposé dans /etc/httpd/conf.d/ est chargé au prochain démarrage, sans étape supplémentaire. Désactiver un site revient donc à renommer ou déplacer son fichier.

CommandeDescription
httpd -tTester la configuration
httpd -MLister les modules actifs
httpd -SLister les Virtual Hosts

La ligne à mémoriser est celle des logs, parce que c'est là que vous irez à chaque incident. Attention au nom du fichier, il diffère : error.log avec un point sur Debian, error_log avec un tiret bas sur RHEL. Un tail qui répond « No such file or directory » sur un serveur qui tourne vient presque toujours de cette confusion.

ÉlémentDebian/UbuntuRHEL/Rocky
Config principale/etc/apache2/apache2.conf/etc/httpd/conf/httpd.conf
Sites disponibles/etc/apache2/sites-available/-
Sites activés/etc/apache2/sites-enabled//etc/httpd/conf.d/
Modules/etc/apache2/mods-available//etc/httpd/conf.modules.d/
Logs/var/log/apache2//var/log/httpd/
DocumentRoot par défaut/var/www/html/var/www/html

Six directives suffisent à écrire un Virtual Host correct. Deux d'entre elles se placent à l'intérieur d'un bloc <Directory> et nulle part ailleurs, Options et AllowOverride ; les autres vivent au niveau du <VirtualHost>. Se tromper de niveau produit soit un « Syntax OK » suivi d'un comportement inattendu, soit une erreur explicite de contexte au configtest.

DirectiveDescription
ServerName www.exemple.comNom du site
ServerAlias exemple.comAlias (sans www)
DocumentRoot /var/www/siteRacine des fichiers
Options -IndexesDésactiver le listing
AllowOverride NoneIgnorer les .htaccess
Require all grantedAutoriser l'accès

Ces six erreurs représentent l'essentiel des tickets ouverts sur des serveurs Apache mal réglés. Les deux dernières lignes sont les plus coûteuses en temps parce qu'elles ne produisent aucun message clair : SELinux renvoie une 403 identique à celle d'un problème de permissions, et l'association mod_php avec le MPM event provoque des plantages intermittents impossibles à reproduire à la demande. Prenez le réflexe de vérifier getenforce et apache2ctl -M | grep php avant d'entamer une recherche longue.

ErreurConséquenceSolution
AllowOverride All partoutPerf dégradée, sécurité réduiteAllowOverride None + config centrale
Oublier a2enmod sslHTTPS ne fonctionne pasActiver le module avant Certbot
Permissions 777Faille de sécurité majeure755 pour dossiers, 644 pour fichiers
Confondre Debian et RHELChemins et commandes différentsVérifier la distribution
SELinux non configuré403 mystérieux sur RHELsetsebool -P httpd_can_network_connect 1
mod_php + MPM eventCrash ou comportement erratiqueUtiliser prefork avec mod_php
  1. apache2ctl configtest : toujours tester avant de recharger
  2. Virtual Host = un site, avec son ServerName et DocumentRoot
  3. AllowOverride None : la règle en production
  4. MPM event : pour les perfs (sauf mod_php)
  5. Certbot : HTTPS en une commande
  6. Logs : votre meilleur ami pour débugger

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn