SSD Nodes Learn 🎉 VPS dès $5.50/mois
Guides Matt ConnorPar Matt Connor · Mis à jour le 2026-08-21

Contourner le CGNAT avec un tunnel inverse sur un VPS

Derrière un CGNAT, la redirection de port échoue. Configurez frp sur un VPS, ouvrez le tunnel depuis chez vous et terminez HTTPS avec un vrai certificat.

Pourquoi la redirection de port ne fonctionne pas derrière un CGNAT

Derrière un CGNAT (Carrier-Grade Network Address Translation), l’adresse WAN de votre routeur est partagée avec d’autres abonnés. Aucune adresse IP publique ne vous est attribuée et aucun port ne peut être redirigé vers votre réseau. Un tunnel inverse résout ce problème : un VPS peu coûteux fournit l’adresse IP publique, votre serveur domestique ouvre une connexion sortante vers le VPS, puis les requêtes entrantes empruntent la connexion déjà ouverte par le serveur domestique. Vous conservez le matériel que vous possédez déjà. Vous louez uniquement ce que votre FAI ne vous fournit pas : une adresse routable.

Chaque commande ci-dessous indique la machine sur laquelle elle doit être exécutée. Cette configuration nécessite deux machines : le VPS avec une adresse IP publique et le serveur domestique qui exécute le service auquel vous voulez accéder.

Comment savoir si vous êtes réellement derrière un CGNAT

Ouvrez la page d’administration de votre routeur et relevez l’adresse WAN qu’il indique. Demandez ensuite à Internet quelle adresse il voit.

# on the home box
curl -4 -s https://ifconfig.me; echo

Si les deux adresses correspondent, vous disposez d’une IP publique et vous n’avez pas besoin de tout cela. Redirigez le port et arrêtez votre lecture. Si l’adresse WAN du routeur se trouve dans 100.64.0.0/10, vous êtes derrière un CGNAT. Il s’agit de l’espace d’adressage partagé défini par la RFC 6598, réservé précisément à cet usage. Certains FAI placent plutôt 10.0.0.0/8 côté WAN. La situation est identique, même si l’étiquette change.

Vérifiez un point avant de louer quoi que ce soit. De nombreux FAI utilisant un CGNAT fournissent un préfixe IPv6 réel. Si votre équipement domestique possède une adresse IPv6 globale, vous pouvez ouvrir le firewall sur cette adresse et vous passer complètement du tunnel. Cela cesse de fonctionner dès qu’un visiteur utilise un réseau IPv4 uniquement. C’est pourquoi la plupart des utilisateurs finissent malgré tout par arriver ici.

Fonctionnement d’un tunnel inverse vers un VPS, établi depuis le réseau interne

Le CGNAT et les routeurs domestiques classiques bloquent les connexions entrantes non sollicitées. Les pare-feu d’entreprise font de même. Aucun de ces équipements ne bloque les connexions sortantes, car chaque navigateur et chaque client de mise à jour en établit toute la journée. Lorsqu’un équipement NAT détecte une connexion TCP sortante, il crée une translation pour cette connexion, puis autorise le trafic de retour associé. Aucun équipement externe ne peut démarrer une connexion vers votre machine à domicile. C’est donc la machine à domicile qui l’établit, et le tunnel achemine le trafic en retour sur cette même connexion, dans l’autre sens.

C’est tout le mécanisme. La machine à domicile se connecte au VPS sur un port et maintient la connexion ouverte. Le VPS accepte les requêtes publiques et les transmet par cette connexion existante. Rien ne tente d’atteindre votre adresse IP domestique. Ce n’est donc pas nécessaire.

Deux conséquences en découlent, et elles sont utiles. Votre enregistrement DNS pointe vers le VPS, jamais vers votre domicile. Votre adresse publique est désormais celle du VPS. Ainsi, les informations obtenues par un observateur lors d’une recherche d’adresse IP concernent un serveur loué, et non votre connexion à domicile.

