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 random 256-bit session keys
- Reverse tunnel architecture - Client connects TO server (bypasses outbound blocking)
- Mode selection -
reverse(default) anddirect - 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 and arm64 (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://raw.githubusercontent.com/frenchtoblerone54/ghostwire/main/scripts/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://raw.githubusercontent.com/frenchtoblerone54/ghostwire/main/scripts/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.
- Setup Guide - Detailed setup instructions for server and client
- Port Mapping - Port mapping syntax and examples
- Troubleshooting - Common issues and solutions
- CloudFlare Setup - CloudFlare configuration guide
- Security - Encryption, authentication, and MITM protection details
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)
GhostWire now supports:
mode="reverse"(default)mode="direct"
reverse: server listens on[tunnels], client connects out and dials remote targetsdirect: client listens on[tunnels], server dials remote targets
- Host website on your own computer (client side) and expose it from server public IP/domain: use
mode="reverse"(server-side[tunnels]). - Connect to a VPN that is running on the server side through GhostWire encryption: use
mode="direct"(client-side[tunnels]).
- WebSocket pool (
ws_pool_enabled,ws_pool_children,ws_pool_min) works in direct mode too. - In
direct, pool channels are used by client-side direct listeners. - Scope: pool applies to WebSocket transports (
protocol="websocket"andprotocol="aiohttp-ws"), not HTTP/2 or gRPC.
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), "http-request", "http-request-sse", "http2", or "grpc"
listen_host="0.0.0.0"
listen_port=8443
mode="reverse" # "reverse" (default) or "direct"
listen_backlog=4096 # TCP listen queue depth
websocket_path="/ws" # Used by websocket and HTTP request protocols
ping_interval=30 # Application-level ping interval (seconds)
ping_timeout=60 # Connection timeout (seconds)
http_proxy="" # optional: proxy for outbound CONNECT tunnel traffic
https_proxy="" # optional: proxy for outbound CONNECT tunnel traffic
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)
ws_send_batch_bytes=65536 # Max bytes per WebSocket frame (default: 65536)
http_request_min_upload_ms=50 # Minimum delay between upload requests
http_request_min_download_ms=100 # Minimum delay between poll requests
http_request_max_upload_bytes=262144 # Max bytes sent in a single upload request
http_request_max_download_bytes=262144 # Max bytes returned in a single poll/upload response
http_request_body_param="data" # Query parameter carrying http-request-body payloads
http_request_body_method="GET" # GET query mode or POST body mode for http-request-body
auto_update=true
update_check_interval=300
update_check_on_startup=true
service_name="ghostwire-server" # systemd service name for auto-restart after update
pinned_private_key="" # optional: path to PEM private key file; if set, server uses this key instead of generating an ephemeral one
[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
-
http_request_min_upload_msandhttp_request_min_download_ms(both, defaults:50and100): Minimum spacing between upload requests and download polls/SSE sends forprotocol="http-request"andprotocol="http-request-sse"- Increase them to reduce request count and blend in with non-streaming HTTP transports
- Decrease them to improve latency at the cost of more HTTP requests
-
http_request_max_upload_bytesandhttp_request_max_download_bytes(both, default:262144): Per-request caps for upload payloads and poll/upload/SSE response payloads in HTTP request transports262144bytes is256KB(0.25 MB)524288bytes is512KB(0.5 MB)- Larger values improve throughput, smaller values reduce per-request burst size
-
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), "http-request", "http-request-sse", "http2", or "grpc"
url="wss://tunnel.example.com/ws" # Use ws(s):// for websocket, http(s):// for HTTP request/http2/grpc
token="V1StGXR8_Z5jdHi6B-my"
mode="reverse" # must match server mode
ping_interval=30 # Application-level ping interval (seconds)
ping_timeout=60 # Connection timeout (seconds)
http_proxy="" # optional: proxy for WebSocket tunnel connection
https_proxy="" # optional: proxy for WebSocket tunnel connection (preferred for wss://)
allow_insecure=false # allow expired/self-signed certificates (less secure)
resolve_ip="" # pre-resolve domain to IP; accepts single IP, comma-separated list, or TOML array
resolve_ip_mode="least_ping" # IP selection mode when multiple IPs given: "least_ping" (default) or "round_robin"
resolve_ip_latency_interval=60 # seconds between latency re-checks in least_ping mode
sni="" # override TLS SNI hostname (default: original domain when resolve_ip is set)
host_header="" # override Host header (default: original domain when resolve_ip is set)
domain_fronting_host="" # hostname to rewrite after redirects, e.g. script.googleusercontent.com
domain_fronting_target="" # connect hostname/IP, e.g. www.google.com or a configured IP
domain_fronting_sni="" # TLS SNI used for domain fronting target
gas_script_id="" # Apps Script deployment ID; builds https://script.google.com/macros/s/{id}/exec
service_name="ghostwire-client" # systemd service name for auto-restart after update
pinned_server_public_key="" # optional: path to PEM public key file; if set, client verifies server identity (MITM protection)
ws_send_batch_bytes=65536 # Max bytes per WebSocket frame (default: 65536)
http_request_min_upload_ms=50 # Minimum delay between upload requests
http_request_min_download_ms=100 # Minimum delay between poll requests
http_request_max_upload_bytes=262144 # Max bytes sent in a single upload request
http_request_max_download_bytes=262144 # Max bytes returned in a single poll/upload response
http_request_body_param="data" # Query parameter carrying http-request-body payloads
http_request_body_method="GET" # GET query mode or POST body mode for http-request-body
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
# [tunnels] section is only needed in direct mode (mode="direct")
# ports=[]
[logging]
level="info"
file="/var/log/ghostwire-client.log"Google Apps Script domain-fronting example for http-request-body:
[server]
protocol="http-request-body"
url="https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec"
gas_script_id="YOUR_DEPLOYMENT_ID"
allow_redirects=true
http_request_body_param="data"
http_request_body_method="POST"
domain_fronting_host="script.google.com"
domain_fronting_target="www.google.com"
domain_fronting_sni="www.google.com"gas_script_id makes GhostWire start at script.google.com/macros/s/{id}/exec and front both script.google.com and redirected script.googleusercontent.com through domain_fronting_target with domain_fronting_sni, while preserving the current Google hostname as the HTTP Host. http-request-body uses http_request_body_method; GET carries the GhostWire request envelope in the query parameter named by http_request_body_param, while POST sends it in the request body. Configure the same method and parameter value on both client and server.
With a configured IP instead of www.google.com:
domain_fronting_host="script.googleusercontent.com"
domain_fronting_target="142.250.190.36"
domain_fronting_sni="www.google.com"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.
For tunnel outbound target connections (reverse and direct), GhostWire can route via an HTTP proxy using CONNECT:
http_proxy="http://127.0.0.1:8080"
https_proxy="http://127.0.0.1:8080"https_proxyis preferred for destination port443http_proxyis used for other ports (or as fallback)- Supports proxy auth in URL form:
http://user:pass@proxy:8080
GhostWire supports four 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: HTTP-only environments where streaming is unreliable or blocked, while still using GhostWire's encrypted/authenticated tunnel messages
- ✅ Uses regular HTTP requests instead of a long-lived stream
- ✅ Uploads data with per-request HTTP calls and downloads with polling
- ✅ Upload responses can also carry download data to reduce extra requests
- ✅ Works with simple HTTP reverse proxies that do not support WebSocket/gRPC streaming well
- ❌ Higher request overhead than WebSocket/gRPC/HTTP2 streaming
- ❌ Throughput and latency depend heavily on the min interval and max byte settings
- ❌ WebSocket pool/child-channel scaling does not apply
Configuration:
[server]
protocol="http-request"
url="https://tunnel.example.com/ws"
http_request_min_upload_ms=10
http_request_min_download_ms=10
http_request_max_upload_bytes=524288
http_request_max_download_bytes=524288How it works:
- Client uploads encrypted GhostWire packets with HTTP requests
http-request-bodyuses GET-only requests with the body envelope in query parameters for Google Apps Script compatibility- Client downloads queued packets with HTTP polling
- The server may return queued download data directly in an upload response
- The min upload/download settings limit request frequency
- The max upload/download settings cap per-request payload size
nginx config:
location /ws {
proxy_pass http://127.0.0.1:8443;
proxy_http_version 1.1;
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;
}CloudFlare compatibility: HTTP per-request uses standard HTTP POST/GET requests, so CloudFlare proxies it by default with no special dashboard toggles required. Set SSL/TLS to Full (Strict). Increase http_request_min_upload_ms / http_request_min_download_ms to 200–500ms if you want requests to blend in with normal HTTP traffic through CloudFlare.
Best for: Unstable CDNs where repeated download polling performs poorly but a one-way HTTP stream is reliable
- ✅ Downloads over Server-Sent Events (SSE)
- ✅ Uploads use small, short-lived POST requests
- ✅ Uses the same encrypted GhostWire packet format as
http-request - ✅
http_request_max_upload_bytesandhttp_request_min_upload_mscontrol upload burst size and request rate - ❌ Requires proxies/CDNs to allow long-lived
text/event-streamresponses - ❌ WebSocket pool/child-channel scaling does not apply
Configuration:
[server]
protocol="http-request-sse"
url="https://tunnel.example.com/ws"
http_request_min_upload_ms=50
http_request_min_download_ms=50
http_request_max_upload_bytes=65536
http_request_max_download_bytes=131072How it works:
- Client uploads encrypted packets using short POST requests
- Server streams download packets over one SSE response
- Client acknowledges streamed batches with small POST acknowledgements
- Smaller upload limits and higher upload intervals can reduce CDN buffering and request bursts
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 HTTP per-request if: You need non-streaming HTTP transport but still want GhostWire security and tunneling
- Use HTTP request SSE if: An unstable CDN handles one-way SSE better than download polling or WebSocket
- 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:
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 /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:
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 {
proxy_pass http://127.0.0.1:8443;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 86400s;
}
}For HTTP per-request and HTTP request SSE protocols:
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 /ws {
proxy_pass http://127.0.0.1:8443;
proxy_http_version 1.1;
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;
}
}For gRPC protocol:
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 per-request | ✅ Yes (default) | Standard HTTP — no special CF settings needed |
| HTTP request SSE | ✅ Yes (default) | Uses SSE download plus short POST uploads |
| 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
For HTTP per-request protocol:
- SSL/TLS → Overview: Set to Full (Strict) (not "Flexible")
- No WebSockets or gRPC toggles needed — standard HTTP POST/GET passes through CloudFlare by default
- Consider setting
http_request_min_upload_msandhttp_request_min_download_msto 200–500ms to reduce request rate through CloudFlare's edge
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
Update (manual):
sudo ghostwire-server update
sudo ghostwire-client updateChecks GitHub for a newer release, downloads and verifies it, installs in place, then restarts the service automatically.
Panel setup:
sudo ghostwire-server panel configureInteractive wizard: enables the web panel in server.toml (if not already configured) and optionally sets up nginx with a TLS certificate.
Other:
ghostwire-server --version
ghostwire-server --generate-tokenServer:
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.
Join the Telegram channel for updates and announcements: @GhostSoftDev
For issues and questions, please open an issue on GitHub.