GhostWire is a WebSocket-based reverse tunnel system designed to help users in censored countries access the internet freely. It uses secure WebSocket connections over TLS, making it difficult to detect and block.
- Multiple protocol support - WebSocket, HTTP/2, and gRPC transports
- RSA-encrypted authentication - Token invisible to TLS-terminating proxies (CloudFlare-proof)
- End-to-end AES-256-GCM encryption - All tunnel data encrypted with nanoid-derived keys
- Reverse tunnel architecture - Client connects TO server (bypasses outbound blocking)
- Bidirectional streaming - Single persistent connection over TLS
- Flexible TCP port forwarding - Port ranges, IP binding, custom mappings
- Built-in heartbeat - Transport and application-layer keepalive
- CloudFlare compatible - Works behind TLS-terminating proxies (with WebSocket/HTTP/2)
- Web management panel - Real-time system monitoring, tunnel config, logs, service control
- nginx reverse proxy - Production-ready setup with Let's Encrypt
- Compiled binaries - Linux amd64 (Ubuntu 22.04+ compatible)
- systemd services - Automated start, restart, logging
- Auto-update - Configurable automatic binary updates via GitHub releases
- Easy installation - One-command setup scripts with interactive configuration
The server runs in the censored country with a public IP that can receive incoming connections.
wget https://github.com/frenchtoblerone54/ghostwire/releases/latest/download/install-server.sh -O install-server.sh
chmod +x install-server.sh
sudo ./install-server.shNote: Save the authentication token - you'll need it for the client!
The client runs on a VPS in an uncensored country with unrestricted internet access.
wget https://github.com/frenchtoblerone54/ghostwire/releases/latest/download/install-client.sh -O install-client.sh
chmod +x install-client.sh
sudo ./install-client.shEnter:
- Server URL pointing to your Iran server (e.g.,
wss://iran-server.com/ws) - Authentication token from server
- The client will connect TO the Iran server
Users in Iran connect to the server's local ports (e.g., localhost:8080) and traffic is tunneled through to the NL client which makes the actual internet requests.
- Installation Guide - Detailed setup instructions for server and client
- Configuration Reference - Complete configuration options
- Troubleshooting - Common issues and solutions
- Security - Encryption details and security considerations
Reverse Tunnel for Bypassing Outbound Blocking:
Designed for scenarios where censored countries block outbound connections to foreign servers (e.g., Iran blocks connections to international websites).
Setup:
- Server: Runs in censored country (Iran) with public IP
- Client: Runs in uncensored country (Netherlands) with unrestricted internet
Why This Works:
- Iran blocks outbound connections to foreign servers
- But Iran server has public IP and can receive inbound WebSocket connections
- NL client connects TO Iran server (inbound to Iran = allowed ✅)
- Once tunnel is established, traffic flows bidirectionally
Data Flow:
[User in Iran] → [Server localhost:8080] → [Server Iran]
↓ WebSocket Tunnel
[Client NL] → [Internet: Port 80/443]
Step-by-Step:
- Client (NL) initiates WebSocket connection TO server (Iran)
- Server (Iran) listens on local ports (e.g., 8080) for users
- User in Iran connects to
localhost:8080 - Traffic tunnels through WebSocket to NL client
- NL client makes actual connection to blocked websites
- Response travels back through tunnel to user in Iran
CloudFlare/DNS: Points to Iran server IP (where WebSocket server listens for client connections)
The server supports flexible port mapping configurations (server listens, client connects):
ports=[
"443-600", # Listen on all ports 443-600, forward to same port on remote
"443-600:5201", # Listen on all ports 443-600, forward all to remote port 5201
"443-600=1.1.1.1:5201", # Listen on all ports 443-600, forward all to 1.1.1.1:5201
"443", # Listen on local port 443, forward to remote port 443
"4000=5000", # Listen on local port 4000, forward to remote port 5000
"127.0.0.2:443=5201", # Bind to 127.0.0.2:443, forward to remote port 5201
"443=1.1.1.1:5201", # Listen on local port 443, forward to 1.1.1.1:5201
"127.0.0.2:443=1.1.1.1:5201", # Bind to 127.0.0.2:443, forward to 1.1.1.1:5201
]Location: Censored country (Iran) - has public IP, listens for client connections
[server]
protocol="websocket" # "websocket" (default), "http2", or "grpc"
listen_host="0.0.0.0"
listen_port=8443
listen_backlog=4096 # TCP listen queue depth
websocket_path="/ws" # Only used for websocket protocol
ping_interval=30 # Application-level ping interval (seconds)
ping_timeout=60 # Connection timeout (seconds)
ws_pool_enabled=true # Enable child channel pooling (default: true)
ws_pool_children=8 # Max child channels (default: 8)
ws_pool_min=2 # Min always-connected channels (default: 2)
ws_pool_stripe=false # Stripe packets across channels (unstable, default: false)
udp_enabled=true # Also listen for UDP on tunnel ports (default: true)
auto_update=true
update_check_interval=300
update_check_on_startup=true
[auth]
token="V1StGXR8_Z5jdHi6B-my"
[tunnels]
ports=["8080=80", "8443=443"]
[panel]
enabled=true
host="127.0.0.1"
port=9090
path="aBcDeFgHiJkLmNoPqRsT"
threads=4 # HTTP server worker threads
[logging]
level="info"
file="/var/log/ghostwire-server.log"Web Management Panel: The server includes an optional web-based management panel for:
- Real-time system monitoring (CPU, RAM, disk, network usage)
- Tunnel configuration and management
- Log viewing
- Service control (restart/stop)
- Configuration editor
The panel is accessible at http://127.0.0.1:9090/{path}/ where path is a randomly generated nanoid. Access is restricted to localhost by default for security. The threads parameter (default: 4) controls the number of worker threads for the panel's HTTP server - increase for high traffic.
Performance Tuning for High Concurrency:
For web browsing with hundreds of concurrent connections (typical modern websites load 50-200+ resources):
-
ws_pool_enabled(server only, default: true): Enable dynamic multi-connection pool to mitigate TCP-over-TCP meltdown under heavy load -
ws_pool_children(server only, default: 8): Max parallel WebSocket connections- 2-4: Light usage (< 50 concurrent connections)
- 8: Default, good for most deployments
- 16-32: Heavy usage (multiple simultaneous users)
-
ws_pool_min(server only, default: 2): Minimum always-connected channels; pool scales between min and max based on load -
ws_pool_stripe(server only, default: false): Stripe individual packets across channels for higher throughput — disabled by default as it requires sequence reordering and is unstable under packet loss -
udp_enabled(server only, default: true): Also listen on the configured tunnel ports via UDP; set tofalseto disable UDP tunneling -
ws_send_batch_bytes(both, default: 65536): Max bytes batched into a single WebSocket frame- Lower values reduce latency under high load (speedtest, video) by preventing large frames from blocking smaller packets
- 65536 (64KB): Default, best balance for most use cases
- 262144 (256KB): Higher throughput, some latency increase under load
- 16384 (16KB): Lowest latency, slightly lower throughput
-
ping_intervalandping_timeout: Critical for CloudFlare stability (configure on both server and client)- For low latency (< 50ms):
ping_interval=10,ping_timeout=10 - For high latency (> 200ms, CloudFlare):
ping_interval=30,ping_timeout=60 - Aggressive timeouts (< 15s) cause constant reconnections on high-latency WAN links
- CloudFlare adds 5-500ms latency and has 100s idle timeout, so 30s ping interval is recommended
- For low latency (< 50ms):
Location: Uncensored country (Netherlands) - connects TO server, makes internet requests
[server]
protocol="websocket" # "websocket" (default), "http2", or "grpc"
url="wss://tunnel.example.com/ws" # Use wss:// for websocket, https:// for http2/grpc
token="V1StGXR8_Z5jdHi6B-my"
ping_interval=30 # Application-level ping interval (seconds)
ping_timeout=60 # Connection timeout (seconds)
auto_update=true
update_check_interval=300
update_check_on_startup=true
[reconnect]
initial_delay=1
max_delay=60
multiplier=2
[cloudflare]
enabled=false
ips=[]
host=""
check_interval=300
max_connection_time=1740
[logging]
level="info"
file="/var/log/ghostwire-client.log"Both server and client support automatic updates from GitHub releases:
auto_update(default:true): Enable/disable automatic updatesupdate_check_interval(default:300): Seconds between update checksupdate_check_on_startup(default:true): Check for updates immediately on startup
When an update is found, the binary is downloaded, verified with SHA-256 checksum, and the service restarts automatically via systemd.
HTTP/HTTPS Proxy for Updates: If your server or client needs to use a proxy to reach GitHub for auto-updates, add these options to the [server] section:
update_http_proxy="http://127.0.0.1:8080"
update_https_proxy="http://127.0.0.1:8080"These proxy settings only affect auto-update downloads from GitHub. They do not affect tunnel traffic. Leave empty (or omit) if no proxy is needed.
GhostWire supports three transport protocols, each with different trade-offs:
Best for: CloudFlare, general-purpose use, maximum compatibility
- ✅ Works with CloudFlare (requires WebSockets enabled)
- ✅ Simple browser-based debugging tools available
- ✅ Widely supported by proxies and load balancers
- ❌ HTTP/2-only proxies may block WebSocket upgrade (causes HTTP 426)
- ❌ Requires special
Upgradeheader handling in nginx
Configuration:
[server]
protocol="websocket"
url="wss://tunnel.example.com/ws"Best for: Direct connections without CloudFlare, custom proxy setups
- ✅ Native HTTP/2 streams (no WebSocket upgrade handshake)
- ✅ Simple protocol debugging tools available
- ✅ No protobuf overhead
- ❌ NOT compatible with CloudFlare (raw HTTP/2 streams not supported)
- ❌ Requires HTTP/2-capable proxy or direct connection
Configuration:
[server]
protocol="http2"
url="https://tunnel.example.com/tunnel" # Use /tunnel pathNote: Can also use /ws path for HTTP/2 (kept for consistency with WebSocket mode)
nginx config:
location /tunnel {
proxy_pass http://127.0.0.1:8443;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 86400s;
}Best for: CloudFlare with gRPC enabled, high-performance scenarios
- ✅ Compatible with CloudFlare (requires Network → gRPC enabled)
- ✅ Highest throughput efficiency (protobuf serialization)
- ✅ Built-in streaming multiplexing
- ✅ Lowest protocol overhead
- ❌ Requires CloudFlare gRPC toggle or gRPC-aware proxy
- ❌ More complex debugging
Configuration:
[server]
protocol="grpc"
url="https://tunnel.example.com/tunnel" # Use /tunnel path, not /wsnginx config for CloudFlare:
location /tunnel {
grpc_pass grpc://127.0.0.1:8443;
grpc_set_header Host $host;
grpc_read_timeout 86400s;
grpc_send_timeout 86400s;
}Protocol Selection Guide:
- Use WebSocket if: Running through CloudFlare (most common), need maximum compatibility
- Use gRPC if: Running through CloudFlare with gRPC enabled, want best performance
- Use HTTP/2 if: Direct connection without CloudFlare, custom proxy setup
For WebSocket protocol:
location /ws {
proxy_pass http://127.0.0.1:8443;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400;
proxy_send_timeout 86400;
proxy_buffering off;
proxy_request_buffering off;
tcp_nodelay on;
}For HTTP/2 protocol:
location /tunnel {
proxy_pass http://127.0.0.1:8443;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 86400s;
}For gRPC protocol:
location /tunnel {
grpc_pass grpc://127.0.0.1:8443;
grpc_set_header Host $host;
grpc_set_header X-Real-IP $remote_addr;
grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
grpc_read_timeout 86400s;
grpc_send_timeout 86400s;
}For gRPC with CloudFlare:
server {
listen 443 ssl http2;
server_name tunnel.example.com;
ssl_certificate /etc/letsencrypt/live/tunnel.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/tunnel.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location /tunnel {
grpc_pass grpc://127.0.0.1:8443;
grpc_set_header Host $host;
grpc_set_header X-Real-IP $remote_addr;
grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
grpc_read_timeout 86400s;
grpc_send_timeout 86400s;
}
}Important notes for gRPC with nginx:
- nginx 1.13.10+ required for gRPC support
- Use
grpc_passinstead ofproxy_pass - Use
grpc_*timeout directives instead ofproxy_* - CloudFlare requires Network → gRPC toggle enabled
- URL path is
/tunnelfor gRPC (not/ws)
Note: proxy_buffering off and proxy_request_buffering off are critical for WebSocket — without these, nginx buffers frames causing significant throughput degradation.
For WebSocket protocol:
- Create a new Proxy Host pointing to
127.0.0.1:8443 - Enable "Websockets Support" toggle on the Details tab
- Under the Advanced tab, add these custom directives:
proxy_read_timeout 86400;
proxy_send_timeout 86400;
proxy_buffering off;
proxy_request_buffering off;
tcp_nodelay on;For HTTP/2 or gRPC protocol:
- Use the same timeout directives
- Do NOT enable "Websockets Support" toggle
- For gRPC, NPM must support gRPC proxying (nginx 1.13.10+)
Without these timeouts, NPM will drop the persistent connection after ~60 seconds.
Protocol Compatibility with CloudFlare:
| Protocol | CloudFlare Support | Notes |
|---|---|---|
| WebSocket | ✅ Yes (with config) | Requires Network → WebSockets ON |
| gRPC | ✅ Yes (with config) | Requires Network → gRPC ON |
| HTTP/2 | ❌ No | Not compatible - use direct connection |
CRITICAL: Required CloudFlare Dashboard Settings
For WebSocket protocol:
- Network → WebSockets: MUST be enabled (OFF by default - will cause disconnections!)
- SSL/TLS → Overview: Set to Full (Strict) (not "Flexible")
- Speed → Rocket Loader: Turn OFF (breaks WebSocket connections)
- Speed → Auto Minify: Disable all (HTML, CSS, JS)
- Speed → Early Hints: Turn OFF
For gRPC protocol:
- Network → gRPC: MUST be enabled (OFF by default)
- SSL/TLS → Overview: Set to Full (Strict) (not "Flexible")
- Speed → Rocket Loader: Turn OFF
- Speed → Auto Minify: Disable all (HTML, CSS, JS)
- Speed → Early Hints: Turn OFF
Client Configuration for CloudFlare:
CloudFlare's free tier has a 100-second idle timeout and a 30-minute hard connection limit. Enable proactive reconnect:
[cloudflare]
enabled=true
max_connection_time=1740 # 29 minutes - reconnect before 30min limitWith enabled=true and empty ips/host, the IP selection is skipped but the proactive reconnect still applies.
Performance Notes:
- GhostWire v0.9.3+ is optimized for CloudFlare with 64KB buffers (reduced from 16MB)
- Application-level ping (30s) replaces WebSocket ping for CloudFlare reliability
- CloudFlare adds 5-500ms latency - this is normal and handled by the implementation
Server:
sudo systemctl start ghostwire-server
sudo systemctl stop ghostwire-server
sudo systemctl restart ghostwire-server
sudo systemctl status ghostwire-server
sudo journalctl -u ghostwire-server -fClient:
sudo systemctl start ghostwire-client
sudo systemctl stop ghostwire-client
sudo systemctl restart ghostwire-client
sudo systemctl status ghostwire-client
sudo journalctl -u ghostwire-client -fpip install -r requirements.txt
cd build
chmod +x build.sh
./build.shBinaries will be created in the dist/ directory.
GhostWire implements multiple layers of security:
-
RSA-2048 Token Exchange: Authentication tokens are encrypted with server's public key before transmission
- Protects tokens from TLS-terminating proxies (CloudFlare, nginx)
- Only server can decrypt token with its private key
-
TLS Layer: WebSocket over HTTPS (WSS) protects transport
- Prevents network eavesdropping
- Standard HTTPS encryption
-
Application Layer: AES-256-GCM end-to-end encryption
- Server generates 256-bit random session key
- Session key sent to client via RSA-2048 encrypted exchange
- All tunnel data encrypted with this session key
- Protects against intermediate inspection
- Even CloudFlare cannot read tunnel contents
-
Built-in Heartbeat: WebSocket ping/pong every 20 seconds
- Detects dead connections quickly
- Prevents timeout issues
MIT License - See LICENSE file for details
Contributions are welcome! Please open an issue or submit a pull request.
For issues and questions, please open an issue on GitHub.