Pourquoi auto-héberger PostgreSQL sur un VPS
Les bases managées (RDS, Cloud SQL) facturent au Go stocké, aux IOPS et aux connexions, et limitent souvent les extensions disponibles. En déployant PostgreSQL sur votre propre VPS, vous activez pg_stat_statements, PostGIS, pgvector ou TimescaleDB sans restriction, vous choisissez votre version exacte (15, 16, 17) et vous gardez vos données dans une juridiction que vous maîtrisez. Un VPS avec disque SSD offre des latences I/O très basses, idéales pour les workloads transactionnels d'une application en production. Vous payez un forfait fixe et prévisible, et vous décidez seul de votre stratégie de réplication, de chiffrement et de rétention des backups.
Les bénéfices concrets d'un PostgreSQL auto-hébergé
- Coût fixe et prévisible : pas de facturation au Go, aux IOPS ni au nombre de connexions actives.
- Extensions libres :
pgvectorpour l'IA,PostGISpour le géospatial,TimescaleDBpour les séries temporelles. - Contrôle total des sauvegardes :
pg_dumpplanifié,pg_basebackupou réplication physique vers un second VPS. - Tuning fin de
postgresql.conf(shared_buffers, work_mem, WAL) impossible chez la plupart des offres managées. - Souveraineté des données : vos données restent sur une infrastructure que vous choisissez.
- Accès superutilisateur réel pour auditer, profiler et optimiser sans ticket support.
Prérequis matériels et logiciels
Pour une instance PostgreSQL de développement ou un petit projet en production, prévoyez un VPS avec 2 vCPU, 4 Go de RAM et 40 Go de SSD NVMe : PostgreSQL aime la mémoire pour son cache de pages. Pour une charge plus soutenue (plusieurs centaines de connexions ou jeux de données de plusieurs dizaines de Go), visez 4 vCPU et 8 Go de RAM. Côté logiciel : Ubuntu 22.04 ou 24.04 LTS, Docker Engine et le plugin Docker Compose v2, un nom de domaine pointant vers le VPS (ex : db.mondomaine.com) si vous exposez l'accès distant, et un pare-feu ufw configuré pour ne laisser passer que le strict nécessaire. Réservez aussi un volume Docker dédié pour PGDATA afin de découpler les données du conteneur.
Déployer PostgreSQL avec Docker en production
Préparer le VPS et le pare-feu
Connectez-vous en SSH, mettez à jour le système avec
apt update && apt upgrade -y, installez Docker viacurl -fsSL https://get.docker.com | sh. Bloquez le port 5432 depuis l'extérieur avecufw deny 5432: la base ne doit jamais être directement exposée à Internet.Écrire le docker-compose.yml
Créez un service
postgres:17, montez un volume nommé sur/var/lib/postgresql/data, et définissezPOSTGRES_PASSWORD,POSTGRES_DBetPOSTGRES_USERvia un fichier.envjamais commité. Ajoutezshm_size: 256mbpour les tris volumineux et unehealthcheckavecpg_isready.Lancer et initialiser la base
Démarrez avec
docker compose up -d, vérifiez les logs viadocker compose logs -f postgres, puis connectez-vous avecdocker compose exec postgres psql -U app -d mabase. Créez vos rôles applicatifs avec des privilèges minimaux plutôt que d'utiliser le superutilisateur.Exposer l'accès via un tunnel ou un proxy TLS
Pour un accès distant sécurisé, n'ouvrez jamais 5432 en clair : utilisez un tunnel SSH (
ssh -L 5432:localhost:5432) ou placezpgbouncerderrière un reverse proxy. Activez le SSL côté PostgreSQL avec un certificat Let's Encrypt monté dans le conteneur (ssl = ondanspostgresql.conf).Tuner la configuration
Ajustez
shared_buffersà environ 25% de la RAM,effective_cache_sizeà 50-75%, etwork_memselon vos requêtes. Activezpg_stat_statementspour identifier les requêtes lentes. Utilisez l'outil PGTune comme point de départ.Automatiser les sauvegardes
Planifiez un
pg_dumpquotidien via une tâche cron sur l'hôte, compressez en.dumpcustom format, et copiez les archives vers un stockage externe. Testez régulièrement la restauration avecpg_restore: une sauvegarde non testée n'existe pas.Se connecter la première fois
pgAdmin n'est pas publié sur Internet : ouvrez un tunnel depuis votre poste (ssh -L 5050:127.0.0.1:5050 root@<IP>) puis http://127.0.0.1:5050, et connectez-vous avec [email protected] et le mot de passe fourni. Ajoutez ensuite votre serveur (« Add New Server ») avec l'hôte db, le port 5432, l'utilisateur postgres et ce même mot de passe.
Configurer postgresql.conf pour la production
work_mem et max_connections
Réglez
work_memen fonction de vos requêtes de tri et de jointure : 4 à 16 Mo est raisonnable pour une charge mixte. Attention : chaque connexion peut allouer plusieurs foiswork_memsimultanément, donc multipliez parmax_connectionspour estimer la consommation mémoire maximale. Pour 100 connexions applicatives,max_connections = 150(réserve pour les outils de monitoring et les migrations) suffit largement ; au-delà, utilisezpgbounceren mode transaction pour multiplexer les connexions.Logging et détection des requêtes lentes
Activez
logging_collector = on,log_min_duration_statement = <seuil_ms>(ms) etlog_line_prefix = '%t [%p]: [%l-1] db=%d,user=%u 'pour tracer toutes les requêtes qui dépassent le seuil configuré. Les logs atterrissent danspg_log/à l'intérieur du volume PGDATA. Coupler cette configuration àpg_stat_statements(extension à activer dansshared_preload_libraries) vous donne une vue agrégée des requêtes les plus coûteuses sans scanner chaque fichier de log.WAL et checkpoints
Augmentez
wal_buffersà 16 Mo etcheckpoint_completion_targetà 0.9 pour étaler l'écriture des checkpoints dans le temps. Sur un SSD NVMe,random_page_cost = 1.1(au lieu du défaut 4.0) incite le planificateur à choisir les index scans plutôt que les seq scans — un réglage souvent oublié qui change drastiquement les plans d'exécution sur une base volumineuse.
Sauvegardes automatiques et stratégie de recovery
Une sauvegarde non testée est une sauvegarde qui n'existe pas. Trois approches complémentaires couvrent des besoins différents.
pg_dump + cron est le point de départ : simple à mettre en place, il produit un fichier SQL logique restaurable sur n'importe quelle version compatible. Planifiez-le à 3h du matin, compressez avec --format=custom (format binaire compressé, restauration sélective par table), et copiez l'archive vers un stockage externe (objet S3-compatible, autre VPS) avec rclone ou scp. Conservez 7 jours en local et 30 jours en distant.
pgBackRest va plus loin : il supporte les backups incrémentaux et différentiels, la compression parallèle et la restauration point-in-time (PITR) à partir des archives WAL. C'est la solution recommandée dès que votre base dépasse quelques Go ou que vous avez besoin de revenir à un état précis à la minute près après un DELETE accidentel. Il se configure via un fichier pgbackrest.conf et s'intègre à un cron ou un timer systemd.
Test de restauration mensuel : exécutez régulièrement pg_restore -d test_restore <dump> sur un VPS de test pour vérifier l'intégrité de vos archives. Un script hebdomadaire qui restaure la dernière sauvegarde et vérifie le nombre de tables ou une requête de contrôle suffit à détecter une corruption silencieuse avant qu'elle ne soit critique.
Haute disponibilité légère sans Kubernetes
La haute disponibilité PostgreSQL sur VPS n'implique pas nécessairement Patroni ou un cluster Pacemaker complexe. La réplication streaming native couvre la majorité des besoins avec un minimum de configuration.
Réplication streaming manuelle : sur le VPS primaire, activez wal_level = replica, ajoutez un utilisateur dédié replicator dans pg_hba.conf et définissez max_wal_senders = 3. Sur le VPS secondaire (standby), lancez pg_basebackup pour initialiser une copie physique, créez un fichier standby.signal et configurez primary_conninfo dans postgresql.conf. La réplication démarre automatiquement au redémarrage. Avec hot_standby = on, la standby accepte les requêtes en lecture seule — parfait pour déporter les rapports et le monitoring.
Patroni ajoute la bascule automatique (failover) : il surveille le primaire via une clé dans etcd, Consul ou ZooKeeper, et promeut automatiquement la standby si le primaire devient inaccessible pendant plus de 30 secondes. Sur deux VPS, il suffit d'un etcd tiers (VM légère ou service managé) pour le consensus. Patroni est la voie recommandée dès que vous ne pouvez pas tolérer une intervention manuelle de 5 à 15 minutes lors d'une panne.
Supervision du lag de réplication : SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), sent_lsn) AS lag_bytes FROM pg_stat_replication sur le primaire vous donne le décalage en octets en temps réel. Un lag supérieur à quelques Mo indique une saturation réseau ou une standby surchargée.
PostgreSQL self-hosted vs RDS vs Supabase vs Neon
Faites défiler le tableau
| Critère | VPS ServOrbit (self-hosted) | AWS RDS PostgreSQL | Supabase (cloud) | Neon (serverless) |
|---|---|---|---|---|
| Coût mensuel (small) | 99 DH/mois (forfait fixe) | ~35-70 $/mois (db.t3.medium) | ~25 $/mois (Pro) | ~19 $/mois (Launch) |
| Contrôle de postgresql.conf | Total | Partiel (paramètre groups) | Limité | Minimal |
| Extensions custom | Toutes (pgvector, PostGIS…) | Liste restreinte | pgvector, PostGIS | Limitées |
| PITR (Point-in-Time Recovery) | Oui avec pgBackRest | Oui (natif) | Oui (Pro+) | Oui (natif) |
| Haute disponibilité | Réplication manuelle ou Patroni | Multi-AZ natif | Natif | Multi-région natif |
| Charge de maintenance | Élevée (DBA requis) | Faible | Très faible | Très faible |
| Données hors US | Oui (VPS marocain) | Selon région | Selon région | US/EU uniquement |
| Superuser / extensions | Oui | Non | Non | Non |
Dépannage des problèmes courants en production
OOM killer tue PostgreSQL : le signal OOM dans /var/log/syslog (oom_kill_process) indique que la mémoire est épuisée. Causes principales : shared_buffers trop élevé, work_mem mal calibré face au nombre de connexions, ou processus concurrent (Elasticsearch, Node.js) qui consomme tout le swap. Diagnostic : cat /proc/meminfo | grep -E 'MemFree|Cached|Buffers' et ps aux --sort=-%mem | head -10. Remède : réduire work_mem, activer huge_pages = try dans postgresql.conf, ou upgrader le VPS.
Connexions épuisées (FATAL: sorry, too many clients already) : max_connections est atteint. Court terme : SELECT count(*), state FROM pg_stat_activity GROUP BY state pour identifier les connexions idle. Long terme : déployez pgbouncer en mode transaction (pool_mode = transaction) devant PostgreSQL — il multiplex des centaines de connexions applicatives vers un pool de 20-50 connexions réelles.
Autovacuum saturé : des tables avec beaucoup d'UPDATE ou de DELETE accumulent des tuples morts (dead tuples) qui gonflent les tables et ralentissent les scans. SELECT relname, n_dead_tup, last_autovacuum FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 10 révèle les tables problématiques. Accélérez l'autovacuum en réduisant autovacuum_vacuum_scale_factor (ex : 0.01 au lieu de 0.2) pour les tables critiques via ALTER TABLE ... SET (autovacuum_vacuum_scale_factor = 0.01).
Checkpoints trop fréquents (LOG: checkpoint occurring too frequently) : le WAL se remplit plus vite que checkpoint_timeout (défaut 5 min). Augmentez max_wal_size (ex : 2-4 Go) et checkpoint_timeout à 15-30 min pour espacer les checkpoints sans risquer une perte de données excessive en cas de crash.
Pour une haute disponibilité, déployez un second VPS en réplication physique en streaming (primary_conninfo + slot de réplication). Vous obtenez une standby chaude prête à basculer, et vous pouvez y déporter vos requêtes de lecture lourdes (hot_standby = on) pour soulager le primaire. Couplé à pgBackRest pour des backups incrémentaux différentiels, vous atteignez un RPO de quelques minutes sans dépendre d'un service managé.
La documentation officielle
Pour la configuration avancée, les options propres à l'outil et les changements de version, référez-vous à la documentation officielle de PostgreSQL. Ce guide couvre la mise en ligne sur VPS ServOrbit ; la documentation éditeur reste la référence pour les réglages fins.