Comment installer HAProxy avec Docker Compose sur un VPS Ubuntu
Un service web sur un VPS est facile à exposer directement — jusqu’à ce que vous vouliez une seule porte d’entrée publique propre, la liberté de changer le backend plus tard, ou un moyen plus sûr d’arrêter d’envoyer du trafic vers quelque chose de cassé. C’est à ce moment qu’un proxy cesse de sembler « quelque chose pour les grandes équipes d’infrastructure » et commence à sembler pratique.

HAProxy convient bien à ce rôle. Pensez-y comme au gestionnaire de trafic assis devant votre application : les requêtes frappent d’abord HAProxy, et HAProxy décide où elles doivent aller ensuite. Vous n’avez pas besoin d’un grand cluster pour en bénéficier. Même sur un VPS Ubuntu 24.04 unique, cela vous donne une séparation plus propre entre Internet et le service que vous exécutez réellement.
Ce guide garde le premier déploiement intentionnellement structuré : un VPS Ubuntu 24.04, Docker Compose, un conteneur HAProxy, un backend de démonstration, et la preuve que le routage fonctionne vraiment.
Pourquoi HAProxy est Important Avant d’en Avoir Besoin
Imaginez un petit VPS exécutant une application correctement aujourd’hui. Il répond sur un port, le site se charge, et tout semble bon. Les frictions commencent quand vous voulez un point d’entrée public stable, la possibilité de remplacer le backend plus tard sans changer l’adresse publique, ou une couche frontale qui peut arrêter d’envoyer du trafic à un service défaillant. Exposer l’application directement commence à sembler fragile étonnamment rapidement.

Ces exigences pointent toutes vers la même couche manquante : un point d’entrée contrôlé entre Internet et votre application. HAProxy fournit cette couche. Les clients se connectent d’abord à HAProxy, et HAProxy décide où chaque requête va ensuite.
Cette séparation est utile même avant d’avoir plusieurs serveurs. Elle vous donne un bord public plus propre maintenant et un chemin plus sûr pour des changements ultérieurs tels que le remplacement du backend, le routage conscient de la santé, et HTTPS. Le reste du guide montre ce modèle dans sa forme de travail la plus simple et le vérifie avec un chemin de requête réel.
Termes rapides de HAProxy qui facilitent le reste de ce guide

Vous n’avez besoin que d’un petit ensemble de vocabulaire pour suivre un premier déploiement de HAProxy en toute confiance. Le tableau ci-dessous couvre les termes qui importent dans ce guide.
| Terme | Signification en français clair |
|---|---|
| 🌐 reverse proxy | Un service en première ligne qui reçoit les demandes en premier et les transmet à un autre service interne. |
| ⚖️ load balancer | Une couche frontale qui peut distribuer les demandes sur plusieurs cibles backend. |
| 🚪 frontend | L’endroit où les clients se connectent à HAProxy. |
| 🧩 backend | Le service ou le serveur auquel HAProxy envoie la demande ensuite. |
| ❤️ health check | Un moyen pour HAProxy de remarquer si un backend doit continuer à recevoir du trafic. |
| 🐳 image | Un modèle d’application empaqueté utilisé pour créer des conteneurs. |
| 📦 container | Une instance en cours d’exécution d’une image. |
Pour ce guide, reverse proxy est le premier modèle mental à garder à l’esprit. HAProxy se place devant quelque chose d’autre et contrôle la transmission. Load balancing est la capacité étendue qui devient utile lorsque vous ajoutez plusieurs serveurs backend ultérieurement.
Les deux termes qui importent le plus une fois que vous ouvrez la config sont frontend et backend. Le frontend est l’endroit où le client arrive. Le backend est l’endroit où HAProxy envoie la demande ensuite. Un health check importe car il permet à HAProxy de remarquer quand une cible doit cesser de recevoir du trafic.
Ce que HAProxy fait bien — et ce que ce guide omet intentionnellement

