If you've ever had a VPN blocked by a corporate firewall, a hotel network, or a restrictive ISP, you already know the problem. Most VPN protocols are trivially identifiable — they have distinct handshakes, use unusual ports, or simply don't look like anything a browser would generate. Trojan solves this.

Unlike traditional VPN obfuscation that wraps traffic in something VPN-shaped, Trojan tunnels your connection over real TLS on port 443. To any firewall doing deep packet inspection, it looks identical to a browser opening an HTTPS connection. To an active probe — where someone actually connects to your server to investigate — it responds with a real website.

This guide walks through the complete setup: building Caddy with the Trojan plugin on your VPS, then connecting from Windows, Linux, macOS, and Android.

What We're Building

Your Device → Trojan client (TLS/443) → VPS (Caddy + Trojan plugin)
                                              ↓ correct password
                                         Your traffic forwarded
                                              ↓ wrong password / probe
                                         Real website served

Caddy handles TLS termination, automatic Let's Encrypt certificates, and Trojan authentication all in one process — no separate Trojan server binary needed.

Prerequisites

  • A Linux VPS (Ubuntu 22.04 or Debian 12 recommended)
  • A domain name pointed at your VPS — a real domain, not just an IP, because you need a valid TLS certificate
  • Port 443 open on your VPS firewall
  • Root access

Part 1 — Building Caddy With the Trojan Plugin on Your VPS

Caddy's Trojan support comes as a plugin, which means you need to compile a custom Caddy binary rather than installing the standard package. This sounds harder than it is — the xcaddy build tool handles everything.

Step 1 — Install Go

xcaddy requires Go 1.21 or later. The version in Ubuntu's default repos is often outdated, so install directly from the official source:

wget https://go.dev/dl/go1.22.0.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
source ~/.bashrc
go version

You should see go version go1.22.0 linux/amd64 or similar.

Step 2 — Install xcaddy

go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest

xcaddy will be placed in ~/go/bin/. Add that to your path:

echo 'export PATH=$PATH:~/go/bin' >> ~/.bashrc
source ~/.bashrc

Step 3 — Build Caddy With the Trojan Plugin


xcaddy build --with github.com/imgk/caddy-trojan@latest

This downloads Caddy's source, pulls in the Trojan plugin, and compiles them together. It takes 1–2 minutes. When it finishes you'll have a caddy binary in your current directory.

Move it into place:

sudo mv caddy /usr/local/bin/caddy
sudo chmod +x /usr/local/bin/caddy
caddy version

Step 4 — Create a Fallback Web Page

The whole point of Trojan is that unauthenticated connections get a real website back. Create a minimal but convincing one:

sudo mkdir -p /var/www/fallback
sudo cat > /var/www/fallback/index.html << 'EOF'
<!DOCTYPE html>
<html>
<head><title>Welcome</title></head>
<body>
<h1>Welcome</h1>
<p>This site is under construction.</p>
</body>
</html>
EOF

You can make this more elaborate — a real static site is more convincing than a placeholder.

Step 5 — Write the Caddyfile

Create /etc/caddy/Caddyfile:

sudo mkdir -p /etc/caddy
sudo nano /etc/caddy/Caddyfile

Paste the following, replacing yourdomain.com and the password:

{
 email test@example.com

 # Make the trojan HTTP handler placeable in the chain
 order trojan before file_server

 # Enable the trojan listener wrapper on 443
 servers :443 {
  listener_wrappers {
   trojan
  }
 }

 # Global trojan app config (auth + fallback behavior)
 trojan {
  caddy
  no_proxy

  # passwords (space-separated)
  users pass1 pass2
 }
}

www.yourdomain.com {
 # Your TLS cert for this host
 tls test@example.com

 # Trojan handler (allowed usage)
 trojan {
  connect_method
  # add websocket if your client uses WS:
  # websocket
 }

 # What "normal HTTPS" users/probes see
 reverse_proxy 127.0.0.1:8080
}

:8080 {
 root * /var/www/fallback
 file_server
}

Generate a strong password if you need one:

openssl rand -base64 32

Step 6 — Set Up Caddy as a Systemd Service

Create the service file at /etc/systemd/system/caddy.service:

[Unit]
Description=Caddy with Trojan
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target

[Service]
Type=notify
User=root
ExecStart=/usr/local/bin/caddy run --config /etc/caddy/Caddyfile
ExecReload=/usr/local/bin/caddy reload --config /etc/caddy/Caddyfile
TimeoutStopSec=5s
LimitNOFILE=1048576
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_BIND_SERVICE
Restart=on-failure
[Install]
WantedBy=multi-user.target

