Un VPS qui expose SSH, Django, PostgreSQL et le port d’administration sur la même adresse publique finit par se faire balayer. Le découpage utile est simple : le conteneur applicatif et la base vivent sur un réseau privé Incus, et seul un reverse proxy publie les ports 80 et 443.
Trois conteneurs, un seul visible
- Proxy, par exemple
10.10.0.5. C’est le seul à avoir une carte vers l’extérieur. Nginx y termine le TLS et transmet les requêtes à l’application. - Application,
10.10.0.10. Gunicorn n’écoute que sur cette adresse privée, jamais sur0.0.0.0public. - Base,
10.10.0.30. PostgreSQL n’accepte de connexions que depuis l’adresse de l’application.
Le visiteur ne peut pas ouvrir https://10.10.0.10 : cette adresse n’existe pas sur Internet. Un scan du VPS ne voit que le proxy, et SSH sur l’hôte si vous l’avez laissé ouvert.
Ordre de montage
- Créer le réseau privé Incus. Les conteneurs s’y parlent entre eux. Ils ne reçoivent pas d’adresse publique, sauf le proxy.
- Installer l’application et la base sur ce réseau. Vérifier depuis le proxy :
curl http://10.10.0.10:8000/répond. Depuis votre machine, la même adresse ne répond pas. - Poser Nginx sur le proxy. Un
serveren 443, un certificat pour le nom public, unproxy_passvershttp://10.10.0.10:8000. - N’ouvrir sur le pare-feu de l’hôte que 80, 443, et le port SSH. Pas 8000, pas 5432.
Ce que le proxy doit transmettre
Django a besoin de savoir que la requête d’origine est en HTTPS, sinon les redirections et les cookies sécurisés se trompent. Nginx envoie Host, X-Forwarded-For et X-Forwarded-Proto. Dans les réglages Django, SECURE_PROXY_SSL_HEADER ne fait confiance à cet en-tête que parce que l’application n’est joignable que par le proxy. Si Gunicorn était public, n’importe qui pourrait se prétendre en HTTPS.
Ce qu’il ne faut pas faire
- Donner une IP publique « temporaire » au conteneur applicatif pour débugger, puis l’oublier.
- Publier le port Incus ou le socket de l’hôte sur Internet.
- Mettre le mot de passe root de l’hôte dans un inventaire commité.
Le proxy peut lui-même être derrière Cloudflare ou Bunny. L’origine reste le conteneur Nginx, pas Gunicorn. L’article sur SSH par clé décrit comment fermer l’autre porte, celle de l’hôte.