# Stealth Trojan VPN Behind Cloudflare Guide

September 9, 2025 · 4 min · Iman Alipour Comments

## Introduction#

So you want a VPN that doesn’t scream “I am a VPN” to every censor and firewall out there?
Welcome to the world of Trojan over WebSocket + TLS behind Cloudflare.

This guide not only shows you how to set it up but also sprinkles in some debugging magic so you can figure out why things break (and they will break, trust me).

We’ll anonymise domains and secrets, so substitute with your own:

- VPN domain: `web.example.com`
- Panel domain: `panel.example.com`
- Secret WS path: `/stealth-path_abcd1234`
- Password: `<PASSWORD>`

## Architecture at a Glance#

Think of it as a disguise party:

- Trojan = the shy guest (your VPN protocol)
- Nginx = the bouncer checking IDs (reverse proxy)
- Cloudflare = the doorman who makes sure nobody sees who’s inside (CDN & proxy)
- Your fake website = the mask (camouflage page)

Traffic flow:

## Step 1: Panel Setup (`panel.example.com`)#

- Bind the 3x-ui panel to localhost (e.g., `127.0.0.1:46309`).
- Choose a funky web base path like `/panel-bananas_42/`.
- Proxy it through Nginx with HTTPS + Basic Auth.
- Test it at `https://panel.example.com/panel-bananas_42/`.

Pro tip: If you see a blank page → your base path is mismatched or Nginx is eating it. Check logs!

## Step 2: Trojan Inbound#

In 3x-ui, create a Trojan inbound:

- Local port: `54321`
- Transport: WebSocket
- Path: `/stealth-path_abcd1234`
- Security: none
- Password: `<PASSWORD>`

## Step 3: Nginx for VPN Domain (`web.example.com`)#

Your Nginx is the gatekeeper. Sample config:

    server {
        listen 443 ssl http2;
        server_name web.example.com;

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

        # Fake website at root
        location / {
            root /var/www/html;
            index index.html;
        }

        # Real VPN under secret WS path
        location /stealth-path_abcd1234 {
            proxy_pass http://127.0.0.1:54321;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
        }
    }

## Step 4: Firewall Rules#

Lock things down! Only Cloudflare should reach you:

    ufw allow 22/tcp
    ufw allow 80/tcp
    ufw allow 443/tcp
    ufw deny 54321/tcp

## Step 5: Client Config#

Trojan URI:

iOS clients: Shadowrocket, Stash, FoXray
Android clients: v2rayNG, Clash Meta, NekoBox

## Debugging Time (a.k.a. “Why the heck doesn’t it work?!”)#

### Symptom: 520 Unknown Error (Cloudflare) #

- Likely cause: Nginx couldn’t talk to Trojan.
- Check Nginx error log:

      tail -n 50 /var/log/nginx/error.log

- If you see `upstream sent no valid HTTP/1.0 header` → you’re mixing HTTPS/HTTP between Nginx and Trojan.

### Symptom: 526 Invalid SSL certificate #

- Cloudflare → Nginx cert mismatch.
- Run:

      openssl s_client -connect web.example.com:443 -servername web.example.com -showcerts

- Make sure CN = `web.example.com`. If not, fix your cert.

### Symptom: Blank page on panel #

- Check if the base path matches exactly (case-sensitive, slash-sensitive).
- Watch for sneaky trailing spaces!
- Test locally:

      curl -s -D- http://127.0.0.1:46309/panel-bananas_42/

### Symptom: Client won’t connect #

- Run a WebSocket test:

      curl -i -k -H "Connection: Upgrade" -H "Upgrade: websocket" https://web.example.com/stealth-path_abcd1234

  Expect `101 Switching Protocols`. If not, check Nginx config.

### Symptom: Censor still blocks you #

- Did you expose another service (like SSH, Matrix, or Minecraft) on the same IP?
  → They’ll find your origin IP. Use a second VPS or lock those ports down.
- Did you use an obvious path like `/ws`?
  → Use a random-looking one like `/cdn-assets-329df/`.

## Pro Tips & Fun Tricks#

- Serve a fake blog or portfolio at root so your domain looks legit.
- Rotate WebSocket paths occasionally.
- Use Cloudflare Page Rules to disable Rocket Loader & Minify for your VPN domain.
- Make friends with your Nginx error.log — it will roast you but it tells the truth.

## Conclusion#

By putting Trojan behind Cloudflare, you’ve given your VPN a shiny new disguise:

- Looks like normal HTTPS.
- Hides your origin IP.
- Forces censors into a tough choice: block Cloudflare (and half the web) or let you pass.

Congrats — you’ve built a VPN with both style and stealth. 🥷

Open on GitHub ↗
Comments powered by Isso
RSS