Une base PostgreSQL en production écoute rarement sur une IP publique. Le bon réflexe est de la laisser derrière un bastion SSH, et d'ouvrir un tunnel local depuis sa machine de dev. La commande ssh -L suffit, mais en pratique on bute vite sur les timeouts, les ProxyJump, et la gestion des host keys.
Ce guide reprend les trois approches concrètes : la commande nue, le fichier ~/.ssh/config, et les clients desktop qui intègrent le tunnel directement dans la fiche de connexion. Pour chacun, on liste les options qui comptent et les pièges classiques.
Méthode 1 : ssh -L à la main
La forme de base ressemble à ceci : ssh -L 5433:db.internal:5432 user@bastion.example.com. Une fois la connexion établie, un client PostgreSQL pointé sur localhost:5433 est en réalité acheminé jusqu'à db.internal:5432, résolu côté bastion.
Trois points sont souvent mal compris. D'abord, le port local (5433 ici) est arbitraire, il suffit qu'il soit libre. Ensuite, le remote_host est résolu depuis le bastion : si la base s'appelle postgres dans le DNS interne, on met ce nom, pas une IP locale. Enfin, ajouter -N évite d'ouvrir un shell : la session SSH ne sert qu'au forwarding.
Une fois le tunnel ouvert, on se connecte avec psql : psql -h 127.0.0.1 -p 5433 -U app_user -d production. On préfère 127.0.0.1 à localhost pour forcer l'IPv4 et éviter les surprises quand sshd n'écoute que sur v4.
Méthode 2 : automatiser avec ~/.ssh/config
Taper la commande à chaque fois devient pénible. Le fichier ~/.ssh/config permet de déclarer une entrée nommée et de réutiliser ssh prod-db ou ssh -f -N prod-db pour lancer le tunnel en arrière-plan.
Une entrée typique contient : Host prod-db, HostName bastion.example.com, User ops, IdentityFile ~/.ssh/prod_ed25519, LocalForward 5433 db.internal:5432. Avec LocalForward dans la config, ssh prod-db ouvre le tunnel automatiquement.
Pour passer par plusieurs sauts (bastion puis sous-bastion), on ajoute ProxyJump. Exemple : ProxyJump jump-host où jump-host est lui-même défini comme une entrée plus haut dans le fichier. C'est plus propre que l'ancien pattern ssh -t bastion ssh sub-bastion.
Pour éviter que le tunnel meure pendant qu'on rédige une requête, on règle le keepalive : ServerAliveInterval 30 et ServerAliveCountMax 3. OpenSSH envoie alors un message toutes les 30 secondes et coupe après 3 échecs.
Méthode 3 : les clients qui intègrent le tunnel
Plusieurs clients SQL desktop intègrent la phase SSH dans la fiche de connexion. L'idée est la même : on saisit les paramètres du bastion à côté des paramètres de la base, l'outil orchestre le tunnel avant d'ouvrir la connexion PostgreSQL.
Dans DBeaver, l'onglet SSH d'une connexion permet de cocher Use SSH Tunnel et de saisir host, port, user, méthode d'authentification (password ou clé). DBeaver utilise une implémentation Java de SSH, ce qui peut diverger de la config OpenSSH locale sur certains points comme les algorithmes ou ProxyJump.
Dans DataGrip, la même logique existe via SSH/SSL et un menu déroulant Auth type. DataGrip peut réutiliser ~/.ssh/config via l'option OpenSSH config and authentication agent, ce qui évite la duplication de configuration.
Dans QoreDB, le tunnel SSH est délégué au client ssh natif de la machine, donc OpenSSH sous Linux, macOS et Windows 10/11. L'app lance ssh -L en sous-processus avec les options issues de la fiche de connexion. Le bénéfice : zéro ré-implémentation de SSH, donc même comportement et même surface d'audit que le binaire système.
Les champs à remplir dans QoreDB pour PostgreSQL via SSH sont : hôte et port DB (le host est résolu côté bastion, comme avec ssh -L), hôte et port SSH, utilisateur SSH, chemin de clé privée, politique de host key (accept_new pour le premier essai, strict ensuite), et un champ ProxyJump optionnel pour les setups à plusieurs sauts.
Les pièges qui reviennent
Le premier piège est le keepalive. Une session SSH idle finit par être coupée par le bastion ou par un NAT intermédiaire. Sans ServerAliveInterval, la connexion psql se met à freezer après quelques minutes d'inactivité. Régler 30 secondes côté client suffit dans 95 % des cas.
Le second piège est la résolution DNS. Le remote_host dans ssh -L local:remote_host:remote_port est résolu sur le bastion, pas sur le poste local. Mettre localhost est correct si la base tourne sur le bastion lui-même. Sinon il faut le nom DNS interne ou l'IP privée.
Le troisième piège est la vérification de la host key. La première connexion ajoute la clé du bastion dans ~/.ssh/known_hosts. Si le bastion est recréé (changement d'AMI, redéploiement), OpenSSH refuse de se connecter en signalant REMOTE HOST IDENTIFICATION HAS CHANGED. On retire la ligne avec ssh-keygen -R bastion.example.com plutôt que d'éditer le fichier à la main.
Le quatrième piège est le ProxyJump enchaîné. Si chaque saut requiert une clé différente, il faut une entrée par hôte dans ~/.ssh/config avec son IdentityFile et son User dédiés. Mettre IdentitiesOnly yes évite qu'OpenSSH essaie toutes les clés du dossier ~/.ssh et se fasse jeter pour trop de tentatives.
Quelle méthode choisir
La commande nue est pratique pour un test ponctuel ou pour comprendre ce qui se passe. Le fichier ~/.ssh/config est la bonne approche dès qu'on a deux bases à atteindre ou plusieurs sauts. Les clients qui intègrent le tunnel évitent la gestion d'un process ssh séparé, et garantissent que la session DB et le tunnel partagent le même cycle de vie.
Dans tous les cas, garder ~/.ssh/config comme source de vérité reste un bon réflexe : les clients qui savent le lire (DataGrip, QoreDB qui s'appuie sur OpenSSH) héritent automatiquement de la même configuration que la ligne de commande. C'est plus facile à auditer et à partager avec un coéquipier qu'une fiche de connexion graphique remplie à la main.
Restez informé des nouveautés
Rejoignez notre newsletter pour recevoir les mises à jour majeures, les nouveaux drivers et nos coulisses techniques.