Trois façons de le mettre en place

  1. ssh -R : déjà installé aux deux extrémités, il convient à un seul service ou à une démonstration temporaire. Il n’offre ni tableau de bord ni véritable logique de reconnexion.
  2. frp : un petit serveur Go (frps) et un client correspondant (frpc). Il convient à une configuration permanente avec plusieurs services derrière un même nom d’hôte. C’est la méthode principale de ce guide.
  3. Un VPN mesh : Tailscale ou un serveur WireGuard que vous administrez vous-même. Il convient lorsque vous voulez que vos propres appareils communiquent entre eux en privé, plutôt que de publier un service sur Internet.

Choisissez le mesh si votre objectif est d’accéder en privé à vos services depuis des appareils que vous contrôlez. Tailscale Serve et Funnel explique comment publier un service hors d’un tailnet, tandis que un VPN WireGuard auto-hébergé sur le même VPS fournit une architecture similaire sans serveur de coordination tiers sur le chemin. Consultez l’une de ces pages et ignorez le reste de celle-ci. Tout ce qui suit suppose que vous voulez un nom d’hôte HTTPS public que n’importe qui peut ouvrir.

La version rapide : ssh -R pour un seul service

Supposons que le serveur du domicile exécute une application sur 127.0.0.1:3000 et que vous disposiez déjà d’un accès SSH au VPS.

# on the home box
ssh -N \
  -o ExitOnForwardFailure=yes \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  -R 127.0.0.1:8080:127.0.0.1:3000 \
  tunnel@vps.example.com

-R 127.0.0.1:8080:127.0.0.1:3000 indique à sshd du VPS d’écouter sur son propre 127.0.0.1:8080 et de transmettre tout ce qui y arrive vers 127.0.0.1:3000 sur le serveur du domicile. -N signifie qu’aucun shell ne doit être lancé. Les deux options ServerAlive permettent à ssh de détecter une liaison interrompue en environ 90 secondes, au lieu de rester bloqué sur une connexion qui n’existe plus.

C’est ici que la plupart des utilisateurs se trompent. L’écouteur est lié à loopback, donc curl http://vps.example.com:8080 échoue depuis n’importe quelle autre machine. sshd utilise par défaut GatewayPorts no, ce qui signifie qu’une redirection distante est liée uniquement à l’interface loopback. Ne corrigez pas cela en définissant GatewayPorts yes. Laissez la redirection sur loopback et placez nginx devant, comme dans la configuration frp ci-dessous : le port public est alors 443 avec un certificat, et le port du tunnel n’est jamais exposé à Internet. Si vous ne savez pas ce qui écoute actuellement ni sur quelle interface, consultez cette brève présentation des ports et des écouteurs sous Linux, qui ne prend qu’une dizaine de minutes.

Si le port est déjà utilisé sur le VPS, ssh affiche ce message, et ExitOnForwardFailure=yes lui indique d’abandonner au lieu d’établir une connexion sans tunnel fonctionnel :

Warning: remote port forwarding failed for listen port 8080

La cause habituelle est une session précédente qui s’est interrompue sans que sshd le détecte. Définissez ClientAliveInterval 30 et ClientAliveCountMax 3 dans /etc/ssh/sshd_config du VPS afin que les sessions interrompues soient supprimées et que le port soit libéré. Placez toute la commande dans une unité systemd avec Restart=always et une clé dédiée, ou utilisez autossh. Pour plusieurs services, arrêtez-vous ici et utilisez frp.

Installer frp sur le VPS en épinglant une version

frp est distribué sous la forme d’un binaire Go statique. Il ne figure pas dans les archives Ubuntu ou Debian. Vous devez donc télécharger une release et la vérifier vous-même. Épinglez la version utilisée. Le format de configuration a changé avec v0.52.0, et les noms des options ont encore évolué depuis. Un tutoriel obsolète peut donc vous fournir des clés que votre binaire ne reconnaît pas. Ce guide utilise v0.71.0, publiée le 14 août 2026.

