Introduction
Express démarre vite, mais de petites erreurs de configuration peuvent saper silencieusement la sécurité, la fiabilité et la débogabilité. Symptômes typiques : cookies qui refusent d'être sécurisés derrière un proxy, navigateurs bloqués par un CORS mal appliqué, réponses 413 "Payload Too Large" soudaines, ou traces d'erreur divulguées aux utilisateurs. La bonne nouvelle : la plupart de ces problèmes ont des correctifs simples, à faible risque, applicables et vérifiables en quelques minutes.
Ce guide montre comment inventorier votre configuration actuelle, adopter une configuration de base sûre, vérifier les résultats observables, et préparer des chemins de retour arrière avant de déployer. Les exemples utilisent Express pur sans hypothèses sur des frameworks ou outils de déploiement spécifiques.
Inventaire des versions et de l'environnement
Avant de modifier la configuration, capturez un instantané de l'existant. Cela simplifie le débogage et le retour arrière.
- Enregistrez les versions d'exécution et des bibliothèques :
node -vnpm ls expressnpm ls body-parser helmet cors morgan- Notez la topologie : êtes-vous derrière un proxy inverse ou un load balancer ? TLS se termine-t-il avant Express ? Si oui, le comportement de
trust proxyimporte. - Capturez les variables d'environnement critiques :
NODE_ENV,PORT,HOST,TRUST_PROXY,CORS_ORIGINS,JSON_LIMIT,LOG_LEVEL,SESSION_SECURE, et tout feature flag. - Sauvegardez les réglages actuels de l'application (si disponibles) :
app.get('env'),app.get('trust proxy'),app.get('json spaces'),app.get('x-powered-by').
Commandes d'exemple et vérifications rapides :
node -vnpm ls expresscurl -i http://localhost:3000/healthcurl -i -X OPTIONS http://localhost:3000/any-route -H "Origin: https://example.com" -H "Access-Control-Request-Method: GET"
Utilisez le tableau ci-dessous comme référence rapide d'inventaire.
| Élément | Comment vérifier | Exemple attendu |
|---|---|---|
| Version Node.js | node -v | v18.x ou ultérieur |
| Version Express | npm ls express | [email protected] ou 5.x |
| Proxy inverse présent | notes équipe/réseau | oui/non |
| NODE_ENV | echo $NODE_ENV | production (pour prod) |
| Trust proxy | log app.get('trust proxy') | true/nombre/fonction |
| Origines CORS | echo $CORS_ORIGINS | https://app.example.com |
| Limite corps JSON | echo $JSON_LIMIT | 1mb |
| Port/hôte | echo $PORT $HOST | 3000 0.0.0.0 |
Chemin de configuration sûr
Une base sûre réduit les surprises et rend le comportement observable. Les exemples ci-dessous illustrent des motifs ; adaptez nombres et listes à votre système.
1) Valider l'environnement tôt et échouer vite
Erreur : Compter sur des défauts implicites et exécuter avec des variables d'environnement manquantes ou invalides.
Correctif sûr : Utilisez un petit validateur pour rejeter les mauvaises configurations au démarrage. Exemple avec une vérification simple artisanale (aucune bibliothèque externe requise) :
// config.js
function requireEnv(name, fallback) {
const v = process.env[name] ?? fallback;
if (v === undefined || v === '') throw new Error(`Missing env: ${name}`);
return v;
}
function parseBool(v, def = false) {
if (v == null) return def;
return /^(1|true|yes)$/i.test(v);
}
const cfg = {
env: process.env.NODE_ENV || 'development',
port: Number(requireEnv('PORT', 3000)),
host: process.env.HOST || '0.0.0.0',
trustProxy: process.env.TRUST_PROXY || '0', // '0', '1', 'true', ou liste d'IP
corsOrigins: (process.env.CORS_ORIGINS || '').split(',').map(s => s.trim()).filter(Boolean),
jsonLimit: process.env.JSON_LIMIT || '1mb',
logLevel: process.env.LOG_LEVEL || 'info',
sessionSecure: parseBool(process.env.SESSION_SECURE, false),
};
module.exports = cfg;
2) Ordre correct des middlewares
Erreur : Charger les middlewares dans le mauvais ordre, causant le saut des parseurs ou la perte d'en-têtes.
Correctif sûr : Utilisez un ordre clair de haut en bas : en-têtes de sécurité, journalisation, limites de taux (le cas échéant), parseurs, CORS, routes, 404, gestionnaire d'erreurs.
// app.js
const express = require('express');
const helmet = require('helmet');
const morgan = require('morgan');
const cors = require('cors');
const cfg = require('./config');
const app = express();
// 1) En-têtes de sécurité
app.disable('x-powered-by');
app.use(helmet());
// 2) Journalisation (s'assurer qu'elle s'exécute pour toutes les requêtes)
app.use(morgan(cfg.env === 'production' ? 'combined' : 'dev'));
// 3) Trust proxy (avant tout ce qui dépend de req.ip ou req.secure)
if (cfg.trustProxy === 'true' || cfg.trustProxy === '1') {
app.set('trust proxy', 1); // derrière un proxy
} else if (cfg.trustProxy !== '0') {
app.set('trust proxy', cfg.trustProxy); // ex: 'loopback' ou IPs
}
// 4) Parseurs de corps avec limites explicites
app.use(express.json({ limit: cfg.jsonLimit, strict: true, type: 'application/json' }));
app.use(express.urlencoded({ extended: false, limit: cfg.jsonLimit }));
// 5) CORS (verrouillé aux origines connues)
const allowOrigins = new Set(cfg.corsOrigins);
app.use(cors({
origin: function(origin, cb) {
if (!origin) return cb(null, false); // refuser origines non-navigateur ou inconnues par défaut
return cb(null, allowOrigins.has(origin));
},
credentials: true,
methods: ['GET','POST','PUT','PATCH','DELETE','OPTIONS'],
allowedHeaders: ['Content-Type','Authorization'],
maxAge: 600
}));
// 6) Route d'exemple
app.get('/health', (req, res) => {
res.json({ status: 'ok', secure: req.secure, ip: req.ip });
});
// 7) Gestionnaire 404
app.use((req, res, next) => {
res.status(404).json({ error: 'Not Found' });
});
// 8) Gestionnaire d'erreurs centralisé (pas de traces de pile en production)
app.use((err, req, res, next) => {
const status = err.status || 500;
const payload = { error: err.message || 'Internal Server Error' };
if (cfg.env !== 'production') payload.stack = err.stack;
res.status(status).json(payload);
});
module.exports = app;
3) Timeouts serveur explicites
Erreur : Compter sur les timeouts HTTP serveur par défaut trop longs pour les clients, laissant les sockets en suspens.
Correctif sûr : Définissez des timeouts et keep-alives explicites adaptés à votre budget de latence API.
// server.js
const http = require('http');
const app = require('./app');
const cfg = require('./config');
const server = http.createServer(app);
server.setTimeout(15_000); // timeout requête 15s
server.headersTimeout = 18_000; // timeout en-têtes légèrement au-dessus de setTimeout
server.keepAliveTimeout = 5_000; // keep-alive selon besoins
server.listen(cfg.port, cfg.host, () => {
console.log(`API sur http://${cfg.host}:${cfg.port} env=${cfg.env}`);
});
4) Trust proxy et cookies sécurisés
Erreur : Définir des cookies sécurisés ou compter sur req.secure sans activer trust proxy derrière un proxy inverse. L'application croit que les requêtes ne sont pas sécurisées et dégrade le comportement.
Correctif sûr : Si votre TLS termine au proxy, définissez app.set('trust proxy', 1) (ou une fonction appropriée) pour qu'Express honore les en-têtes X-Forwarded-*. Exemple de configuration de cookie :
const cookieParser = require('cookie-parser');
const cfg = require('./config');
app.use(cookieParser());
// Exemple de définition de cookie
app.get('/login-demo', (req, res) => {
res.cookie('sid', 'example', {
httpOnly: true,
secure: cfg.sessionSecure, // true en environnements HTTPS
sameSite: 'lax',
path: '/',
});
res.json({ ok: true });
});
Si les cookies sécurisés cessent d'apparaître après ce changement, vérifiez trust proxy et si les requêtes sont réellement HTTPS au niveau du proxy.
5) CORS correspondant à votre surface d'exposition
Erreur : Utiliser des origines wildcard avec credentials ou oublier la gestion du preflight, causant des échecs navigateur.
Correctif sûr : Utilisez une liste d'autorisation stricte et confirmez les en-têtes avec curl.
En-têtes attendus pour origine autorisée :
Access-Control-Allow-Origin: https://app.example.comVary: OriginAccess-Control-Allow-Credentials: true
6) Tailles de corps bornées
Erreur : Limites de taille de corps par défaut trop petites (rejetant requêtes valides) ou trop grandes (risque de pression mémoire).
Correctif sûr : Définissez express.json({ limit: '1mb' }) ou une taille adaptée à votre plus gros payload JSON attendu. Enquêtez sur les pics avant d'augmenter les limites.
7) Journalisation cohérente sans secrets
Erreur : Logs verbeux en production fuite tokens ou PII ; pas d'IDs de requête ; pas de différence de format entre environnements.
Correctif sûr : Utilisez morgan pour les logs de requêtes et masquez les champs sensibles dans des tokens personnalisés si nécessaire. Gardez les logs au niveau info en production et debug localement.
const morgan = require('morgan');
morgan.token('reqid', (req) => req.headers['x-request-id'] || '-');
app.use(morgan(':reqid :method :url :status :res[content-length] - :response-time ms'));
8) Gestion d'erreurs centralisée
Erreur : Lancer des erreurs dans les gestionnaires de routes sans catch, ou fuir traces de pile en production.
Correctif sûr : Transmettez toujours à next(err) ou lancez dans des gestionnaires async enveloppés par un helper ; assurez un gestionnaire d'erreurs final qui répond avec messages assainis en production.
function asyncHandler(fn) {
return (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
}
app.get('/example', asyncHandler(async (req, res) => {
// ...
res.json({ ok: true });
}));
Vérification et diagnostics
Utilisez des vérifications observables pour confirmer le comportement après chaque changement.
1. En-têtes et bases de sécurité
curl -i http://localhost:3000/health- Attendu :
X-DNS-Prefetch-Control,X-Frame-Options,X-Content-Type-Optionsdepuis helmet. Pas d'en-têteX-Powered-By.
2. Liste d'autorisation CORS
curl -i -X OPTIONS http://localhost:3000/health \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: GET"
- Attendu : 204 ou 200 avec
Access-Control-Allow-Origin: https://app.example.cometAccess-Control-Allow-Credentials: true. - Essayez une origine non listée et attendez soit pas d'en-têtes CORS soit un 403 selon votre politique.
3. Trust proxy et sensibilisation HTTPS
curl -i http://localhost:3000/health -H "X-Forwarded-Proto: https" -H "X-Forwarded-For: 203.0.113.7"
- Si derrière un proxy, simulez les en-têtes transférés :
- Attendu : JSON montrant
secure: trueet une IP client plutôt que seulement l'IP du proxy (selon réglage trust proxy).
4. Limites de taille de corps
- Envoyez un payload légèrement sous votre limite et attendez 200. Puis envoyez-en un au-dessus pour confirmer 413.
Exemple (tailles construites) :
import requests, json
payload_ok = 'x' * (1024 * 100) # 100 KiB
payload_big = 'x' * (1024 * 2048) # 2 MiB
print('OK', requests.post('http://localhost:3000/echo', json={'d': payload_ok}).status_code)
print('BIG', requests.post('http://localhost:3000/echo', json={'d': payload_big}).status_code)
5. Comportement du gestionnaire d'erreurs
curl -i http://localhost:3000/boom
- Déclenchez une erreur et confirmez la rédaction en production.
- Attendu : 500 avec
{ "error": "Internal Server Error" }en production ; pile seulement en non-production.
6. Timeouts
- Utilisez une route de test qui délaye la réponse au-delà de
server.setTimeoutet confirmez que le client obtient un timeout et que le serveur loggue un événement de timeout.
Erreurs courantes et correctifs sûrs (référence rapide)
Le tableau ci-dessous résume les pièges fréquents. Toutes les lignes sont des exemples construits.
| Erreur | Symptôme | Correctif sûr | Vérification rapide |
|---|---|---|---|
| Trust proxy manquant | cookies sécurisés non définis ; req.secure false | app.set('trust proxy', 1) derrière un proxy unique | curl avec X-Forwarded-Proto: https montre secure: true |
| CORS wildcard avec credentials | Navigateurs rejettent ; erreurs CORS console | Utiliser liste d'autorisation et retourner origine exacte | Preflight OPTIONS retourne origine autorisée seulement |
| Taille de corps non bornée | Pics mémoire ; risque DoS | express.json({ limit: '1mb' }) | Payload > limite retourne 413 |
| Pas de gestionnaire d'erreurs centralisé | Dumps HTML d'erreurs ; piles fuient | Middleware d'erreur final ; rédiger en prod | Réponse 500 sans pile en prod |
| Défauts pour timeouts | Sockets en suspens ; risque slow-loris | server.setTimeout(...) et headers/keepalive | Route longue coupée comme attendu |
| Logs verbeux prod | Données sensibles dans logs | Format morgan ; éviter corps/tokens | Logs requêtes montrent champs minimaux |
Modes de défaillance et récupération
1. Trust proxy mal configuré
- Défaillance : Après activation trust proxy,
req.ipmontre une IP proxy privée ousecuredevient true pour du trafic HTTP inattendu. - Diagnostic : Log
req.ip,req.ips,req.secure, et en-têtesX-Forwarded-*; comparez au chemin réseau attendu. - Récupération : Revenez à la valeur précédente de
app.set('trust proxy')(0 ou false), ou resserrez au bon nombre de sauts. Gardez une variable d'environnement (TRUST_PROXY) pour qu'un retour arrière soit un simple changement d'env et redémarrage.
2. CORS trop strict
- Défaillance : Navigateurs échouent avec erreurs CORS après déploiement d'une liste d'autorisation.
- Diagnostic : Inspectez l'en-tête
Originde la requête navigateur et comparez avec la liste d'autorisation ; lancez curl preflight. - Récupération : Ajoutez temporairement l'origine manquante à
CORS_ORIGINS, ou désactivez le mode strict en non-production pour trier. N'utilisez jamais wildcard avec credentials.
3. Limite de corps trop petite
- Défaillance : Clients obtiennent 413 Payload Too Large pour requêtes légitimes.
- Diagnostic : Inspectez
Content-Lengthet logs ; confirmez le réglage de limiteexpress.json. - Récupération : Augmentez la limite par paliers (ex: 1mb -> 2mb) et ajoutez surveillance d'usage mémoire. Gardez limites petites par défaut et n'escaladez que pour routes spécifiques si nécessaire.
4. Timeout trop agressif
- Défaillance : Requêtes normales échouent intermittemment avec timeouts réseau.
- Diagnostic : Comparez percentiles de latence de route à la valeur
server.setTimeout. - Récupération : Augmentez légèrement le timeout et optimisez gestionnaires longs. Considérez streaming ou traitement d'arrière-plan pour tâches vraiment longues.
5. Gestionnaire d'erreurs révèle piles en production
- Défaillance : Utilisateurs voient traces de pile ; bots capturent détails d'implémentation.
- Diagnostic :
NODE_ENVpas défini à production ; gestionnaire d'erreurs inclut pile inconditionnellement. - Récupération : Définissez
NODE_ENV=productionet conditionnez sortie de pile à une vérification d'environnement. Ajoutez une assertion CI/démarrage que la production rejette réglages non-sûrs.
6. Surcharge de journalisation
- Défaillance : CPU ou usage disque élevé ; coûts d'ingestion logs explosent.
- Diagnostic : Pic de volume corrélé au niveau de log ou nouveaux champs structurés.
- Récupération : Réduisez niveau de log, supprimez logs bruyants, et faites rouler les logs. Gardez une variable
LOG_LEVELpar env pour qu'un retour arrière soit un redémarrage.
Habitudes de retour arrière qui fonctionnent
- Utilisez des variables d'environnement pour les bascules (
TRUST_PROXY,JSON_LIMIT,LOG_LEVEL). Les retours arrière sont alors des flips d'env plus un redémarrage. - Gardez un extrait de config précédente-connue-bonne minimal validé aux côtés de l'app.
- Pour chaque changement, écrivez une note de changement d'un paragraphe avec avant/après et la commande de vérification utilisée.
- Testez le chemin de retour arrière avant le déploiement : basculez le toggle localement et confirmez que le comportement revient.
Checklist opérations
Utilisez ceci pour planifier, exécuter, et vérifier changements de configuration. Items sont des exemples construits.
1. Planifier
- Capturer versions :
node -v,npm ls express - Identifier topologie : y a-t-il un proxy inverse ? Où TLS termine-t-il ?
- Déclarer changement prévu et résultat observable attendu (en-tête, statut, log, ou métrique)
2. Préparer
- Ajouter ou confirmer validation d'environnement dans
config.js - Ajouter toggles d'env pour le changement
- Écrire un test curl ou navigateur pour vérifier comportement
3. Changer
- Appliquer la plus petite modification viable (ex:
app.set('trust proxy', 1)) - Redémarrer le processus dans un environnement contrôlé
4. Vérifier
- Lancer les tests curl préparés et enregistrer sorties
- Vérifier logs pour lignes attendues et absence d'erreurs
- Confirmer pas de régressions sur endpoints clés (
/health, auth, uploads)
5. Surveiller
- Surveiller taux d'erreurs, latence, et usage ressources 30-60 minutes (guide construit)
6. Retour arrière (si nécessaire)
- Rebasculer le toggle d'env
- Redémarrer
- Relancer vérification pour confirmer comportement précédent
Conclusion
Express est flexible, mais cette flexibilité invite à de subtiles erreurs de configuration. Une base sûre va loin : valider l'environnement tôt, ordonner les middlewares intentionnellement, configurer trust proxy correctement, verrouiller CORS, borner les tailles de requêtes, ajouter un gestionnaire d'erreurs centralisé, et configurer timeouts et logs explicites. Traitez chaque changement comme une petite expérience mesurable que vous pouvez vérifier localement et annuler vite. Avec ces habitudes, vous pouvez faire évoluer votre configuration API en confiance et garder les incidents dus à de petits faux pas hors de votre rotation d'astreinte.