lamp-stack-docs — documentation technique interne — support N2 Dernière mise à jour : 2026-03-13 | Troubleshoot rapide
.--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/

LAMP Stack

DNS → HAProxy → Apache2 → PHP → MariaDB
Dernière modification : 2026-03-13 — Catégorie : Infrastructure — Auteur : Équipe Infra

1. Synopsis

Ce document décrit le fonctionnement d'une stack LAMP (Linux, Apache, MySQL/MariaDB, PHP) placée derrière un reverse proxy HAProxy avec terminaison TLS. Il s'adresse aux techniciens support de niveau 2 et couvre le parcours complet d'une requête HTTP : de la résolution DNS jusqu'à la réponse client.

Note Dans cette architecture, HAProxy remplace la couche SSL d'Apache. Apache n'écoute qu'en HTTP sur le réseau privé. Toute la terminaison TLS est centralisée sur HAProxy.

La stack typique utilise un conteneur LXC dédié par service, reliés par un bridge interne (vmbr1) sur un réseau privé 10.0.0.0/24. HAProxy est le seul point d'entrée exposé aux ports 80 et 443.

2. Architecture

2.1 Schéma réseau

                     ┌─────────────────────────────────────────┐
                     │            I N T E R N E T               │
                     └────────────────┬────────────────────────┘
                                     
                           ┌─────────┴─────────┐
                           │    DNS Resolver    │
                           │ exemple.fr → IP    │
                           └─────────┬─────────┘
                                     │ Port 443 (HTTPS)
                           ┌─────────┴─────────┐
                           │      HAProxy       │
                           │  Terminaison SSL   │
                           │  ACL Host routing  │
                           │  :80 → redirect    │
                           │  :443 → backends   │
                           └─────────┬─────────┘
                                     │ Port 80 (HTTP interne)
                           ┌─────────┴─────────┐
                           │     Apache2        │
                           │  VirtualHost       │
                           │  DocumentRoot      │
                           │  .php → PHP-FPM    │
                           └─────────┬─────────┘
                                     │ Socket UNIX
                           ┌─────────┴─────────┐
                           │      PHP-FPM       │
                           │  Exécute le code   │
                           │  Génère le HTML    │
                           └─────────┬─────────┘
                                     │ Socket / Port 3306
                           ┌─────────┴─────────┐
                           │     MariaDB        │
                           │  Stockage données  │
                           │  Requêtes SQL      │
                           └───────────────────┘

2.2 Rôle de chaque brique

ComposantRôleÉcouteConfig principale
DNSRésolution nom → IP53/udpZone chez le registrar
HAProxyReverse proxy, TLS termination, routage ACL80, 443/etc/haproxy/haproxy.cfg
Apache2Serveur web, VirtualHosts, délégation PHP80 (interne)/etc/apache2/sites-enabled/
PHP-FPMInterpréteur PHP, pool de workerssocket unix/etc/php/8.2/fpm/pool.d/
MariaDBSGBD, stockage persistant, requêtes SQL3306/etc/mysql/mariadb.conf.d/

3. Flux d'une requête

Un utilisateur saisit https://exemple.fr/index.php dans son navigateur. Voici ce qui se passe :

3.1 Simulateur interactif

Cliquez sur chaque étape pour voir le détail :

Étape 1 — Résolution DNS

Protocole : UDP/53Latence : ~5-50msActeur : résolveur DNS

Le navigateur interroge le résolveur DNS configuré (ex: 8.8.8.8). Le résolveur parcourt la hiérarchie : serveur racine → TLD (.fr) → serveur autoritaire pour obtenir l'enregistrement A. Le résultat est mis en cache selon le TTL.

Résultat : exemple.fr → 203.0.113.50

Étape 2 — Réception par HAProxy

Protocole : TCP/443Mode : HTTPActeur : HAProxy

Le paquet arrive sur le port 443. HAProxy effectue le handshake TLS (terminaison SSL), déchiffre le contenu et lit le header Host: exemple.fr. Il compare aux ACL définies et route vers le backend correspondant.

Si la requête arrive sur le port 80, HAProxy renvoie un 301 redirect vers HTTPS.

Étape 3 — Traitement Apache2

Protocole : TCP/80Interne uniquementActeur : Apache httpd

Apache reçoit la requête en HTTP pur (SSL déjà terminé). Il identifie le VirtualHost correspondant, localise le fichier dans le DocumentRoot.

Si c'est un .php, Apache le transmet à PHP-FPM via le socket UNIX. Les fichiers statiques (CSS, JS, images) sont servis directement.

Étape 4 — Exécution PHP-FPM

Socket UNIXWorker poolActeur : PHP-FPM

Un worker PHP-FPM libre prend la requête. Il exécute le script : initialisation $_GET, $_POST, $_SERVER, et si nécessaire, ouverture d'une connexion PDO vers MariaDB.

Le script génère le HTML de sortie retourné à Apache.

Étape 5 — Requête MariaDB