# on the VPS
FRP_VERSION=0.71.0
ARCH=amd64   # use arm64 if `uname -m` prints aarch64
cd /tmp
curl -fsSLO "https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/frp_${FRP_VERSION}_linux_${ARCH}.tar.gz"
curl -fsSLO "https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/frp_sha256_checksums.txt"
sha256sum --check --ignore-missing frp_sha256_checksums.txt

sha256sum doit afficher exactement une ligne :

frp_0.71.0_linux_amd64.tar.gz: OK

--ignore-missing est nécessaire, car le fichier de sommes de contrôle couvre les dix-huit assets de la release, alors que vous n’en avez téléchargé qu’un. Sans ce flag, sha256sum signale les dix-sept autres comme manquants et se termine avec un code différent de zéro. Cela ressemble à un échec de vérification alors que tout fonctionne correctement.

# on the VPS
tar xzf "frp_${FRP_VERSION}_linux_${ARCH}.tar.gz"
sudo install -m 755 "frp_${FRP_VERSION}_linux_${ARCH}/frps" /usr/local/bin/frps
sudo useradd --system --no-create-home --shell /usr/sbin/nologin frp
sudo install -d -m 750 -o root -g frp /etc/frp
frps --version

frps --version affiche 0.71.0. Seul frps doit être installé sur le VPS. frpc doit être installé sur la machine du domicile. Installer les deux binaires partout est le meilleur moyen de lancer accidentellement un serveur de tunnel à domicile.

Configuration du VPS : token, TLS forcé, écouteurs loopback

Générez d’abord un token. C’est la seule protection entre votre tunnel et toute personne qui analyse les ports du VPS.

# on the VPS
openssl rand -base64 32

Écrivez cette valeur dans /etc/frp/frps.toml :

bindAddr = "0.0.0.0"
bindPort = 7000

# Every listener frp creates for a proxy, including the HTTP vhost, stays on loopback.
proxyBindAddr = "127.0.0.1"
vhostHTTPPort = 8080

auth.method = "token"
auth.token = "PASTE_THE_OPENSSL_OUTPUT_HERE"

transport.tls.force = true

webServer.addr = "127.0.0.1"
webServer.port = 7500
webServer.user = "admin"
webServer.password = "PASTE_A_SECOND_SECRET_HERE"

log.level = "info"

Quatre de ces lignes assurent la sécurité. Examinez-les une par une.

auth.token doit correspondre à auth.token sur le client. Sans cette valeur, frps accepte n’importe quel client qui trouve le port 7000. Ce client peut alors publier ce qu’il veut via votre VPS et votre certificat.

transport.tls.force = true refuse toute connexion de contrôle qui n’utilise pas TLS (transport layer security). Les clients activent TLS par défaut depuis la version v0.50.0. En pratique, cela ne vous coûte rien et empêche un ancien client ou un client écrit manuellement de se connecter en clair sans vous en informer.

proxyBindAddr = "127.0.0.1" est la ligne que la plupart des guides omettent. C’est elle qui permet de laisser cette configuration active en toute sécurité. Elle déplace vers l’interface loopback tous les écouteurs que frp ouvre pour le compte d’un proxy, aussi bien le vhost HTTP que tout remotePort demandé par un client. Internet ne peut pas atteindre ces écouteurs. La seule porte publique est nginx sur 443, que vous configurez et contrôlez.

webServer.addr = "127.0.0.1" maintient le dashboard hors de l’interface publique. Le dashboard fournit une vue complète de vos services privés et de leur trafic. Il est protégé par un seul mot de passe HTTP basic auth. Il ne doit donc pas être exposé sur 0.0.0.0.

Définissez le propriétaire afin que le token ne soit pas lisible par tout le monde, puis vérifiez la syntaxe avant de démarrer quoi que ce soit :

# on the VPS
sudo chown root:frp /etc/frp/frps.toml
sudo chmod 640 /etc/frp/frps.toml
sudo -u frp frps verify -c /etc/frp/frps.toml

Un fichier valide affiche :

frps: the configuration file /etc/frp/frps.toml syntax is ok

