- pip install sans venv pollue l'installation globale et casse les autres projets en cas de conflit de version.
- Le cron ne charge pas le PATH du shell interactif : utiliser le chemin absolu vers /venv/bin/python.
- load_dotenv() cherche le .env dans le répertoire courant au lancement, pas dans le dossier du script ; cd explicite ou chemin absolu obligatoire.
- chmod 600 .env est obligatoire pour ne pas exposer les clés API aux autres utilisateurs du serveur.
- Sans logging.basicConfig configuré, les erreurs des scripts cron disparaissent dans le vide et le debug devient impossible.
J’ai passé six heures à déboguer mon premier script Python en production sur un VPS Hetzner.
Six heures sur un script qui tournait parfaitement en local depuis une semaine. Même code. Même logique. Mais le VPS, lui, ne voulait rien savoir.
Le problème n’était pas dans le code. Il était dans tout ce que j’avais fait autour : comment j’avais installé Python, comment j’avais configuré le cron, comment je gérais mes variables d’environnement. Des détails que les tutoriels de déploiement survolent en une ligne, et qui en réalité concentrent la majorité des heures perdues quand on déploie pour la première fois.
Voici les cinq erreurs exactes que j’ai faites, dans l’ordre où elles m’ont planté dessus.
Erreur 1 : installer les dépendances en global avec pip
Réponse directe : Installer tes librairies Python directement avec pip install sans environnement virtuel pollue l’installation Python globale du serveur. Quand deux projets ont besoin de versions différentes de la même librairie, l’un des deux casse. La solution est de créer un environnement virtuel par projet avant d’installer quoi que ce soit.
Sur ma machine locale, je faisais pip install notion-client requests python-dotenv sans me poser de questions. Ça marchait. Je suis arrivé sur le VPS et j’ai fait pareil.
Trois semaines plus tard, j’ai voulu déployer un deuxième script sur le même VPS. Ce deuxième projet avait besoin d’une version antérieure de requests. En l’installant, j’ai cassé le premier. Le premier s’est mis à planter avec des erreurs d’import que je ne comprenais pas, parce que je ne savais pas encore qu’un conflit de version venait d’écraser ma librairie.
La fix : un environnement virtuel par projet, toujours
# Dans le dossier de ton projet sur le VPS
python3 -m venv venv
# Activation
source venv/bin/activate
# Installation des dépendances dans l'environnement isolé
pip install -r requirements.txt
# Vérification que tu es dans le bon environnement
which python # doit afficher /home/user/ton-projet/venv/bin/python
L’environnement virtuel crée un dossier venv/ qui contient sa propre version de Python et ses propres librairies. Deux projets avec des dépendances incompatibles coexistent sans se toucher.
La règle que j’applique maintenant : sur un VPS, je ne fais jamais pip install sans avoir activé un venv avant. Si je me retrouve à taper pip sans voir (venv) au début de mon prompt, je m’arrête.
Erreur 2 : le cron qui ne trouve pas Python
Réponse directe : Le cron s’exécute dans un environnement minimal qui n’a pas accès aux mêmes variables PATH que ton shell interactif. Taper python3 dans une commande cron peut échouer même si python3 fonctionne parfaitement quand tu es connecté en SSH. La solution est d’utiliser le chemin absolu vers l’exécutable Python de ton environnement virtuel.
C’est l’erreur qui m’a pris le plus de temps à identifier. J’avais configuré mon cron job comme ça :
0 * * * * cd /home/user/mon-projet && python3 main.py
En SSH, cette commande fonctionnait parfaitement. Le cron, lui, se lançait sans rien faire. Pas d’erreur visible, pas de log. Le script ne tournait tout simplement pas.
Le problème : quand tu te connectes en SSH, ton shell charge ton profil utilisateur et configure le PATH pour inclure les chemins vers Python. Le cron ne fait pas ça. Il s’exécute dans un environnement nu, avec un PATH minimal, et python3 ne pointe vers rien.
La même chose se produit avec source, qui est une commande bash. Le shell par défaut du cron est /bin/sh, qui ne supporte pas la commande source. Tenter d’activer un environnement virtuel avec source venv/bin/activate dans un cron échoue silencieusement.
La fix : chemin absolu vers le Python du venv
# Trouve le chemin absolu de ton Python
which python # après avoir activé ton venv en SSH
# Résultat : /home/user/mon-projet/venv/bin/python
# Cron job corrigé
0 * * * * /home/user/mon-projet/venv/bin/python /home/user/mon-projet/main.py >> /home/user/mon-projet/logs/cron.log 2>&1
En pointant directement vers le Python de ton venv, tu contournes le problème de PATH et tu utilises automatiquement les bonnes librairies sans avoir besoin d’activer l’environnement.
Pour déboguer un cron muet : ajoute >> /chemin/vers/ton.log 2>&1 à la fin de ta commande cron. Le 2>&1 redirige les erreurs vers le même fichier de log. Sans ça, les erreurs du cron disparaissent dans le vide.
Erreur 3 : les variables d’environnement qui ne chargent pas
Réponse directe : Le fichier .env ne se charge pas automatiquement sur un serveur. load_dotenv() cherche le fichier dans le répertoire courant au moment de l’exécution. Quand le cron lance ton script, ce répertoire peut ne pas être celui de ton projet, et load_dotenv() ne trouve rien.
Mon script utilisait load_dotenv() et lisait mes clés API depuis un fichier .env. En local, parfait. Sur le VPS, mes appels API retournaient des erreurs d’authentification 401.
Le problème : quand le cron lance mon script, le répertoire courant n’est pas forcément le dossier de mon projet. Il peut être /root ou /home/user, selon la configuration. load_dotenv() cherche le .env là où il est lancé, pas là où le script se trouve.
Fix 1 : charger le .env avec un chemin absolu
from dotenv import load_dotenv
from pathlib import Path
# Chemin absolu vers le .env, indépendant du répertoire courant
env_path = Path(__file__).parent / ".env"
load_dotenv(dotenv_path=env_path)
__file__ est le chemin du script Python en cours d’exécution. Path(__file__).parent est le dossier qui le contient. Le .env se trouve donc toujours au bon endroit, peu importe d’où le script est lancé.
Fix 2 : s’assurer que le cron lance depuis le bon répertoire
0 * * * * cd /home/user/mon-projet && /home/user/mon-projet/venv/bin/python main.py
Le cd avant la commande garantit que le répertoire courant est bien le dossier du projet.
Ce que je vérifie maintenant en premier quand un script fonctionne en local mais échoue en prod : je log la valeur d’une variable d’environnement en début de script. Si elle est vide, le .env ne charge pas.
Erreur 4 : les permissions qui bloquent en silence
Réponse directe : Les scripts Python qui écrivent dans des fichiers, lisent un .env, ou créent des dossiers de logs peuvent échouer silencieusement si les permissions Unix ne sont pas correctes. Le script tourne, il ne plante pas, mais il n’écrit rien. Ou il plante avec un PermissionError qui n’est loggé nulle part.
Deux cas concrets que j’ai rencontrés.
Cas 1 : le script ne peut pas écrire dans son propre dossier de logs
# Le dossier logs/ créé par root n'est pas accessible à mon utilisateur
ls -la
# drwxr-xr-x 2 root root 4096 jan 15 10:23 logs/
# Fix : donner la propriété à ton utilisateur
sudo chown -R user:user /home/user/mon-projet/logs/
Cas 2 : le .env est lisible par tout le monde
ls -la .env
# -rw-r--r-- 1 user user 245 jan 15 10:23 .env
# Tout le monde peut lire tes clés API
# Fix : lecture uniquement par ton utilisateur
chmod 600 .env
J’ai mentionné ce point dans l’article sur le pipeline LinkedIn automatisé, mais c’est une erreur suffisamment courante pour mériter une section complète ici.
La commande de diagnostic rapide pour vérifier les permissions d’un dossier de projet :
ls -la /home/user/mon-projet/
Si tu vois root comme propriétaire sur des fichiers que ton script doit modifier, c’est là que ça bloque.
Erreur 5 : zéro logging, donc zéro visibilité
Réponse directe : Un script Python sans logging structuré est impossible à déboguer en production. Quand il plante, tu ne sais pas à quelle ligne, avec quelle valeur, dans quel contexte. Ajouter logging au début de ton script prend cinq minutes et te fait gagner des heures sur chaque bug futur.
C’était mon état au début : un script qui tournait en cron, qui semblait ne rien faire, et aucun moyen de savoir pourquoi.
La raison : les print() dans un script lancé par cron n’apparaissent nulle part. Et sans redirection des erreurs dans la commande cron, les exceptions disparaissent aussi.
La configuration de logging que j’utilise sur tous mes projets VPS :
import logging
import sys
from pathlib import Path
# Création du dossier logs si absent
log_dir = Path(__file__).parent / "logs"
log_dir.mkdir(exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
# Fichier de log persistant
logging.FileHandler(log_dir / "app.log"),
# Sortie standard pour les tests en SSH
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
Utilisation dans le code :
logger.info("Démarrage du script")
logger.info(f"Traitement de : {item_name}")
logger.error(f"Erreur sur {item_name} : {str(e)}")
Avec ce setup, chaque exécution laisse une trace dans logs/app.log avec la date, le niveau, et le message. Quand quelque chose plante à 3h du matin, tu ouvres le fichier et tu vois exactement ce qui s’est passé.
Pour voir les logs en temps réel pendant que tu testes :
tail -f /home/user/mon-projet/logs/app.log
Récapitulatif : la checklist avant chaque déploiement
Avant de considérer qu’un script Python est “déployé” sur un VPS, je vérifie maintenant ces cinq points dans l’ordre :
python3 -m venv venvcréé etpip install -r requirements.txtlancé depuis le venv- Commande cron avec chemin absolu vers
/venv/bin/pythonet redirection des logs>> app.log 2>&1 load_dotenv(dotenv_path=Path(__file__).parent / ".env")dans le scriptchmod 600 .envetchown -R user:usersur le dossier du projetlogging.basicConfig(...)configuré avec un fichier de log
Ces cinq points couvrent 90% des raisons pour lesquelles un script qui tourne en local refuse de tourner en production.
Si tu intègres des APIs LLM dans ce script, j’ai documenté les erreurs spécifiques aux appels Gemini et Claude en production qui s’ajoutent à cette checklist.
Six heures de debug pour comprendre ces cinq points. Tu viens de les lire en dix minutes.
Aucune de ces erreurs n’est dans la documentation officielle de Python. Elles ne sont pas dans les tutoriels “deploy to VPS in 5 minutes”. Elles apparaissent uniquement quand tu fais le saut entre un script qui tourne sur ta machine et un script qui doit tourner seul, la nuit, sans que tu sois là.
Si tu en as rencontré d’autres que je n’ai pas listées ici, mets-les en commentaire. J’enrichirai la checklist avec les cas réels.
Ce guide fait partie de mon guide complet pour configurer un VPS Hetzner de zéro.
Discussion