Socket ou TCP/3306Protocole MySQLActeur : MariaDB/InnoDB

PHP envoie une requête SQL via PDO ou mysqli. MariaDB l'exécute avec le moteur InnoDB : parsing → optimisation → exécution → retour du jeu de résultats.

Étape 6 — Réponse au client

Chemin inverse~50-200ms totalHTTP 200 OK

Le HTML généré remonte : MariaDB → PHP-FPM → Apache2 → HAProxy → Client

HAProxy réencapsule en TLS et l'envoie au navigateur. Les assets statiques déclenchent des requêtes supplémentaires qui suivent le même chemin sans passer par PHP.

Astuce N2 Pour identifier où une requête bloque, ajoutez des headers personnalisés à chaque couche. HAProxy injecte X-Forwarded-For et Apache peut loguer les temps de réponse backend avec %D dans le LogFormat.

4. Configurations

4.1 HAProxy

Le fichier principal /etc/haproxy/haproxy.cfg se compose de 4 sections : global, defaults, frontend et backend.

/etc/haproxy/haproxy.cfg
global
    log /dev/log local0
    maxconn 4096
    ssl-default-bind-options ssl-min-ver TLSv1.2

defaults
    log     global
    mode    http
    option  httplog
    timeout connect 5000ms
    timeout client  50000ms
    timeout server  50000ms

# ── Frontend : redirection HTTP → HTTPS ──
frontend http_front
    bind *:80
    redirect scheme https code 301

# ── Frontend : entrée HTTPS ──
frontend https_front
    bind *:443 ssl crt /etc/haproxy/certs/

    # ACL : routage par header Host
    acl host_www     hdr(host) -i exemple.fr
    acl host_api     hdr(host) -i api.exemple.fr

    use_backend backend_www    if host_www
    use_backend backend_api    if host_api
    default_backend backend_deny

# ── Backends ──
backend backend_www
    server web1 10.0.0.10:80 check inter 10s

backend backend_api
    server api1 10.0.0.11:80 check inter 10s

backend backend_deny
    http-request deny deny_status 403
Attention Toujours tester avant de redémarrer : haproxy -c -f /etc/haproxy/haproxy.cfg

4.2 Apache2

Apache n'écoute qu'en HTTP sur le réseau interne. Chaque site a son VirtualHost.

/etc/apache2/sites-available/exemple.conf
<VirtualHost *:80>
    ServerName    exemple.fr
    DocumentRoot  /var/www/exemple/public

    ErrorLog  ${APACHE_LOG_DIR}/exemple-error.log
    CustomLog ${APACHE_LOG_DIR}/exemple-access.log combined

    <Directory /var/www/exemple/public>
        Options       -Indexes +FollowSymLinks
        AllowOverride All
        Require       all granted
    </Directory>

    # Délégation PHP → PHP-FPM
    <FilesMatch \.php$>
        SetHandler "proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost"
    </FilesMatch>
</VirtualHost>
Commandes Apache utiles
shell
$ a2ensite exemple.conf              # Activer le site
$ a2enmod proxy_fcgi rewrite          # Activer les modules
$ apachectl -t                        # Tester la syntaxe
$ systemctl reload apache2             # Appliquer sans coupure
$ tail -f /var/log/apache2/error.log   # Logs temps réel

4.3 PHP-FPM

PHP-FPM gère un pool de processus pour exécuter le code PHP. Chaque site peut avoir son propre pool.

/etc/php/8.2/fpm/pool.d/exemple.conf
[exemple]
user  = www-data
group = www-data

; Socket UNIX — plus rapide que TCP en local
listen       = /run/php/php8.2-fpm.sock
listen.owner = www-data
listen.group = www-data

; Gestion dynamique du pool
pm                   = dynamic
pm.max_children      = 20     ; max simultanés
pm.start_servers     = 5      ; au démarrage
pm.min_spare_servers = 3      ; minimum idle
pm.max_spare_servers = 10     ; maximum idle

php_admin_value[error_log] = /var/log/php/exemple-error.log
php_admin_flag[log_errors] = on
Note pm = dynamic convient à la plupart des cas. Utilisez ondemand pour les sites à faible trafic et static pour le fort trafic prévisible.
Directives php.ini importantes
/etc/php/8.2/fpm/php.ini (extraits)
memory_limit       = 256M
max_execution_time = 30
upload_max_filesize = 50M
post_max_size      = 50M
display_errors     = Off   ; JAMAIS On en prod
log_errors         = On
extension=pdo_mysql
extension=mysqli

4.4 MariaDB

/etc/mysql/mariadb.conf.d/50-server.cnf
[mysqld]
bind-address = 127.0.0.1
port         = 3306

# InnoDB — adapter à la RAM dispo
innodb_buffer_pool_size        = 1G
innodb_log_file_size           = 256M
innodb_flush_log_at_trx_commit = 2

# Slow query log
slow_query_log      = 1
slow_query_log_file = /var/log/mysql/slow-query.log
long_query_time     = 1