Une précision sur le format vous fera gagner une heure. frp choisit son parser à partir de l’extension du fichier et reconnaît .toml, .yaml, .yml et .json. Les anciens fichiers .ini sont toujours chargés via un chemin de conversion legacy, mais le format INI est obsolète et les nouvelles options sont documentées uniquement pour TOML. Si un tutoriel vous montre une section [common] et server_addr = x.x.x.x, il est antérieur à la version v0.52.0 et les noms de ses clés ne correspondront pas au binaire que vous venez d’installer.

Exécuter frps comme un service sans privilèges

bindPort est 7000 et vhostHTTPPort est 8080. Les deux ports sont supérieurs à 1024. frps n’a donc jamais besoin de root ni de CAP_NET_BIND_SERVICE. C’est la raison pour laquelle il ne faut pas placer le vhost sur le port 80 et laisser nginx l’utiliser à la place.

Écrivez /etc/systemd/system/frps.service :

[Unit]
Description=frp reverse tunnel server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=frp
Group=frp
ExecStart=/usr/local/bin/frps -c /etc/frp/frps.toml
Restart=on-failure
RestartSec=5s
LimitNOFILE=65535
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true

[Install]
WantedBy=multi-user.target
# on the VPS
sudo systemctl daemon-reload
sudo systemctl enable --now frps
sudo journalctl -u frps -n 20 --no-pager

Le journal doit afficher les deux listeners. Les adresses sont plus importantes que les ports :

frps tcp listen on 0.0.0.0:7000
http service listen on 127.0.0.1:8080

ProtectSystem=strict rend l’ensemble du système de fichiers accessible en lecture seule pour ce service. frps le tolère, car son journal est envoyé vers la sortie standard par défaut et capturé par journald. Si vous définissez log.to avec un chemin de fichier, le service ne peut pas y écrire tant que vous n’avez pas ajouté une ligne ReadWritePaths= correspondante. Laissez donc la valeur par défaut.

Pare-feu : ouvrez un seul port, pas une plage

# on the VPS
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 7000/tcp
sudo ufw enable
sudo ufw status numbered

Quatre règles, dont une sert uniquement au renouvellement des certificats. 22 correspond à SSH. 80 redirige vers 443 et répond au challenge ACME (automatic certificate management environment). 443 sert toutes les applications tunnelisées. 7000 est le port de contrôle de frp, et c’est le seul port auquel un client doit pouvoir accéder.

Les guides qui vous demandent d’ouvrir une plage telle que sudo ufw allow 20000:30000/tcp décrivent l’autre architecture, dans laquelle chaque service utilise son propre port TCP public. Ce n’est pas nécessaire ici, car toutes les connexions arrivent sur 443 et frp les achemine selon le nom d’hôte. Si vous devez ensuite exposer un véritable port TCP public, rétablissez proxyBindAddr vers 0.0.0.0 et ajoutez des limites afin qu’un client ne puisse utiliser que les ports que vous avez indiqués :

allowPorts = [
  { start = 20000, end = 20010 }
]
maxPortsPerClient = 5

La plupart des fournisseurs exécutent également un pare-feu réseau dans le panneau de contrôle, séparément d’ufw sur le serveur. Une règle qui semble correcte dans sudo ufw status mais dont les connexions expirent quand même est généralement bloquée à ce niveau. Les règles ufw réellement nécessaires sur un VPS explique la configuration par défaut qui refuse tout le trafic, utilisée dans cette section.

Terminer HTTPS sur le VPS avec un vrai certificat

Pointez un enregistrement A vers home.example.com, à l’adresse IP publique du VPS. Pas vers votre domicile. Votre domicile n’a pas d’adresse vers laquelle pointer, et c’est précisément le problème que vous êtes en train de résoudre.

# on the VPS
sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx

Créez d’abord /etc/nginx/sites-available/home.example.com avec un bloc utilisant uniquement le port 80, afin que certbot dispose d’un server_name correspondant :

server {
    listen 80;
    listen [::]:80;
    server_name home.example.com;
    location / { return 404; }
}
# on the VPS
sudo ln -s /etc/nginx/sites-available/home.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot certonly --nginx -d home.example.com