Enable and start it:

systemctl daemon-reload
systemctl enable caddy
systemctl start caddy
systemctl status caddy

Watch the logs to confirm Caddy has obtained your TLS certificate:

journalctl -u caddy -f

You should see a line confirming the certificate was issued. If it fails, make sure port 80 is open on your VPS firewall — Let's Encrypt needs it for the HTTP-01 challenge.

Step 7 — Verify the Fallback

Open a browser and navigate to https://yourdomain.com. You should see your fallback page served over HTTPS with a valid certificate. This confirms Caddy is running, TLS is working, and unauthenticated requests hit the fallback correctly.

Your server is ready. Now for the clients.

Part 2 — Connecting From Windows

The best Trojan client for Windows is v2rayN — it's actively maintained, has a clean system tray interface, and handles routing at the OS level.

Step 1 — Download v2rayN

Go to the v2rayN GitHub releases page and download v2rayN-With-Core.zip. This bundle includes the Xray core engine that handles Trojan, so no separate installs are needed.

Extract the ZIP to a permanent location — C:\v2rayN\ works well. Run v2rayN.exe. It will appear in your system tray (bottom right, near the clock) as a small V icon.

Step 2 — Add Your Server

Right-click the tray icon → ServersAdd [Trojan] server

Fill in the fields:

Field Value Address yourdomain.com Port 443 Password your password from the Caddyfile SNI yourdomain.com Allow Insecure OFF Alias Anything — e.g. "Home VPS"

Click OK.

Step 3 — Enable TUN Mode

System Proxy mode routes browser traffic. TUN mode routes everything at the OS level — all apps, all protocols. For most use cases TUN mode is what you want.

Right-click tray → TUN Mode → toggle on. When prompted to install a virtual network adapter, allow it.

If TUN mode fails to enable, right-click v2rayN.exe and choose Run as Administrator.

Step 4 — Test

Right-click tray → Server: Test Latency (all). A ping value should appear next to your server. Then open a browser and visit https://whatismyip.com — it should show your VPS IP, not your home IP.

Step 5 — Auto-Start on Boot (Optional)

Right-click tray → Settings → check Auto-run at startup.

Part 3 — Connecting From Linux

On Linux you can run the Xray core directly — it's a single binary with no GUI needed.

Step 1 — Install Xray

bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install

This installs Xray to /usr/local/bin/xray and sets it up as a systemd service.

Step 2 — Configure Xray as a Trojan Client

Edit /usr/local/etc/xray/config.json:

{
  "log": {
    "loglevel": "warning"
  },
  "inbounds": [
    {
      "port": 1080,
      "protocol": "socks",
      "listen": "127.0.0.1",
      "settings": {
        "udp": true
      }
    },
    {
      "port": 1081,
      "protocol": "http",
      "listen": "127.0.0.1"
    }
  ],
  "outbounds": [
    {
      "protocol": "trojan",
      "settings": {
        "servers": [
          {
            "address": "yourdomain.com",
            "port": 443,
            "password": "your_very_strong_password_here"
          }
        ]
      },
      "streamSettings": {
        "network": "tcp",
        "security": "tls",
        "tlsSettings": {
          "serverName": "yourdomain.com",
          "allowInsecure": false
        }
      }
    }
  ]
}

Step 3 — Start Xray

systemctl enable xray
systemctl start xray
systemctl status xray

Step 4 — Route Traffic Through the Proxy

Xray creates a SOCKS5 proxy at 127.0.0.1:1080 and an HTTP proxy at 127.0.0.1:1081.

For system-wide routing, set your proxy in GNOME:

Settings → Network → Network Proxy → Manual

Set SOCKS Host to 127.0.0.1, port 1080.

For terminal-only routing, set the environment variables:

export https_proxy=http://127.0.0.1:1081
export http_proxy=http://127.0.0.1:1081

For full OS-level routing (all traffic, including non-proxy-aware apps), use tun2socks:

apt install tun2socks -y
ip tuntap add mode tun dev tun0
ip addr add 198.18.0.1/15 dev tun0
ip link set dev tun0 up
tun2socks -device tun0 -proxy socks5://127.0.0.1:1080

Then adjust your routing table to send traffic through tun0. This is more involved but routes literally everything.

Part 4 — Connecting From macOS

The cleanest macOS client is V2RayXS — a native macOS app that wraps Xray with a menu bar interface.

Step 1 — Download V2RayXS

