Port inaccessible sous Linux : diagnostiquer avec ss, curl et tcpdump sans couper le pare-feu
Le service tourne, le conteneur est « Up », le port semble publié… mais depuis l’extérieur, rien ne répond. Dans ce genre de panne, la tentation est forte : redémarrer plusieurs fois, ouvrir largement le pare-feu, changer le reverse proxy et espérer tomber sur la bonne cause.
On peut faire beaucoup plus propre. Un port inaccessible se diagnostique couche par couche : processus, adresse d’écoute, application, conteneur, pare-feu, puis trajet réseau. L’objectif n’est pas d’essayer des commandes au hasard, mais de savoir exactement jusqu’où la connexion arrive.
Le scénario de travail
Dans les exemples, le service est censé répondre en TCP sur le port 8443. Remplacez ce numéro, le protocole et les adresses par ceux de votre environnement.
Avant de toucher à la configuration, notez quatre informations :
- le nom DNS ou l’adresse IP réellement testée ;
- le port et le protocole attendus : TCP ou UDP ;
- l’endroit où le service tourne : hôte, conteneur ou autre espace réseau ;
- l’exposition voulue : uniquement locale, réseau privé ou Internet.
Cette dernière question est essentielle. Un tableau de bord d’administration destiné au reverse proxy ne devrait pas devenir public simplement parce qu’un test échoue.
1. Vérifier qu’un processus écoute réellement
Sur le serveur, commencez avec ss :
sudo ss -ltnp 'sport = :8443'
Les options demandent les sockets TCP (-t), en écoute (-l), sans résolution de noms (-n) et avec le processus associé (-p). Si la sortie est vide, inutile d’accuser le pare-feu : aucun processus de cet espace réseau n’écoute sur ce port.
Regardez ensuite l’adresse locale affichée :
127.0.0.1:8443: écoute limitée à la boucle locale IPv4 ;[::1]:8443: écoute limitée à la boucle locale IPv6 ;0.0.0.0:8443: écoute sur toutes les adresses IPv4 de l’hôte ;[::]:8443: écoute IPv6 non spécifiée, dont le comportement IPv4 dépend aussi de la configuration système ;192.0.2.10:8443: écoute limitée à cette adresse précise.
Une application liée à 127.0.0.1 peut parfaitement fonctionner depuis le serveur tout en restant inaccessible depuis un autre poste. Ce n’est pas forcément une erreur : c’est souvent le bon réglage lorsque seul un reverse proxy local doit la joindre.
2. Tester l’application depuis plusieurs points
Testez d’abord la boucle locale :
curl -sv --connect-timeout 3 http://127.0.0.1:8443/ -o /dev/null
Puis l’adresse réseau du serveur :
curl -sv --connect-timeout 3 http://<IP_DU_SERVEUR>:8443/ -o /dev/null
Enfin, lancez le même test depuis une autre machine. Si le service parle HTTPS, utilisez https:// et gardez la sortie détaillée de curl. N’ajoutez pas immédiatement -k : ignorer la validation du certificat masque une information utile. Un échec TLS n’est pas un échec de connexion TCP.
Cette progression donne déjà plusieurs conclusions :
- échec même sur
127.0.0.1: service arrêté, mauvais port, mauvais protocole ou application défaillante ; - réussite en local mais échec sur l’adresse du serveur : adresse d’écoute incorrecte ou règle locale ;
- réussite depuis le serveur mais échec à distance : pare-feu, routage, NAT ou filtre situé avant l’hôte ;
- connexion TCP réussie suivie d’une erreur HTTP ou TLS : le réseau fonctionne, cherchez dans l’application ou le reverse proxy.
3. Ne pas oublier IPv4 et IPv6
Un nom de domaine peut renvoyer une adresse IPv4 et une adresse IPv6. Le test peut donc emprunter un chemin différent de celui que vous imaginez.
curl -4 -sv --connect-timeout 3 https://exemple.test:8443/ -o /dev/null
curl -6 -sv --connect-timeout 3 https://exemple.test:8443/ -o /dev/null
sudo ss -4 -ltnp 'sport = :8443'
sudo ss -6 -ltnp 'sport = :8443'
Si IPv4 fonctionne et IPv6 échoue, vérifiez l’enregistrement AAAA, l’adresse d’écoute IPv6, le routage et les règles du pare-feu pour cette famille. Supprimer l’AAAA peut être un contournement temporaire, mais le vrai diagnostic consiste à comprendre pourquoi le chemin IPv6 annoncé ne fonctionne pas.
4. Si le service est dans Docker, vérifier le port publié
Un processus peut écouter correctement dans son conteneur sans être publié sur l’hôte. Commencez par afficher les correspondances :
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
Une sortie comme 127.0.0.1:8443->8080/tcp signifie que le port 8080 du conteneur est accessible sur le port 8443 de l’hôte, mais uniquement par la boucle locale. À l’inverse, 0.0.0.0:8443->8080/tcp publie par défaut sur toutes les interfaces IPv4 de l’hôte.
La documentation Docker rappelle qu’un port non publié n’est pas accessible depuis l’extérieur de l’hôte avec la configuration bridge par défaut. Elle avertit aussi qu’une publication sans adresse précise peut exposer le service au monde extérieur. Pour une application placée derrière un reverse proxy installé sur le même serveur, une publication sur 127.0.0.1 est souvent le choix le plus sûr.
Vérifiez également que l’application écoute dans le conteneur sur 0.0.0.0 ou sur l’adresse appropriée, et pas seulement sur le 127.0.0.1 du conteneur. La boucle locale du conteneur n’est pas celle de l’hôte.
5. Lire le pare-feu avant de le modifier
Sur un système utilisant nftables, commencez par une lecture complète :
sudo nft -a list ruleset
Repérez la chaîne attachée au hook input, sa politique par défaut, les règles qui acceptent le trafic établi et celles qui traitent le port concerné. Les compteurs de paquets, lorsqu’ils sont présents, permettent de voir si une règle est réellement traversée.
Ne lancez pas nft flush ruleset pour « tester ». Sur un serveur distant, vous pouvez supprimer en une commande les protections réseau, les règles Docker et votre propre accès SSH. Si une ouverture est nécessaire, elle doit être limitée au bon protocole, au bon port, à la bonne interface et, quand c’est possible, aux bonnes adresses sources.
Le traçage nft monitor trace permet d’aller plus loin, mais seulement après avoir marqué précisément les paquets concernés avec nftrace. Il vaut mieux réserver cette manipulation à une fenêtre de maintenance et prévoir la suppression de la règle de diagnostic dès la fin du test.
6. Regarder si les paquets atteignent réellement le serveur
Quand les vérifications précédentes ne suffisent pas, tcpdump permet d’observer le trafic correspondant au port :
sudo tcpdump -ni any 'tcp port 8443'
Lancez ensuite une seule tentative de connexion depuis la machine cliente. Le filtre limite la capture au port étudié ; arrêtez avec Ctrl+C. Évitez de capturer ou partager inutilement le contenu des paquets d’un service sensible.
Pour une ouverture TCP classique, vous cherchez principalement les drapeaux SYN, SYN-ACK, ACK et éventuellement RST :
- aucun SYN n’arrive : le blocage se situe avant le serveur, ou le client vise une autre adresse ;
- les SYN arrivent sans réponse visible : examinez le pare-feu local, le routage de retour et l’adresse réellement configurée ;
- le serveur répond par RST : aucun service n’accepte généralement cette connexion sur l’adresse et le port visés, ou une règle la rejette explicitement ;
- le SYN-ACK quitte le serveur mais n’atteint pas le client : cherchez un filtrage ou un chemin de retour asymétrique en amont ;
- la poignée de main s’achève : la connectivité TCP fonctionne ; passez aux journaux HTTP, TLS, proxy ou applicatifs.
7. Corréler avec les journaux du service
Pour un service géré par systemd, observez son état et ses journaux pendant le test :
systemctl status mon-service --no-pager
journalctl -u mon-service --since '-10 minutes' --no-pager
Si la connexion TCP apparaît dans tcpdump mais aucun événement n’arrive dans l’application, le trafic peut être terminé par un reverse proxy, dirigé vers un autre processus ou bloqué avant la couche applicative. Si l’application journalise la requête et renvoie une erreur, le problème n’est plus l’ouverture du port.
La matrice de diagnostic rapide
| Observation | Couche à examiner |
|---|---|
Aucun listener dans ss | Service, configuration, espace réseau |
Listener limité à 127.0.0.1 | Architecture prévue ou adresse d’écoute |
| Local OK, distant KO, aucun SYN capturé | DNS, routeur, NAT, pare-feu cloud |
| SYN reçu, pas de réponse | Pare-feu local, routage de retour |
| Handshake TCP complet | TLS, HTTP, reverse proxy, application |
| Docker publie sur localhost | Choix volontaire ou mapping à corriger |
En clair
Le bon diagnostic suit le paquet au lieu de modifier toute la pile. Vérifiez d’abord le processus et son adresse d’écoute, testez localement, contrôlez le mapping Docker, lisez le pare-feu, puis capturez une tentative précise.
Et surtout, ne transformez pas une panne de connectivité en faille de sécurité. Ouvrir le port sur toutes les interfaces ou couper le pare-feu peut faire disparaître le symptôme tout en créant un problème bien plus grave. La correction doit correspondre à l’exposition réellement souhaitée.