nginx -t affiche nginx: configuration file /etc/nginx/nginx.conf test is successful lorsque les fichiers sont syntaxiquement valides. Exécutez cette commande avant chaque reload. Si un reload échoue, nginx conserve l’ancienne configuration active. Une modification incorrecte donne donc l’impression de n’avoir rien changé.

Les upgrades WebSocket nécessitent un map au niveau http. Placez-le dans /etc/nginx/conf.d/upgrade.conf :

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

Remplacez maintenant le fichier du site par la configuration réelle :

server {
    listen 80;
    listen [::]:80;
    server_name home.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name home.example.com;

    ssl_certificate     /etc/letsencrypt/live/home.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/home.example.com/privkey.pem;

    client_max_body_size 512m;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade           $http_upgrade;
        proxy_set_header Connection        $connection_upgrade;
        proxy_read_timeout 3600s;
    }
}

proxy_set_header Host $host; est indispensable ici. Le vhost HTTP de frp route les requêtes selon l’en-tête Host, en le comparant à la liste customDomains de la configuration du client. Si cet en-tête est absent, nginx envoie Host: 127.0.0.1, frp ne trouve aucun proxy correspondant à ce nom et le visiteur reçoit un simple 404 de frp au lieu de la page de l’application. Explication de chaque ligne d’un bloc reverse proxy nginx détaille le rôle des autres en-têtes.

# on the VPS
sudo nginx -t && sudo systemctl reload nginx
sudo certbot renew --dry-run

Le dry run confirme que le renouvellement fonctionnera dans quatre-vingt-dix jours, lorsque vous ne serez pas là pour le surveiller. Le port 80 doit être accessible, d’où la règle ufw.

Côté réseau local : frpc comme service sans privilèges

Installez frpc sur la machine du réseau local exactement comme vous avez installé frps, avec la même version et la même vérification du checksum, puis créez le même utilisateur frp et le même répertoire /etc/frp. Écrivez /etc/frp/frpc.toml :

serverAddr = "vps.example.com"
serverPort = 7000

auth.method = "token"
auth.token = "PASTE_THE_SAME_TOKEN_HERE"

transport.tls.enable = true
loginFailExit = false

proxies = [
  { name = "home-app", type = "http", localIP = "127.0.0.1", localPort = 3000, customDomains = ["home.example.com"] }
]

L’ordre des clés est important dans ce fichier, et pas pour des raisons de style. TOML associe chaque clé qui suit un en-tête de table à cette table. Ainsi, un paramètre de niveau supérieur comme serverAddr, placé sous l’en-tête d’une table de proxy, devient silencieusement un paramètre du proxy que frp ignore. L’écriture de la liste des proxys sous forme de tableau inline, comme ci-dessus, évite ce piège : chaque clé de niveau supérieur reste clairement au niveau supérieur.

type = "http" fait passer ce proxy par le listener vhost au lieu de lui attribuer son propre port TCP public. C’est pourquoi le firewall est resté limité à quatre règles. customDomains doit contenir le hostname que nginx transmet dans l’en-tête Host. Il s’agit donc de home.example.com, et jamais de l’adresse IP du VPS.

loginFailExit = false est plus important qu’il n’y paraît. La valeur par défaut est true, ce qui fait quitter frpc si sa première tentative de connexion échoue. Sur une machine du réseau local qui termine son boot avant que la liaison du FAI soit disponible, le service reste alors arrêté jusqu’à ce que vous le remarquiez. Définissez-le sur false afin que frpc continue à réessayer jusqu’à ce que le VPS réponde.

Écrivez /etc/systemd/system/frpc.service :

[Unit]
Description=frp reverse tunnel client
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=frp
Group=frp
ExecStart=/usr/local/bin/frpc -c /etc/frp/frpc.toml
Restart=always
RestartSec=10s
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true