character-set-server = utf8mb4
collation-server    = utf8mb4_unicode_ci
Créer un utilisateur et une base pour un site
mysql shell
mysql> CREATE DATABASE app_db
       CHARACTER SET utf8mb4
       COLLATE utf8mb4_unicode_ci;

mysql> CREATE USER 'app_user'@'localhost'
       IDENTIFIED BY '<MOT_DE_PASSE_FORT>';

mysql> GRANT ALL PRIVILEGES ON app_db.*
       TO 'app_user'@'localhost';

mysql> FLUSH PRIVILEGES;

4.5 SSL / Certbot

Avec HAProxy en frontal, Certbot fonctionne en mode standalone. Il faut temporairement libérer le port 80.

Procédure complète
# 1. Libérer le port 80
$ systemctl stop haproxy

# 2. Demander le certificat
$ certbot certonly --standalone \
    --http-01-port 80 \
    -d exemple.fr \
    --email admin@exemple.fr \
    --agree-tos --non-interactive

# 3. Combiner fullchain + privkey pour HAProxy
$ cat /etc/letsencrypt/live/exemple.fr/fullchain.pem \
      /etc/letsencrypt/live/exemple.fr/privkey.pem \
      > /etc/haproxy/certs/exemple.fr.pem

# 4. Rétablir HAProxy
$ systemctl start haproxy

# 5. Vérifier
$ echo | openssl s_client -connect exemple.fr:443 2>/dev/null \
    | openssl x509 -noout -dates
Important Cette procédure provoque une coupure de service pendant la génération. En prod, privilégiez le challenge DNS ou les hooks --pre-hook / --post-hook de Certbot.
Renouvellement automatique via cron
/etc/cron.d/certbot-haproxy
# Renouvellement mensuel à 3h du matin
0 3 1 * * root \
    systemctl stop haproxy && \
    certbot renew --quiet && \
    for domain in /etc/letsencrypt/live/*/; do \
        d=$(basename "$domain"); \
        cat "$domain/fullchain.pem" "$domain/privkey.pem" \
            > "/etc/haproxy/certs/$d.pem"; \
    done && \
    systemctl start haproxy

5. Ports & protocoles

ServicePortProtoDirectionRemarques
DNS53UDP/TCPClient → RésolveurAvant toute connexion
HAProxy (HTTP)80TCPClient → HAProxyRedirect 301 → HTTPS
HAProxy (HTTPS)443TLSClient → HAProxyTerminaison SSL + ACL
Apache280TCPHAProxy → ApacheHTTP interne uniquement
PHP-FPMUNIXApache → PHP-FPM/run/php/php-fpm.sock
MariaDB3306TCPPHP → MariaDBbind 127.0.0.1
Note Seuls les ports 80 et 443 sont exposés sur l'IP publique via DNAT. Le reste transite sur le réseau interne privé.

6. Troubleshooting

Diagnostiquez toujours de l'extérieur vers l'intérieur : DNS → HAProxy → Apache → PHP → BDD.

ERR_NAME_NOT_RESOLVED — Le domaine ne résout pas

L'enregistrement DNS A est absent ou pointe vers la mauvaise IP. Vérifier la zone chez le registrar et le TTL.

$ dig exemple.fr +short @8.8.8.8
ERR_CONNECTION_REFUSED — Rien n'écoute

HAProxy est down ou le firewall bloque. Vérifier le service et les règles iptables/DNAT.

$ ss -tlnp | grep -E ':80|:443' && systemctl status haproxy
503 Service Unavailable — Backend injoignable

HAProxy ne peut pas joindre Apache (conteneur down, IP incorrecte, Apache stoppé).

$ curl -v http://10.0.0.10:80 && systemctl status apache2
502 Bad Gateway — PHP-FPM ne répond plus

Le socket PHP-FPM est absent, mauvaises permissions, ou le service a crashé.

$ ls -la /run/php/php8.2-fpm.sock && systemctl status php8.2-fpm
Page blanche / HTTP 500 — Erreur fatale PHP

Erreur de syntaxe, extension manquante ou exception non catchée.

$ tail -30 /var/log/php/exemple-error.log
SQLSTATE[HY000] — Connexion BDD refusée

MariaDB stoppée, credentials incorrects ou droits SQL manquants.

$ systemctl status mariadb && mysql -u app_user -p -e "SHOW DATABASES;"
NET::ERR_CERT_DATE_INVALID — Certificat expiré

Le cron de renouvellement a échoué ou le .pem n'a pas été regénéré.

$ echo | openssl s_client -connect exemple.fr:443 2>/dev/null | openssl x509 -noout -dates
Lenteurs > 2s — Requêtes lentes

SQL lent, pool FPM saturé ou buffer pool InnoDB sous-dimensionné.

$ tail -20 /var/log/mysql/slow-query.log
Méthodologie N2 Toujours diagnostiquer de l'extérieur vers l'intérieur. Isolez la couche en défaut avant d'approfondir. Un curl -v à chaque étape suffit souvent à trouver.