Nginx devant Perforce : terminaison TLS en niveau 4 et le protocole P4
Avant de mettre nginx devant Perforce, il faut se demander si c’est une bonne idée et j’ai traité cette question à part dans reverse proxy devant Perforce, le pour et le contre . Je pars ici du choix fait: on veut une terminaison TLS centralisée, gérée au même endroit et par le même outil ACME que le certificat de Jenkins. Reste à le faire correctement et correctement veut dire autrement qu’un tutoriel HTTP ne le suggère.
Perforce n’est pas du HTTP
Le serveur Perforce, p4d, parle son propre protocole RPC sur le port 1666. C’est un protocole binaire, stateful, à connexion longue, avec sa propre compression. Rien à voir avec HTTP.
Un proxy_pass http:// posé dans un bloc http de nginx ne proxifie donc rien pour un vrai client p4: nginx attend des requêtes HTTP et reçoit du protocole Perforce et au mieux la connexion se ferme, au pire elle traîne sans que rien ne passe. C’est l’erreur qu’on recopie sans la voir, parce que la conf démarre et que nginx ne se plaint pas.
La terminaison TLS centralisée se fait donc en niveau 4, avec le module stream, qui fait du proxy TCP: il accepte la connexion TLS du client, la déchiffre et relaie le flux Perforce en clair vers p4d sur la machine locale.
# contexte top-level de nginx.conf
stream {
upstream p4_backend {
server p4.domain.internal:1666;
}
server {
listen 1666 ssl;
proxy_pass p4_backend;
ssl_certificate ssl/p4.domain.internal.crt;
ssl_certificate_key ssl/p4.domain.internal.key;
ssl_protocols TLSv1.2 TLSv1.3;
# filtrage au niveau 4, lisible et fermé par défaut
allow 10.0.0.0/8; # à restreindre à vos plages réelles
allow 127.0.0.1;
deny all;
# voir plus bas: dimensionné pour l'idle long, pas pour le transfert
proxy_timeout 12h;
proxy_connect_timeout 10s;
proxy_socket_keepalive on;
}
}
Le backend est un FQDN interne, résolu par le DNS interne, pour que le jour où p4d déménage rien ne bouge dans nginx. Le filtrage se fait en allow/deny, la forme idiomatique au niveau 4. Côté client, la connexion devient P4PORT=ssl:p4.example.com:1666 et au premier contact un p4 trust enregistre l’empreinte du certificat présenté par nginx: le client ne voit jamais p4d, il voit nginx.
Un détail Windows qui n’en est pas un: le binaire nginx mainline pour Windows inclut le module stream, à vérifier d’un nginx -V sur une distribution exotique.
Pourquoi les sessions coupent et le tuning qui les tient
C’est le vrai sujet et il ne se voit qu’en production. Une sync de dépôt de jeu, ce sont des centaines de gigaoctets et des opérations qui durent des heures. Deux moments posent problème et aucun des deux n’est le transfert lui-même.
Le premier, c’est le temps que p4d passe à calculer côté serveur, un gros changelist par exemple, pendant lequel aucun octet ne circule sur la connexion alors qu’elle doit rester ouverte. Le second, c’est une session interactive laissée ouverte entre deux commandes.
Dans les deux cas, la connexion est inactive mais vivante et tout ce qui s’intercale sur le chemin, proxy TCP, firewall, NAT, VPN, la juge morte et la coupe en silence.
Côté nginx, le piège est proxy_timeout, qui vaut dix minutes par défaut dans le module stream et se déclenche dès qu’aucune donnée ne circule dans aucun sens. Sur un transfert actif de centaines de gigaoctets, il ne se déclenche jamais puisque les octets réarment le compteur en continu. Mais sur une phase de calcul serveur silencieuse, dix minutes sont vite dépassées et c’est exactement là que les sessions tombaient. Il faut donc le dimensionner non pas sur la durée du transfert, mais sur la plus longue plage d’inactivité plausible d’une connexion qu’on veut garder, ce qui mène à des heures et non des minutes. D’où le proxy_timeout 12h ci-dessus, à ajuster à votre réalité.
Le proxy_socket_keepalive on active SO_KEEPALIVE sur la connexion vers p4d, mais l’essentiel du keepalive se règle sur p4d lui-même, parce que c’est lui qui parle aux clients à travers les middleboxes.
Sur p4d, les configurables de keepalive sont inertes par défaut, tout à zéro, documentation Perforce
:
net.keepalive.idle: secondes d’inactivité avant le premier keepalivenet.keepalive.interval: intervalle entre keepalivesnet.keepalive.count: nombre de keepalives sans ACK avant de déclarer la connexion mortenet.keepalive.disable: laissé à 0 pour que le keepalive soit autorisé
Pour que les connexions survivent aux middleboxes, on pose idle et interval nettement sous la plus courte fenêtre d’inactivité du chemin, souvent de l’ordre de la trentaine de secondes si un firewall coupe au bout de quelques minutes. C’est le réglage qui fait tenir les sessions longues.
Trois autres tunables comptent pour un dépôt de jeu, orientés débit et rafales:
net.tcpsize, 512K par défaut, les buffers d’envoi et de réception posés à la connexion, qu’on monte sur gros fichiers ou lien à latence (restart serveur requis)net.backlog, 128 par défaut, la file des connexions en attente, à relever quand beaucoup d’agents de build se connectent en rafalenet.autotuneà 1 sur Linux, qui peut améliorer les connexions à longue latence, doc tuning réseau , à tester en surveillant les hangs
Le keepalive se règle aussi au niveau du système d’exploitation du client et Perforce a une note dédiée pour Windows là-dessus.
Ce que ça donne
Perforce derrière nginx, ce n’est pas un location HTTP, c’est un bloc stream qui termine le TLS en niveau 4 et relaie le TCP brut à p4d.
Le reste du travail n’est pas dans le proxy mais dans le réglage des connexions longues, parce qu’une sync de plusieurs heures passe l’essentiel de son temps soit à transférer, soit à attendre p4d et que c’est l’attente qui tue la session si le keepalive n’est pas armé des deux côtés et le proxy_timeout dimensionné en heures.
Les valeurs données ici sont celles d’une conf de référence, à caler sur les vôtres: le trio qui raconte vraiment comment les sessions tiennent, c’est proxy_timeout, proxy_socket_keepalive et les net.keepalive.* de p4d.
[edit: 2026] L’implémentation Ansible de tout ceci: le rôle nginx .