[Install]
WantedBy=multi-user.target
# on the home box
sudo chown root:frp /etc/frp/frpc.toml
sudo chmod 640 /etc/frp/frpc.toml
sudo -u frp frpc verify -c /etc/frp/frpc.toml
sudo systemctl daemon-reload
sudo systemctl enable --now frpc
sudo journalctl -u frpc -n 20 --no-pager

Un client qui s’est connecté journalise un identifiant d’exécution :

login to server success, get run id [3a1f9c2b7d4e5f60]

Ouvrez https://home.example.com dans un navigateur. Vous devriez obtenir l’application qui s’exécute sur 127.0.0.1:3000 dans le réseau local. Restart=always côté client est volontaire : les connexions du réseau local peuvent être interrompues, et le service doit revenir automatiquement.

Laissez le tableau de bord hors de l’interface publique

Avec webServer.addr = "127.0.0.1", le tableau de bord répond uniquement sur le VPS lui-même. Accédez-y depuis votre ordinateur portable avec une redirection locale au lieu d’ouvrir un port :

# on your laptop
ssh -N -L 7500:127.0.0.1:7500 you@vps.example.com

Ouvrez http://127.0.0.1:7500 et connectez-vous avec webServer.user et webServer.password depuis frps.toml. La page liste chaque client connecté ainsi que les compteurs de trafic de chaque proxy. C’est le moyen le plus rapide de vérifier si le serveur du domicile est actuellement connecté. Fermez la session SSH et le tableau de bord redevient inaccessible.

Ce que le tunnel ne fait pas

Lisez cette partie deux fois, car c’est là que les erreurs peuvent avoir de graves conséquences. Le tunnel rend un service privé accessible depuis Internet. Il n’authentifie pas les personnes qui y accèdent. Dès que https://home.example.com sera résolu, des scanners le trouveront en quelques jours, que vous ayez communiqué ou non le nom. Les journaux de transparence des certificats publient chaque nom d’hôte pour lequel vous demandez un certificat. Le nom devient donc public dès que certbot réussit.

Tout ce que vous exposez doit assurer sa propre authentification. Si l’application propose une véritable connexion avec limitation du débit, c’est suffisant. Si sa connexion repose sur un mot de passe partagé, ou si elle ne propose aucune connexion, placez un proxy d’authentification devant elle sur le VPS. Un oauth2-proxy placé devant l’application est la solution habituelle. Il s’intercale entre nginx et le vhost frp sans modifier les deux extrémités du tunnel.

Le token dans frps.toml protège le tunnel, pas les applications. Il empêche un tiers d’enregistrer son propre proxy sur votre VPS. Il ne fait rien pour une requête qui arrive sur 443 avec un nom d’hôte que vous avez volontairement publié.

Deux habitudes sont utiles. Faites tourner le token en modifiant les deux fichiers, puis en redémarrant les deux services, car il n’expire jamais automatiquement. Maintenez également frp à jour : ce binaire constitue votre point d’entrée exposé sur Internet, et les notes de version v0.71.0 signalent un panic du serveur déclenché par une valeur incorrecte envoyée par un client. C’est le type de bug qu’il vaut mieux corriger que tenter d’analyser.

Failure modes, with the strings you will see

The client never connects. journalctl -u frpc repeats connect to server error: followed by a dial timeout. Nothing is reaching port 7000. Check ufw on the VPS, then the provider's network firewall in the control panel, then confirm the name resolves with getent hosts vps.example.com.

The token is wrong. The client says so in as many words:

login to the server failed: token in login doesn't match token from configuration

Copy the token again. A trailing newline, or a $ in an unquoted shell string that expanded to nothing, causes almost all of these. That is why the openssl rand -base64 32 output belongs inside quotes in the TOML file.

The tunnel is up but the browser gets a bare 404. frpc logged a successful login and the dashboard lists the proxy, yet the page returns 404 with none of the app's styling. That is frp reporting it has no proxy for this Host header. Test the vhost directly on the VPS, bypassing both nginx and TLS:

# on the VPS
curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: home.example.com' http://127.0.0.1:8080/

A 404 from that command means customDomains is wrong. Any other code means the request never got the right Host from nginx.

