Deploy Chisel on a RamNode VPS
A fast TCP/UDP tunnel transported over HTTP and secured with SSH — SOCKS5, reverse forwards, multiplexed tunnels, and a self-hosted ngrok replacement with no rate limits.
At a Glance
| Project | Chisel v1.11.5 |
| License | MIT |
| Recommended Plan | RamNode KVM 1–2 GB (over-provisioned) |
| OS | Ubuntu 24.04 / Debian 12 / AlmaLinux 9 |
| Listen Port | 443 (HTTPS, WebSocket upgrade) |
| Estimated Setup Time | 20–30 minutes |
Common Use Cases
- Exposing services behind CGNAT, residential ISPs, or restrictive corporate firewalls
- Personal SOCKS5 proxy for browsing on untrusted networks
- Tunneling SSH, databases, or admin panels to a developer's workstation
- Bridging two private networks through a single public relay
- Replacing ngrok for development tunneling — no rate limits, no session timeouts
Initial Server Preparation
apt update && apt upgrade -y
apt install -y curl wget ufw fail2ban
hostnamectl set-hostname chisel.example.com
timedatectl set-timezone America/Chicagouseradd --system --no-create-home --shell /usr/sbin/nologin chisel
mkdir -p /etc/chisel /var/lib/chisel
chown chisel:chisel /etc/chisel /var/lib/chisel
chmod 750 /etc/chisel /var/lib/chiselThe daemon never needs root — a systemd capability will grant it 443 binding without it.
Install Chisel
cd /tmp
ARCH=$(dpkg --print-architecture)
VERSION=1.11.5
wget "https://github.com/jpillora/chisel/releases/download/v${VERSION}/chisel_${VERSION}_linux_${ARCH}.deb"
apt install -y "./chisel_${VERSION}_linux_${ARCH}.deb"
chisel --versioncd /tmp
VERSION=1.11.5
wget "https://github.com/jpillora/chisel/releases/download/v${VERSION}/chisel_${VERSION}_linux_amd64.rpm"
dnf install -y "./chisel_${VERSION}_linux_amd64.rpm"Avoid the convenience installer (curl https://i.jpillora.com/chisel! | bash) on production servers — pin to a specific release tag and verify checksums.
Generate a Persistent Server Key
By default Chisel generates a fresh ECDSA key on each restart, breaking pinned-fingerprint clients. Generate once:
sudo -u chisel chisel server --keygen /etc/chisel/server.key
chmod 600 /etc/chisel/server.key
chown chisel:chisel /etc/chisel/server.keyRecord the printed fingerprint — clients will pin it with --fingerprint to detect MITM.
Create the Auth File With Per-User ACLs
{
"alice:6Pd9mK3vN8qR2xL5wTyZ": [
"^0\\.0\\.0\\.0:1080quot;,
"^R:0\\.0\\.0\\.0:[0-9]+quot;
],
"bob:Hf4Xs7BcVj9QnE2RtMpY": [
"^192\\.168\\.10\\.[0-9]{1,3}:(22|3306|5432|6379)quot;
],
"ops:Lq8Wn3KvD5tBhJ7MzCxF": [
""
]
}chown chisel:chisel /etc/chisel/users.json
chmod 600 /etc/chisel/users.jsonPatterns are full Go regexes. Forward addresses come through as host:port; reverse as R:iface:port. Empty string matches everything. Generate strong passwords with openssl rand -base64 18. The file is reloaded automatically when changed.
TLS — Built-in Let's Encrypt or nginx
Option A — built-in: Chisel binds 443 directly and uses TLS-ALPN-01 (no port 80 required). Cleanest if Chisel is the only thing on the box.
dig +short chisel.example.com
mkdir -p /var/lib/chisel/.cache
chown -R chisel:chisel /var/lib/chiselOption B — nginx: required if you also host a website or need WAF/rate-limiting. nginx terminates TLS and proxies the WebSocket upgrade.
server {
listen 443 ssl http2;
server_name chisel.example.com;
ssl_certificate /etc/letsencrypt/live/chisel.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/chisel.example.com/privkey.pem;
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;
# Critical for Chisel
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_buffering off;
}
}
server { listen 80; server_name chisel.example.com; return 301 https://$host$request_uri; }systemd Service
[Unit]
Description=Chisel TCP/UDP tunnel server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=chisel
Group=chisel
Environment=CHISEL_LE_CACHE=/var/lib/chisel/.cache
Environment=CHISEL_LE_EMAIL=you@example.com
ExecStart=/usr/local/bin/chisel server \
--port 443 \
--keyfile /etc/chisel/server.key \
--authfile /etc/chisel/users.json \
--tls-domain chisel.example.com \
--reverse \
--socks5 \
--keepalive 25s \
--backend https://www.google.com
Restart=on-failure
RestartSec=5s
# Hardening
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictNamespaces=true
RestrictRealtime=true
LockPersonality=true
ReadWritePaths=/var/lib/chisel
SystemCallArchitectures=native
SystemCallFilter=@system-service
[Install]
WantedBy=multi-user.target--reverserequired for any client usingR:tunnels--socks5enables the server-side SOCKS5 proxy--keepalive 25skeeps connections alive through aggressive intermediate proxies--backend https://www.google.comproxies non-Chisel HTTP requests to Google so casual scanners see nothing interesting
systemctl daemon-reload
systemctl enable --now chisel
journalctl -u chisel -fFor Option B, drop --tls-domain and the CHISEL_LE_* env vars and bind to --host 127.0.0.1 --port 8080.
Firewall
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 443/tcp
ufw enablefirewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-service=https
firewall-cmd --reloadWith built-in Let's Encrypt, port 80 is not required — TLS-ALPN-01 happens on 443.
Connect a Client
The same binary runs as both server and client. Replace Vxk9...trailing= with the real fingerprint from Step 3.
chisel client \
--fingerprint 'Vxk9...trailing=' \
--auth 'alice:6Pd9mK3vN8qR2xL5wTyZ' \
https://chisel.example.com \
1080:sockschisel client \
--fingerprint 'Vxk9...trailing=' \
--auth 'alice:6Pd9mK3vN8qR2xL5wTyZ' \
https://chisel.example.com \
R:8096:localhost:8096chisel client \
--fingerprint 'Vxk9...trailing=' \
--auth 'bob:Hf4Xs7BcVj9QnE2RtMpY' \
https://chisel.example.com \
5432:10.0.0.5:5432[Unit]
Description=Chisel client tunnel
After=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/chisel client \
--fingerprint 'Vxk9...trailing=' \
--auth 'alice:6Pd9mK3vN8qR2xL5wTyZ' \
--keepalive 25s \
--max-retry-interval 30s \
https://chisel.example.com \
R:8096:localhost:8096
Restart=always
RestartSec=10s
[Install]
WantedBy=multi-user.targetHardening & Observability
Fail2ban for failed auths — add -v to the server ExecStart, then:
[Definition]
failregex = Failed authentication.*from <HOST>
ignoreregex =[chisel]
enabled = true
port = 443
filter = chisel
backend = systemd
journalmatch = _SYSTEMD_UNIT=chisel.service
maxretry = 5
findtime = 600
bantime = 3600Monitoring: GET /health returns 200, GET /version returns the version string — both pre-auth, ideal for Uptime Kuma. systemctl kill -s USR2 chisel dumps tunnel stats to the journal.
Troubleshooting
- "Fingerprint mismatch": wrong fingerprint, server regenerated its key (missing
--keyfile), or genuine MITM. Re-runchisel server --keygento confirm. - TLS-ALPN challenge fails on first start: verify A record propagation, port 443 reachability, and that nothing else is bound to 443 (
ss -tlnp | grep :443). - Reverse tunnel works but port unreachable: add a corresponding
ufw allowon the server side. Reverse remotes bind on the server. - Connection drops every 30–60s through a corporate proxy: lower
--keepaliveon both ends.
What's Next
- Front sensitive reverse-tunneled services with nginx + TLS before exposing them publicly
- Subscribe to the Chisel GitHub release feed and update with
apt install ./chisel_*.deb - Pair with WireGuard or Tailscale for cases where a full L3 mesh is a better fit