Si vous imaginez votre stack comme un immeuble de bureaux, HAProxy est la réception : le trafic y arrive d’abord, est dirigé vers la bonne salle, et cesse d’être envoyé vers une salle qui est clairement indisponible.
Dans ce guide, cela se traduit par trois tâches pertinentes pour les débutants :
- accepter les requêtes HTTP entrantes
- les transférer au backend de démonstration
- surveiller si ce backend est suffisamment sain pour continuer à recevoir du trafic
C’est déjà utile avec un seul backend car cela vous donne un point d’accès public contrôlé devant l’application.
Plus tard, le même modèle se met à l’échelle facilement. Vous pouvez remplacer le backend, ajouter d’autres backends, introduire HTTPS, ou laisser HAProxy répartir le trafic sur plusieurs cibles au lieu d’une seule. Pour garder cette première étape pédagogique, ce guide reste en mode HTTP et omet intentionnellement la terminaison TLS, les ACLs, la limitation de débit, les tables de stick, et les paires HA. Ce sont tous des sujets HAProxy réels. Ils ne sont simplement pas le bon point de départ pour un premier déploiement fonctionnel.
Ce que vous construisez et ce dont vous avez besoin d’abord

Avant de créer des fichiers, il est utile de voir la forme finale de la pile. Le déploiement dans ce guide ressemble à ceci :
Client browser or curl
|
v
HAProxy frontend (:80)
|
v
demo backend service (demo:5678)
Optional local-only validation:
HAProxy stats frontend (127.0.0.1:8404/stats)Docker Compose est le chemin principal ici car il garde la première installation reproductible, visible et facile à modifier. Au lieu de construire une image personnalisée le premier jour, vous gardez la configuration HAProxy sur l’hôte, la montez dans le conteneur et démarrez toute la pile à partir d’un seul fichier. Sur un VPS Ubuntu auto-géré — par exemple, un VPS AlexHost — c’est un ajustement propre car la disposition reste facile à inspecter.
💡 Conseil : Ce guide utilise Docker Compose plus un haproxy.cfg monté en bind à dessein. C’est le chemin d’installation initial le plus transparent car vous pouvez modifier la configuration du proxy directement sans ajouter une étape de construction d’image.
Avant de commencer, assurez-vous d’avoir ces éléments de base en place :
- Ubuntu 24.04 VPS
- Docker Engine installé
- Docker Compose v2 disponible via docker compose
- Accès au terminal et permission pour exécuter Docker
- Port 80 disponible sur l’hôte
- HTTP entrant autorisé si vous utilisez UFW ou des règles de pare-feu côté fournisseur
D’abord, vérifiez votre version d’Ubuntu
lsb_release -a
Ensuite, confirmez que Docker et Compose moderne sont disponibles :
docker --version
docker compose version
Si les deux commandes retournent des informations de version, le côté runtime du conteneur est prêt et vous pouvez rester concentré sur HAProxy au lieu de faire un détour dans l’installation de Docker.
Ensuite, assurez-vous que le port 80 n’est pas déjà utilisé, puis vérifiez si UFW est actif et si HTTP est déjà autorisé :
sudo ss -tlnp | grep -E ':(80)s' || true
sudo ufw status
sudo ufw allow 80/tcp
✏️ NOTE : Aucune sortie de la vérification ss signifie généralement que le port 80 est libre. Si vous voyez nginx, apache2, caddy ou un autre service déjà à l’écoute, réglez d’abord cela. C’est une étape de vérification préalable de dix secondes qui évite beaucoup de confusion plus tard.
Dans l’exemple ci-dessus, sudo ufw status affiche Status: active, et 80/tcp est déjà présent dans la liste d’autorisation. C’est pourquoi sudo ufw allow 80/tcp retourne Skipping adding existing rule au lieu d’ajouter une nouvelle règle. Cette sortie est normale et signifie simplement que la règle de pare-feu était déjà en place.
Créer le dossier du projet et le fichier Compose
Commencez par créer un petit dossier de projet pour les deux fichiers dont ce premier déploiement a besoin :
mkdir -p ~/haproxy-docker
cd ~/haproxy-docker
Après cela, la disposition doit être aussi minimaliste que possible :
~/haproxy-docker/
├── compose.yaml
└── haproxy.cfgCréez maintenant compose.yaml et utilisez ce contenu exact :
services:
demo:
image: hashicorp/http-echo:1.0
command: ["-listen=:5678", "-text=Hello from the HAProxy demo backend"]
restart: unless-stopped
haproxy:
image: haproxy:3.4.1
depends_on:
- demo
ports:
- "80:80"
- "127.0.0.1:8404:8404"
volumes:
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
sysctls:
net.ipv4.ip_unprivileged_port_start: "0"
restart: unless-stoppedCe fichier connecte les conteneurs ensemble, mais il ne définit pas encore la logique de requête HAProxy. Il indique à Docker quelles images exécuter, quels ports publier et où le fichier de configuration HAProxy sera monté depuis l’hôte.
Les paramètres suivants sont ceux qui importent le plus pour un premier déploiement propre :
| Paramètre Compose | Pourquoi c’est ici |
|---|---|
| hashicorp/http-echo:1.0 | Vous donne un petit backend de démonstration prévisible sans enseigner un deuxième serveur web en même temps. |
| haproxy:3.4.1 | Utilise une balise stable épinglée au lieu de latest, ce qui rend le guide moins fragile au fil du temps. |
| depends_on | Démarre le service demo avant HAProxy, ce qui est utile pour l’ordre du premier démarrage. |
| 80:80 | Publie l’écouteur HTTP principal sur le port web standard que les lecteurs attendent. |
| 127.0.0.1:8404:8404 | Garde la page de statistiques disponible pour la validation locale sans l’exposer publiquement par défaut. |
| ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro | Monte votre fichier de configuration visible côté hôte dans l’image HAProxy officielle en lecture seule. |
| sysctls avec net.ipv4.ip_unprivileged_port_start: “0” | Permet au conteneur HAProxy non-root de se lier à des ports bas comme 80. |
| restart: unless-stopped | Vous donne une valeur par défaut VPS pratique : redémarrer après une défaillance ou un redémarrage, mais respecter un arrêt manuel intentionnel. |
Un détail de plus importe ici : il n’y a pas de réseau Docker personnalisé dans ce fichier car Docker Compose crée un réseau par défaut automatiquement. Cela vous donne le DNS du nom du service à l’intérieur du projet, c’est pourquoi HAProxy pourra atteindre le backend en tant que demo:5678 sans câblage supplémentaire.
⚠️ Avertissement : Le port 80 est un port privilégié, donc la ligne sysctls n’est pas décorative. Changer le mappage d’hôte en 8080:80 ne supprime pas l’exigence de port privilégié à l’intérieur du conteneur si HAProxy se lie toujours à :80 en interne.
Écrire et valider un minimal haproxy.cfg
Avec le câblage des conteneurs en place, HAProxy a toujours besoin d’instructions pour savoir où le trafic arrive, où il doit aller et comment la santé du backend est vérifiée. Créez haproxy.cfg ensuite :
global
log stdout format raw local0
defaults
mode http
timeout connect 5s
timeout client 30s
timeout server 30s
frontend http
bind :80
default_backend demo_backend
backend demo_backend
balance roundrobin
server demo1 demo:5678 check
frontend stats
bind :8404
stats enable
stats refresh 10s
stats uri /statsC’est une configuration minimale, mais ce n’est pas une configuration jetable. log stdout format raw local0 est le choix de journalisation adapté aux conteneurs car Docker peut facilement exposer stdout, et mode http dans defaults garde tout l’exemple en mode HTTP afin que le comportement du listener et du backend restent cohérents et lisibles.
✏️ NOTE : Un détail mérite d’être souligné avant la répartition des sections : balance roundrobin est défini explicitement car les versions plus récentes de HAProxy ont changé l’algorithme de backend par défaut en random, et roundrobin est plus facile à enseigner de manière prévisible lors d’une première approche.
Voici la répartition en langage clair de chaque section :
| Section | Lignes clés | Ce qu’elle fait |
|---|---|---|
| global | log stdout format raw local0 | Envoie les journaux vers stdout afin que la journalisation Docker reste simple. |
| defaults | mode http, timeouts | Établit le comportement HTTP de base et des valeurs de timeout raisonnables. |
| frontend http | bind :80, default_backend demo_backend | Crée le listener public et le connecte à la définition du backend. |
| backend demo_backend | balance roundrobin, server demo1 demo:5678 check | Indique à HAProxy quel service utiliser et de surveiller sa santé. |
| frontend stats | bind :8404, stats enable, stats uri /stats | Ajoute une page de validation locale optionnelle pour que vous puissiez voir l’état d’exécution plus tard. |
Vous remarquerez peut-être une chose manquante : option forwardfor. Cette omission est intentionnelle dans le chemin de base. Préserver l’IP client d’origine est utile plus tard, mais ce premier déploiement concerne la preuve du routage et de la santé du backend, pas l’enseignement du comportement des en-têtes avec un conteneur de démonstration qui ne rend pas ce signal particulièrement précieux.
💡 Conseil : Validez toujours la configuration HAProxy avant de démarrer la pile complète. Parce que cette configuration fait référence au backend par son nom de service Compose (demo), démarrez d’abord ce backend afin que HAProxy puisse le résoudre lors de la validation.
Exécutez la validation à partir du même répertoire de projet :
docker compose up -d demo
docker compose run --rm --no-deps haproxy haproxy -V -c -f /usr/local/etc/haproxy/haproxy.cfg
Si la deuxième commande se termine par Configuration file is valid, vous avez déjà prouvé que HAProxy peut analyser le fichier correctement et résoudre la cible du backend avant que tout listener en direct ne démarre.
Démarrer la Stack et Prouver que le Proxy Fonctionne
Une fois la config validée, démarrez la stack en mode détaché :
Parce que l’étape de validation a déjà démarré demo, cette commande démarre principalement HAProxy et réconcilie la stack complète à deux services :
docker compose up -d
Ensuite, vérifiez que les deux conteneurs sont actifs :
docker compose ps
Cette vue des processus n’est que le premier point de contrôle. Elle confirme que Docker a démarré les conteneurs, mais pas encore que HAProxy achemine avec succès le trafic vers le backend. La prochaine requête vérifie le chemin de données réel.
Maintenant, exécutez le test de routage réel depuis le VPS lui-même :
curl -i http://127.0.0.1
Le signal de succès est HTTP/1.1 200 OK plus le corps de la réponse contenant Hello from the HAProxy demo backend. Certaines versions de http-echo enveloppent ce texte dans une petite réponse HTML, donc concentrez-vous sur la phrase du corps plutôt que sur le formatage exact.
Si vous voulez une preuve au niveau du navigateur, ouvrez http://YOUR_SERVER_IP depuis une autre machine.