Download the latest release from the V2RayXS GitHub releases page. Open the DMG and drag V2RayXS to your Applications folder.

On first launch, macOS may block it because it's not from the App Store. Go to System Settings → Privacy & Security and click Open Anyway.

Step 2 — Add Your Server

Click the V2RayXS icon in the menu bar → ServersAdd Server

Choose Trojan as the protocol and fill in:

Field Value Server Address yourdomain.com Port 443 Password your password SNI yourdomain.com Allow Insecure off

Save and select the server.

Step 3 — Set Proxy Mode

Menu bar icon → choose one of:

  • PAC Mode — routes only traffic to sites on a list (good for selective use)
  • Global Mode — routes all traffic through the tunnel
  • TPROXY/TUN Mode — full OS-level routing, requires admin password

Global Mode is the most straightforward for full traffic routing.

Step 4 — Test

Visit https://whatismyip.com in your browser — you should see your VPS IP.

Part 5 — Connecting From Android

Android has the best Trojan client ecosystem of any mobile platform. v2rayNG is the recommended choice — lightweight, actively maintained, and available on GitHub.

Step 1 — Install v2rayNG

v2rayNG is on the Google Play Store, but the GitHub releases are often newer. Search for v2rayNG on GitHub and download the APK directly if you prefer.

Install it and grant any permissions it requests (VPN permission is required — Android will show a standard VPN consent dialog).

Step 2 — Add Your Server

Tap the + button (top right) → Add Trojan server

Fill in:

Field Value Address yourdomain.com Port 443 Password your password SNI yourdomain.com Allow Insecure off Remarks anything — e.g. "Home VPS"

Tap the checkmark to save.

Step 3 — Connect

Tap the paper plane icon at the bottom right to connect. Android will show the standard VPN consent dialog on first connection — tap OK.

The icon turns green when connected. Your traffic is now routed through the Trojan tunnel.

Step 4 — Test

Open a browser and visit https://whatismyip.com — you should see your VPS IP.

Step 5 — Per-App Routing (Optional)

v2rayNG supports split tunneling — you can choose which apps use the tunnel and which connect directly.

Go to Settings → VPN Configuration and toggle on Per-app proxy. A list of installed apps appears. Select only the ones you want routed through the tunnel, and everything else connects normally. Useful for keeping local streaming services working while routing everything else through the VPN.

Verifying Your Setup Looks Like Normal HTTPS

To confirm that your traffic genuinely looks like HTTPS and not a VPN, you can check it from the outside.

On any machine not connected to your tunnel, run:

curl -v https://yourdomain.com

You should get your fallback webpage back — the same response anyone would get from a normal HTTPS site. Nothing in the response reveals the existence of the Trojan service.

For deeper verification, use a packet capture on your client machine while connected:

tcpdump -i eth0 port 443 -w capture.pcap

Open the capture in Wireshark. All you'll see is TLS 1.3 traffic to port 443 on your domain. No WireGuard handshake, no WebSocket upgrade headers, no unusual protocol identifiers — just standard HTTPS.

Troubleshooting

Caddy fails to start — certificate error

Make sure port 80 is open. Let's Encrypt's HTTP-01 challenge requires it even though your service runs on 443. Check: ufw allow 80 and try systemctl restart caddy.

Client connects but traffic doesn't route

On Windows, try switching from System Proxy to TUN Mode. On Linux, make sure your proxy environment variables or system proxy settings are pointing at the right port.

Fallback page not appearing

Confirm the :8080 block in your Caddyfile is correct and that /var/www/fallback/index.html exists. Test it locally on the VPS: curl http://localhost:8080.

Android won't connect

Double-check that Allow Insecure is off and your domain exactly matches the SNI field. A mismatch in the SNI causes a TLS handshake failure.

v2rayN TUN mode fails on Windows

Run v2rayN as Administrator. TUN mode requires elevated privileges to install the virtual network adapter.

Summary

The full stack in one picture:

Device
└── Trojan client (v2rayN / v2rayNG / Xray / V2RayXS)
    └── TLS 1.3 on port 443
        └── VPS running Caddy + caddy-trojan
            ├── Valid Let's Encrypt certificate
            ├── Correct password → traffic forwarded
            └── Wrong password / probe → real website served

To any observer on the network between your device and the VPS, this is an HTTPS connection to a website. The certificate is real, the TLS handshake is standard, and the server genuinely responds to unauthenticated requests with a webpage. There is nothing to identify as a VPN unless they can observe both ends of the connection simultaneously — which is not a capability available to most firewalls.