502 from nginx. nginx is answering and frp is not. sudo ss -lntp | grep 8080 on the VPS should show frps listening on 127.0.0.1:8080. Empty output means frps is stopped, or vhostHTTPPort is unset in frps.toml.

The app thinks every visitor is local. Your app logs 127.0.0.1 for every request. frp sets X-Forwarded-For and nginx appends to it, so the real client address is in that header. Configure the app to trust it. Do not skip this if the app rate limits by IP address, because at the moment every visitor on the internet shares one bucket.

Long requests get cut at 60 seconds. Uploads or streaming responses stop partway through. That is nginx's default proxy_read_timeout, not the tunnel. The block above raises it to 3600s. client_max_body_size is the matching limit for upload size, and its default of 1 MB rejects larger bodies with a 413.

Everything works, then dies after a router reboot. Restart=always in the frpc unit plus loginFailExit = false covers this. Confirm with sudo systemctl is-enabled frpc, which must print enabled.

FAQ

Comment savoir si je suis derrière un CGNAT ?

Comparez l’adresse WAN affichée dans la page d’administration de votre routeur avec celle que curl -4 -s https://ifconfig.me renvoie depuis le même réseau. Si elles diffèrent et que l’adresse WAN du routeur appartient à 100.64.0.0/10, votre FAI utilise un NAT de niveau opérateur. Cette plage correspond à l’espace d’adressage partagé RFC 6598 et existe précisément pour cet usage. Certains FAI utilisent plutôt 10.0.0.0/8 côté WAN, ce qui signifie la même chose. Si les deux adresses correspondent, vous avez une adresse IP publique : transférez le port et vous avez terminé.

Ai-je besoin d’un nom de domaine pour un tunnel inverse ?

Pour la configuration HTTPS décrite ici, oui. Un certificat est émis pour un nom d’hôte, et le vhost HTTP de frp achemine les requêtes selon l’en-tête Host ; les deux extrémités doivent donc utiliser un nom commun. Un proxy TCP brut sur un port numéroté fonctionne avec l’adresse IP seule du VPS, sans aucun domaine, mais vous n’avez alors ni certificat ni routage par nom d’hôte. Un seul port public peut donc servir exactement un service.

Est-il sûr d’exécuter frp sur un VPS public ?

Oui, si seul le port de contrôle est exposé et qu’il est authentifié. Définissez auth.token avec une valeur aléatoire sur les deux extrémités, puis configurez transport.tls.force = true sur le serveur. Définissez ensuite proxyBindAddr = "127.0.0.1" afin qu’aucun élément ouvert par frp pour un proxy ne soit accessible depuis Internet, et laissez le tableau de bord sur webServer.addr = "127.0.0.1", accessible via une redirection locale SSH. Mettez à jour le binaire lorsque de nouvelles versions sont publiées, car c’est le processus qui écoute sur votre adresse publique.

Pourquoi personne ne peut-il accéder à mon port transféré ssh -R ?

sshd est fourni avec GatewayPorts no, donc une redirection distante est liée uniquement à l’interface loopback du VPS. curl exécuté directement sur le VPS fonctionne, tandis que curl exécuté depuis un autre emplacement expire. La bonne solution consiste à laisser la redirection sur loopback et à placer nginx sur le port 443 devant celle-ci. Définir GatewayPorts yes publie un port brut sans certificat ni TLS, ce qui crée un problème plus grave que celui que cette option résout.

Dois-je utiliser frp ou un VPN maillé comme Tailscale ou WireGuard ?

Utilisez un VPN maillé lorsque seuls vos propres appareils doivent accéder au service. Rien n’est alors publié et aucun nom d’hôte public ne peut être analysé. Utilisez frp lorsque vous avez besoin d’une adresse HTTPS publique que n’importe quel navigateur peut charger, par exemple pour recevoir des webhooks ou partager une page avec des personnes qui n’installeront pas de client VPN. Les deux solutions fonctionnent sans problème sur un même VPS, avec des ports différents et des usages distincts.

#frp#cgnat#nat#tunnel#reverse-proxy