Pour une deuxième surface de validation, vérifiez la page de statistiques locale uniquement depuis le VPS :
curl http://127.0.0.1:8404/statsSur la page de statistiques, les signaux les plus utiles sont un frontend nommé http, un backend nommé demo_backend, une ligne de serveur nommée demo1, le statut affiché comme UP, et généralement une valeur de dernier contrôle comme L4OK in 0ms. Gardez aussi à l’esprit une petite nuance Docker : la syntaxe courte depends_on contrôle l’ordre de démarrage, mais elle n’attend pas qu’un service devienne sain. Si le tout premier curl échoue une fois juste après le démarrage, attendez quelques secondes et réessayez avant de supposer que la config est incorrecte.
La différence entre l’état du processus et le succès réel est plus facile à comprendre sous forme de tableau :
| État | Ce qu’il vous dit |
|---|---|
| Les conteneurs sont en cours d’exécution | Docker a démarré les processus. |
| curl -i http://127.0.0.1 retourne 200 OK et la phrase de démo | HAProxy achemine réellement le trafic vers le backend. |
| La page de statistiques affiche demo1 comme UP | HAProxy voit le backend comme sain. |
Erreurs courantes au premier lancement et correctifs rapides

Si la configuration ne fonctionne pas immédiatement, résistez à l’envie de réécrire les deux fichiers à la fois. La plupart des défaillances au premier lancement sur cette pile sont prévisibles, et elles deviennent beaucoup plus faciles à corriger lorsque vous modifiez une variable à la fois.
Utilisez cette matrice comme couche de diagnostic rapide :
HAProxy se ferme immédiatement
Cause probable : haproxy.cfg est manquant.
Correctif rapide : Assurez-vous que haproxy.cfg existe à côté de compose.yaml.
Pourquoi cela se produit : L’image officielle ne contient pas de configuration prête à l’emploi.
L’erreur indique qu’il ne peut pas ouvrir /usr/local/etc/haproxy/haproxy.cfg
Cause probable : Chemin de montage incorrect.
Correctif rapide : Vérifiez que ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro est exact.
Pourquoi cela se produit : HAProxy ne peut pas démarrer sans un fichier de configuration valide.
L’erreur indique Permission refusée sur le port 80
Cause probable : Problème de liaison de port privilégié.
Correctif rapide : Conservez net.ipv4.ip_unprivileged_port_start: “0” dans Compose, ou déplacez HAProxy et le port publié vers 8080.
Pourquoi cela se produit : Le conteneur s’exécute en tant qu’utilisateur non-root haproxy.
Vous avez changé le mappage en 8080:80 et vous obtenez toujours une erreur de liaison
Cause probable : HAProxy se lie toujours à :80 à l’intérieur du conteneur.
Correctif rapide : Modifiez à la fois le mappage de l’hôte et la ligne bind interne si vous vous éloignez du port 80.
Pourquoi cela se produit : La règle de port privilégié s’applique également à l’intérieur du conteneur.
Le port 80 est déjà utilisé
Cause probable : Un autre service possède le port de l’hôte.
Correctif rapide : Réexécutez la vérification ss et arrêtez ou déplacez le service en conflit.
Pourquoi cela se produit : Un seul processus peut écouter sur le même port de l’hôte.
La vérification de syntaxe signale un mot-clé inconnu ou des erreurs spécifiques à une ligne
Cause probable : Erreur de frappe dans la configuration HAProxy.
Correctif rapide : Réexécutez la vérification de syntaxe et corrigez la ligne exacte qu’elle signale.
Pourquoi cela se produit : L’analyseur HAProxy est strict, ce qui est utile une fois que vous l’utilisez intentionnellement.
Les conteneurs sont actifs, mais curl ne retourne pas la réponse de démonstration
Cause probable : Le chemin de routage est incorrect.
Correctif rapide : Revérifiez default_backend demo_backend, server demo1 demo:5678 check, et le nom du service demo.
Pourquoi cela se produit : Un conteneur en cours d’exécution n’est pas la preuve d’un chemin frontend-to-backend correct.
curl local fonctionne mais le site est inaccessible de l’extérieur
Cause probable : Pare-feu ou règle de sécurité du fournisseur.
Correctif rapide : Ouvrez le port 80 dans UFW et tout pare-feu côté fournisseur.
Pourquoi cela se produit : La publication locale peut fonctionner même lorsque l’accès public est toujours bloqué.
⚠️ Avertissement : Modifiez une seule chose à la fois. Si vous modifiez à l’aveugle à la fois compose.yaml et haproxy.cfg, vous rendez beaucoup plus difficile de déterminer si la défaillance est un problème de chemin de fichier, un problème de port ou un problème de routage.
Lorsque vous avez besoin de preuves rapides, gardez ces commandes à proximité :
docker compose logs haproxy
docker compose ps
docker compose up -d demo
docker compose run --rm --no-deps haproxy haproxy -V -c -f /usr/local/etc/haproxy/haproxy.cfg
sudo ss -tlnp | grep -E ':(80|8404)s' || trueVoici les trois modèles d’alerte les plus importants à reconnaître au premier coup d’œil :
[ALERT] ... Cannot open configuration file /usr/local/etc/haproxy/haproxy.cfg : No such file or directory
[ALERT] ... Starting frontend http: cannot bind socket (Permission denied) [0.0.0.0:80]
[ALERT] ... parsing [/usr/local/etc/haproxy/haproxy.cfg:12] : unknown keyword 'chekc'; did you mean 'check' maybe?C’est la partie rassurante d’un petit déploiement initial : les formes de défaillance sont généralement petites aussi. Vous n’avez pas besoin de recommencer. Vous devez identifier quelle couche se plaint et corriger d’abord cette seule chose.
Où aller après l’installation
Une fois que la démo avec un backend fonctionne, l’architecture est déjà utile. L’étape réelle suivante consiste à remplacer le conteneur de démo par votre application réelle tout en conservant la même structure HAProxy. Après cela, ajoutez HTTPS/TLS comme étape de suivi dédiée, et traitez le routage basé sur le domaine plus les ACLs comme des sujets séparés plutôt que de les précipiter dans cette première installation.

Lorsque vous êtes prêt pour plus d’un backend, le même modèle devient visiblement significatif :
backend app_backend
balance roundrobin
server app1 app1:8080 check
server app2 app2:8080 checkPour des modifications sûres d’une config montée en bind, validez d’abord puis rechargez HAProxy gracieusement :
docker compose up -d demo
docker compose run --rm --no-deps haproxy haproxy -V -c -f /usr/local/etc/haproxy/haproxy.cfg
docker compose kill -s HUP haproxy📝 Remarque : La page de statistiques est intentionnellement locale uniquement dans ce guide. Si vous l’exposez jamais publiquement, ajoutez d’abord l’authentification et les contrôles d’accès.
Cela vous ramène au problème initial : vous vouliez une seule porte d’entrée propre devant un service, sans transformer la première configuration en un projet d’exploitation complet. Vous avez maintenant ce chemin de travail. Plus important encore, vous avez aussi le bon modèle mental : HAProxy reçoit le trafic en premier, le transfère où il doit aller, et vous donne un moyen plus propre de développer la pile sur un VPS auto-géré sans perdre le contrôle de la configuration.
sur tous les services d'hébergement