(Cloudflare Tunnel / ngrok alternative) Self-hosted edge server with built-in tunneling. Expose services behind NAT or CGNAT at your own domain — automatic HTTPS, subdomain routing, edge filtering.
27
stars
375
commits
Go
primary language
Sep 11, 2026
updated
A self-hosted edge platform with built-in tunneling. One Go binary on a $5 VPS gives you what Cloudflare gives you — TLS termination, automatic HTTPS, hidden origin IPs, and request filtering at the edge — except the edge is yours end-to-end. No third party in your TLS perimeter, no SaaS deciding what your service is allowed to do.
The built-in tunnel is the unlock: any device with outbound internet becomes publicly reachable. A Raspberry Pi in your closet, a laptop on coffee-shop wifi, a server with no public IP — no port forwarding, no NAT traversal, no static IP. And because you own the edge, you own every decision made there: routing, auth, rate limiting, request filtering.
In short, it's a public router for your private services — the same idea as port forwarding, just on a VPS instead of your home router. That's what makes it work even where forwarding can't: behind CGNAT, locked-down campus/corporate networks, or an ISP that blocks inbound ports.
Example:
photos.yourdomain.com → Immich on home NAS (192.168.1.50:2283)
lab.yourdomain.com → Jupyter on university server (no public IP)
vault.yourdomain.com → Bitwarden on Raspberry Pi (behind NAT)
📺 Prefer to watch? 12-minute video install guide — a full walkthrough from VPS to first tunnel.
Gopher ships as a single self-contained binary for Linux. No runtime dependencies — Caddy and rathole are embedded in the binary and run as child processes that Gopher supervises.
Edge — the VPS Gopher runs on:
*.yourdomain.com → <VPS IP>systemd, iptables, and root/sudo for install and firewall management; apt for the optional fail2ban featureOrigin — each machine you expose:
sudo — required by the bootstrap/agent install. Minimal images (Proxmox, some containers) often
ship without it: apt install sudo, or run the bootstrap command as root with the sudo removed.| Tier 1 — tested every release | Best effort — built, not verified | Unsupported in v0.1.0 | |
|---|---|---|---|
| Edge | Ubuntu 22.04/24.04 LTS, Debian 12 — amd64, arm64 | Debian 13, other apt-family distros | RHEL-family, Alpine, Arch, non-systemd distros, macOS, Windows |
| Origin agent | same distros — amd64, arm64 | armv7 (32-bit ARM) | same |
Other distros may well work — they're just not part of the release test matrix yet.
Quick install (latest stable):
curl -fsSL https://raw.githubusercontent.com/smalex-z/gopher/main/scripts/install.sh | bash
The installer verifies the binary against the release's published SHA-256 checksums before installing. To pin an exact version (e.g. for a release candidate):
curl -fsSL https://raw.githubusercontent.com/smalex-z/gopher/main/scripts/install.sh | bash -s -- --version v0.1.0
# or: GOPHER_VERSION=v0.1.0 curl -fsSL ... | bash
Pre-releases (newer features, may be unstable):
curl -fsSL https://raw.githubusercontent.com/smalex-z/gopher/main/scripts/install.sh | bash -s -- --prerelease
Or
Manual download:
# Check releases page for latest version
wget https://github.com/smalex-z/gopher/releases/latest/download/gopher-linux-amd64
chmod +x gopher-linux-amd64
sudo mv gopher-linux-amd64 /usr/local/bin/gopher
Verify your download against SHA256SUMS.txt on the releases page.
Or
Build from source (for development, auditing, or unsupported architectures):
git clone https://github.com/smalex-z/gopher.git
cd gopher
./scripts/build.sh # builds the embedded frontend + agent, then the binary
sudo ./gopher install
Requires Go and Node 18+ (the build fetches the pinned Go toolchain automatically). The result is a
single self-contained ./gopher binary, identical to the release artifact.
# Start the web UI (listens on :4321)
./gopher
# Install as a systemd service (runs on boot, survives reboots)
sudo ./gopher install
# Check what you're running (version + embedded component versions)
gopher version
# Service management
sudo systemctl status gopher
sudo systemctl restart gopher
# Uninstall
sudo ./gopher uninstall
On first start, visit http://<your-vps-ip>:4321 and complete the setup wizard:
router.<yourdomain>)After the wizard, you're at the main dashboard:
1. Add Machines (Machines tab)
gopher-agent — the control channel Gopher uses to
manage the machine (config pushes, health streaming, self-update) over the tunnel itself2. Create Tunnels (Tunnels tab)
photos) and the local port (e.g. 2283)https://photos.yourdomain.com is live with automatic HTTPSGopher supports HTTP, raw TCP, and UDP tunnels.
Run this on the edge (the VPS where you ran gopher):
sudo gopher uninstall
It tears down the whole deployment, in order:
gopher-agent and
tunnel client on every reachable machine (the edge is your path to NAT'd boxes, so this happens
before the edge goes away). Machines that are offline can't be reached and are reported as
orphans to clean up by hand (below)..gopher-backup)
or remove them entirely, and removes the sudoers entry and the gopher-jump user.Your data (database, certs, state) is kept by default — you're prompted before it's deleted, so an uninstall-to-reinstall keeps your setup. Flags:
--keep-data — always preserve the data directory--keep-origins — leave the origin machines installed (only remove the edge)--skip-prompts — remove everything (data + origins included) non-interactivelyTo remove a single machine without uninstalling everything, just delete it on the dashboard's Machines tab — Gopher tears it down on the box for you.
A machine that was offline when you uninstalled the edge keeps its agent + tunnel client installed (it just can't reach the now-gone edge). Clean it up directly on the box:
sudo gopher-uninstall # full removal
sudo gopher-uninstall --remove-tunnel <ID> # remove just one tunnel
sudo gopher-uninstall --remove-machine <ID> # remove just one machine's SSH entry
Full removal stops and deletes gopher-agent and rathole-client (services, binaries, /etc/rathole,
/etc/gopher-agent) and the gopher user.
(The helper lives at /usr/local/bin/gopher-uninstall — use the full path if your shell can't find it.)
Setup: Run Gopher on a public VPS. Point *.yourdomain.com DNS to it.
Bootstrap machines: Run a one-liner on any private machine to install the tunnel client + agent and establish an outbound tunnel to your VPS.
Create tunnels: In Gopher's web UI, map subdomains to services: photos.yourdomain.com → machine-name:2283
Traffic flow:
User visits photos.yourdomain.com
↓
DNS resolves to your VPS IP
↓
Caddy terminates TLS on VPS
↓
Gopher routes by subdomain
↓
rathole tunnel forwards to private machine
↓
Service responds back through tunnel
Key advantage: Machines connect outbound to the VPS, bypassing NAT and firewalls. The VPS routes incoming internet traffic back through those established tunnels. Your origin IPs are never exposed.
Visitor traffic is encrypted (HTTPS) to the edge. The edge terminates TLS to perform routing, filtering, and bot detection, then re-encrypts the traffic via rathole's Noise transport for the hop to your origin. No plaintext ever traverses the public internet — but because the edge inspects requests, it holds the TLS keys by design. That's the deliberate trade-off for edge filtering: terminating at the edge is what makes bot detection possible (you can't filter traffic you've chosen not to decrypt). Since the edge is your VPS, no third party sees your traffic at any point.
This is not TLS passthrough / true end-to-end encryption to the origin — the edge decrypts. If you need an edge that never sees plaintext, passthrough is the alternative, but it forecloses edge filtering.
Gopher is a self-hosted edge server that sits between the internet and your private services:
Internet → Gopher VPS (your edge) → Private networks (your origins)
What makes it an edge server:
Components managed by Gopher:
Caddy and rathole are bundled into the single gopher binary (go:embed) and
extracted + run as child processes that Gopher supervises — no separate apt
package, no downloads at install, no extra systemd units. The whole edge lives
under /etc/gopher (config) and /var/lib/gopher (state + certs): one artifact
to install, two directories to back up.
Similar to Cloudflare, but:
gopher/
├── cmd/
│ ├── server/ # Entry point; embeds frontend, caddy, rathole, agents
│ └── agent/ # gopher-agent (runs on each origin)
├── frontend/ # React + TypeScript + Tailwind
│ └── src/
│ ├── pages/ # Dashboard, Machines, Tunnels, Server, Setup
│ └── components/ # UI components
├── internal/
│ ├── api/ # Chi router + HTTP handlers
│ ├── agentpb/ # gRPC contract between edge and gopher-agent
│ ├── config/ # Caddyfile + rathole TOML generation
│ ├── db/ # SQLite (GORM) models + migrations
│ ├── embedbin/ + supervisor/ # Embedded caddy/rathole extraction + child-process supervision
│ ├── service/ # Business logic (install, tunnels, firewall, …)
│ └── ssh/ # SSH access to machines (jumpbox keys, fallback pushes)
└── scripts/
├── build.sh # Build frontend + agents, then Go binary
└── dev.sh # Dev mode with hot reload
Backend: Go 1.25, Chi router, GORM + glebarez/sqlite (pure-Go, no CGO), gRPC (agent control plane)
Frontend: React 18 + TypeScript, Vite, Tailwind CSS — embedded in the binary via //go:embed
Infrastructure (managed by Gopher): Caddy 2 for HTTPS + routing, rathole for tunneling
Because Gopher terminates TLS at the edge (see Encryption model), it can inspect and filter requests before they ever reach your origin — the thing a plain tunnel can't do. This is what sets it apart from a raw ngrok/Cloudflare-Tunnel pipe.
Dashboard 2FA (TOTP). Protect the admin dashboard with time-based one-time passwords — QR enrollment, multiple devices, and one-time backup codes.
fail2ban integration. Gopher writes and manages a fail2ban jail that bans IPs after repeated failed dashboard logins (configurable retry / find-time / ban-time, plus an allowlist) and surfaces jail status in the dashboard.
DNS preflight. The setup wizard runs a battery of DNS checks before you go live — proving the wildcard record actually resolves to your VPS (not a registrar parking page), checking propagation across public resolvers, and flagging CAA records that would block certificate issuance — so DNS problems surface during setup instead of after.
Firewall management. Gopher can manage iptables for you — a dedicated chain with tunnel ports opened and closed automatically as tunnels come and go (see Firewall Setup).
Bot detection (experimental). Opt-in per HTTP tunnel: a JavaScript proof-of-work challenge (~1–3s of browser work) in front of your service, with an HMAC-signed session cookie so real visitors aren't re-challenged, and JSON responses for API clients. It filters the scrapers and port-scanners that crawl for exposed services. It is best-effort friction, not a security boundary — see Known Limitations.
Password-gated tunnels (experimental). Opt-in per HTTP tunnel: a shared-password login page in front of the service (separate from the dashboard login), with session TTL and an IP allowlist for trusted networks. Handy for sharing a service with a few people without setting up real auth.
Honest list of the current trade-offs — each has a tracking issue and a plan:
NOPASSWD sudo rights (it
manages iptables, systemd, and system packages for you). Run it on its own box, not a shared
server. Narrowing these grants is planned.gopher.db in plaintext — so treat database backups as secrets. At-rest encryption is planned.SameSite=Lax and cross-origin credentialed requests are
rejected; remaining exposure is limited to top-level-navigation GETs, whose responses can't be
read cross-origin. Per-request CSRF tokens are planned.Homelab:
photos.home.com → Jellyfin media server
vault.home.com → Bitwarden password manager
files.home.com → Nextcloud file sync
monitor.home.com → Grafana dashboards
Research Lab:
jupyter.lab.edu → Jupyter notebook server
vnc1.lab.edu → VNC to lab workstation
data.lab.edu → Dataset browser
Multi-site:
nas.example.com → Home NAS (Maryland)
jupyter.example.com → Lab server (UCLA)
media.example.com → Friend's shared Plex
Most tools here are one half of the stack: ngrok and Tailscale are tunnels with no programmable edge; Caddy and Traefik are edges that can't reach behind NAT. Gopher is the combination — a tunnel and an edge you own — the category Cloudflare productized, except self-hosted, so no third party ever sits in front of your services.
| Gopher | ngrok | Cloudflare Tunnel | Tailscale Funnel | Port Forwarding | |
|---|---|---|---|---|---|
| Self-hosted | ✅ | ❌ | ❌ | ❌ | N/A |
| Who can read your requests | ✅ Only you | ❌ ngrok | ❌ Cloudflare | ✅ Only you | ✅ Only you |
| Edge request filtering (bot detection) | ✅ | 💰 Paid | ✅ | ❌ | ❌ |
| Multi-region routing | ✅ | 💰 Paid | ✅ | ⚠️ Limited | ❌ |
| Origin IP hidden | ✅ | ✅ | ✅ | ✅ | ❌ Exposes home IP |
| Unmetered bandwidth | ✅ | ❌ Metered | ✅ | ⚠️ Relay-limited | ✅ |
| Upload size cap | ✅ None | ✅ None | ❌ 100MB (free) | ✅ None | ✅ None |
| Custom domain | ✅ | 💰 Paid only | ⚠️ CF DNS required | ❌ *.ts.net only | ✅ |
| Any DNS registrar | ✅ | ✅ | ❌ Must use CF DNS | ❌ Tailscale subdomain | ✅ |
| Permanent URLs | ✅ | ❌ Ephemeral (free) | ✅ | ✅ | ✅ |
| Tunnel count | ✅ Unlimited | ❌ 1 (free) | ✅ Unlimited | ✅ Unlimited | ✅ Unlimited |
| Protocol support | HTTP / TCP / UDP | HTTP / TCP | HTTP/HTTPS** | HTTPS only*** | All |
| Works behind NAT/CGNAT | ✅ | ✅ | ✅ | ✅ | ❌ |
| Automatic HTTPS | ✅ | ✅ | ✅ | ✅ | ❌ Manual |
| No vendor lock-in | ✅ | ❌ | ❌ | ❌ | ✅ |
* Free with significant limitations
** Cloudflare Tunnel serves HTTP/HTTPS to anonymous visitors; raw TCP needs Spectrum (Pro+), UDP needs Enterprise
*** Tailscale Funnel is HTTPS-only on port 443; no TCP/UDP, no raw port exposure
Cloudflare and ngrok terminate your TLS on their servers, so they can read your requests.
Gopher can manage iptables rules automatically. During the setup wizard, choose Gopher-managed firewall mode and Gopher will:
GOPHER_TUNNELS iptables chainIf you prefer to manage firewall rules yourself, choose Manual mode and open the required ports yourself. Each tunnel you create gets its own port, assigned automatically from 1024 up (skipping any port already in use) — Gopher shows the assigned port when you create one.
Most cloud providers have a firewall that sits in front of the VM and blocks traffic before it ever reaches the OS. You need to open ports there as well — Gopher cannot manage these.
Oracle Cloud (OCI) — the most common gotcha, since the default security list blocks almost everything:
0.0.0.0/0Alternatively, use a Network Security Group attached to your instance instead of the security list.
AWS EC2:
0.0.0.0/0Hetzner:
DigitalOcean:
Tested providers:
Minimum: 1 vCPU, 512 MB RAM handles 10+ tunnels comfortably.
Gopher would not exist without two excellent open-source projects:
Caddy handles all HTTPS termination and subdomain routing on the VPS. It automatically provisions and renews TLS certificates via Let's Encrypt, with zero configuration required from the user. Gopher generates and manages the Caddyfile for you, but Caddy is doing the actual reverse proxying.
rathole is the tunnel engine. It's a Rust-based, extremely lightweight TCP/UDP tunnel that machines use to punch through NAT and firewalls by maintaining a persistent outbound connection to the VPS. Gopher manages the rathole server config and deploys rathole client configs to each machine — but rathole is what makes the tunnels actually work.
If you find Gopher useful, please consider starring those projects too.
Issues and PRs welcome. See open issues.
Areas that would help most:
Apache License 2.0 — see LICENSE and NOTICE
Gopher is a thin management layer on top of two great projects: Caddy by Matt Holt and the team, and rathole (originally by rapiz1). Without them there is no Gopher.
363 commits
12 commits
Go
68.1%
TypeScript
25.7%
Shell
6.1%
(Cloudflare Tunnel / ngrok alternative) Self-hosted edge server with built-in tunneling. Expose services behind NAT or CGNAT at your own domain — automatic HTTPS, subdomain routing, edge filtering.
27
stars
375
commits
Go
primary language
Sep 11, 2026
updated
A self-hosted edge platform with built-in tunneling. One Go binary on a $5 VPS gives you what Cloudflare gives you — TLS termination, automatic HTTPS, hidden origin IPs, and request filtering at the edge — except the edge is yours end-to-end. No third party in your TLS perimeter, no SaaS deciding what your service is allowed to do.
The built-in tunnel is the unlock: any device with outbound internet becomes publicly reachable. A Raspberry Pi in your closet, a laptop on coffee-shop wifi, a server with no public IP — no port forwarding, no NAT traversal, no static IP. And because you own the edge, you own every decision made there: routing, auth, rate limiting, request filtering.
In short, it's a public router for your private services — the same idea as port forwarding, just on a VPS instead of your home router. That's what makes it work even where forwarding can't: behind CGNAT, locked-down campus/corporate networks, or an ISP that blocks inbound ports.
Example:
photos.yourdomain.com → Immich on home NAS (192.168.1.50:2283)
lab.yourdomain.com → Jupyter on university server (no public IP)
vault.yourdomain.com → Bitwarden on Raspberry Pi (behind NAT)
📺 Prefer to watch? 12-minute video install guide — a full walkthrough from VPS to first tunnel.
Gopher ships as a single self-contained binary for Linux. No runtime dependencies — Caddy and rathole are embedded in the binary and run as child processes that Gopher supervises.
Edge — the VPS Gopher runs on:
*.yourdomain.com → <VPS IP>systemd, iptables, and root/sudo for install and firewall management; apt for the optional fail2ban featureOrigin — each machine you expose:
sudo — required by the bootstrap/agent install. Minimal images (Proxmox, some containers) often
ship without it: apt install sudo, or run the bootstrap command as root with the sudo removed.| Tier 1 — tested every release | Best effort — built, not verified | Unsupported in v0.1.0 | |
|---|---|---|---|
| Edge | Ubuntu 22.04/24.04 LTS, Debian 12 — amd64, arm64 | Debian 13, other apt-family distros | RHEL-family, Alpine, Arch, non-systemd distros, macOS, Windows |
| Origin agent | same distros — amd64, arm64 | armv7 (32-bit ARM) | same |
Other distros may well work — they're just not part of the release test matrix yet.
Quick install (latest stable):
curl -fsSL https://raw.githubusercontent.com/smalex-z/gopher/main/scripts/install.sh | bash
The installer verifies the binary against the release's published SHA-256 checksums before installing. To pin an exact version (e.g. for a release candidate):
curl -fsSL https://raw.githubusercontent.com/smalex-z/gopher/main/scripts/install.sh | bash -s -- --version v0.1.0
# or: GOPHER_VERSION=v0.1.0 curl -fsSL ... | bash
Pre-releases (newer features, may be unstable):
curl -fsSL https://raw.githubusercontent.com/smalex-z/gopher/main/scripts/install.sh | bash -s -- --prerelease
Or
Manual download:
# Check releases page for latest version
wget https://github.com/smalex-z/gopher/releases/latest/download/gopher-linux-amd64
chmod +x gopher-linux-amd64
sudo mv gopher-linux-amd64 /usr/local/bin/gopher
Verify your download against SHA256SUMS.txt on the releases page.
Or
Build from source (for development, auditing, or unsupported architectures):
git clone https://github.com/smalex-z/gopher.git
cd gopher
./scripts/build.sh # builds the embedded frontend + agent, then the binary
sudo ./gopher install
Requires Go and Node 18+ (the build fetches the pinned Go toolchain automatically). The result is a
single self-contained ./gopher binary, identical to the release artifact.
# Start the web UI (listens on :4321)
./gopher
# Install as a systemd service (runs on boot, survives reboots)
sudo ./gopher install
# Check what you're running (version + embedded component versions)
gopher version
# Service management
sudo systemctl status gopher
sudo systemctl restart gopher
# Uninstall
sudo ./gopher uninstall
On first start, visit http://<your-vps-ip>:4321 and complete the setup wizard:
router.<yourdomain>)After the wizard, you're at the main dashboard:
1. Add Machines (Machines tab)
gopher-agent — the control channel Gopher uses to
manage the machine (config pushes, health streaming, self-update) over the tunnel itself2. Create Tunnels (Tunnels tab)
photos) and the local port (e.g. 2283)https://photos.yourdomain.com is live with automatic HTTPSGopher supports HTTP, raw TCP, and UDP tunnels.
Run this on the edge (the VPS where you ran gopher):
sudo gopher uninstall
It tears down the whole deployment, in order:
gopher-agent and
tunnel client on every reachable machine (the edge is your path to NAT'd boxes, so this happens
before the edge goes away). Machines that are offline can't be reached and are reported as
orphans to clean up by hand (below)..gopher-backup)
or remove them entirely, and removes the sudoers entry and the gopher-jump user.Your data (database, certs, state) is kept by default — you're prompted before it's deleted, so an uninstall-to-reinstall keeps your setup. Flags:
--keep-data — always preserve the data directory--keep-origins — leave the origin machines installed (only remove the edge)--skip-prompts — remove everything (data + origins included) non-interactivelyTo remove a single machine without uninstalling everything, just delete it on the dashboard's Machines tab — Gopher tears it down on the box for you.
A machine that was offline when you uninstalled the edge keeps its agent + tunnel client installed (it just can't reach the now-gone edge). Clean it up directly on the box:
sudo gopher-uninstall # full removal
sudo gopher-uninstall --remove-tunnel <ID> # remove just one tunnel
sudo gopher-uninstall --remove-machine <ID> # remove just one machine's SSH entry
Full removal stops and deletes gopher-agent and rathole-client (services, binaries, /etc/rathole,
/etc/gopher-agent) and the gopher user.
(The helper lives at /usr/local/bin/gopher-uninstall — use the full path if your shell can't find it.)
Setup: Run Gopher on a public VPS. Point *.yourdomain.com DNS to it.
Bootstrap machines: Run a one-liner on any private machine to install the tunnel client + agent and establish an outbound tunnel to your VPS.
Create tunnels: In Gopher's web UI, map subdomains to services: photos.yourdomain.com → machine-name:2283
Traffic flow:
User visits photos.yourdomain.com
↓
DNS resolves to your VPS IP
↓
Caddy terminates TLS on VPS
↓
Gopher routes by subdomain
↓
rathole tunnel forwards to private machine
↓
Service responds back through tunnel
Key advantage: Machines connect outbound to the VPS, bypassing NAT and firewalls. The VPS routes incoming internet traffic back through those established tunnels. Your origin IPs are never exposed.
Visitor traffic is encrypted (HTTPS) to the edge. The edge terminates TLS to perform routing, filtering, and bot detection, then re-encrypts the traffic via rathole's Noise transport for the hop to your origin. No plaintext ever traverses the public internet — but because the edge inspects requests, it holds the TLS keys by design. That's the deliberate trade-off for edge filtering: terminating at the edge is what makes bot detection possible (you can't filter traffic you've chosen not to decrypt). Since the edge is your VPS, no third party sees your traffic at any point.
This is not TLS passthrough / true end-to-end encryption to the origin — the edge decrypts. If you need an edge that never sees plaintext, passthrough is the alternative, but it forecloses edge filtering.
Gopher is a self-hosted edge server that sits between the internet and your private services:
Internet → Gopher VPS (your edge) → Private networks (your origins)
What makes it an edge server:
Components managed by Gopher:
Caddy and rathole are bundled into the single gopher binary (go:embed) and
extracted + run as child processes that Gopher supervises — no separate apt
package, no downloads at install, no extra systemd units. The whole edge lives
under /etc/gopher (config) and /var/lib/gopher (state + certs): one artifact
to install, two directories to back up.
Similar to Cloudflare, but:
gopher/
├── cmd/
│ ├── server/ # Entry point; embeds frontend, caddy, rathole, agents
│ └── agent/ # gopher-agent (runs on each origin)
├── frontend/ # React + TypeScript + Tailwind
│ └── src/
│ ├── pages/ # Dashboard, Machines, Tunnels, Server, Setup
│ └── components/ # UI components
├── internal/
│ ├── api/ # Chi router + HTTP handlers
│ ├── agentpb/ # gRPC contract between edge and gopher-agent
│ ├── config/ # Caddyfile + rathole TOML generation
│ ├── db/ # SQLite (GORM) models + migrations
│ ├── embedbin/ + supervisor/ # Embedded caddy/rathole extraction + child-process supervision
│ ├── service/ # Business logic (install, tunnels, firewall, …)
│ └── ssh/ # SSH access to machines (jumpbox keys, fallback pushes)
└── scripts/
├── build.sh # Build frontend + agents, then Go binary
└── dev.sh # Dev mode with hot reload
Backend: Go 1.25, Chi router, GORM + glebarez/sqlite (pure-Go, no CGO), gRPC (agent control plane)
Frontend: React 18 + TypeScript, Vite, Tailwind CSS — embedded in the binary via //go:embed
Infrastructure (managed by Gopher): Caddy 2 for HTTPS + routing, rathole for tunneling
Because Gopher terminates TLS at the edge (see Encryption model), it can inspect and filter requests before they ever reach your origin — the thing a plain tunnel can't do. This is what sets it apart from a raw ngrok/Cloudflare-Tunnel pipe.
Dashboard 2FA (TOTP). Protect the admin dashboard with time-based one-time passwords — QR enrollment, multiple devices, and one-time backup codes.
fail2ban integration. Gopher writes and manages a fail2ban jail that bans IPs after repeated failed dashboard logins (configurable retry / find-time / ban-time, plus an allowlist) and surfaces jail status in the dashboard.
DNS preflight. The setup wizard runs a battery of DNS checks before you go live — proving the wildcard record actually resolves to your VPS (not a registrar parking page), checking propagation across public resolvers, and flagging CAA records that would block certificate issuance — so DNS problems surface during setup instead of after.
Firewall management. Gopher can manage iptables for you — a dedicated chain with tunnel ports opened and closed automatically as tunnels come and go (see Firewall Setup).
Bot detection (experimental). Opt-in per HTTP tunnel: a JavaScript proof-of-work challenge (~1–3s of browser work) in front of your service, with an HMAC-signed session cookie so real visitors aren't re-challenged, and JSON responses for API clients. It filters the scrapers and port-scanners that crawl for exposed services. It is best-effort friction, not a security boundary — see Known Limitations.
Password-gated tunnels (experimental). Opt-in per HTTP tunnel: a shared-password login page in front of the service (separate from the dashboard login), with session TTL and an IP allowlist for trusted networks. Handy for sharing a service with a few people without setting up real auth.
Honest list of the current trade-offs — each has a tracking issue and a plan:
NOPASSWD sudo rights (it
manages iptables, systemd, and system packages for you). Run it on its own box, not a shared
server. Narrowing these grants is planned.gopher.db in plaintext — so treat database backups as secrets. At-rest encryption is planned.SameSite=Lax and cross-origin credentialed requests are
rejected; remaining exposure is limited to top-level-navigation GETs, whose responses can't be
read cross-origin. Per-request CSRF tokens are planned.Homelab:
photos.home.com → Jellyfin media server
vault.home.com → Bitwarden password manager
files.home.com → Nextcloud file sync
monitor.home.com → Grafana dashboards
Research Lab:
jupyter.lab.edu → Jupyter notebook server
vnc1.lab.edu → VNC to lab workstation
data.lab.edu → Dataset browser
Multi-site:
nas.example.com → Home NAS (Maryland)
jupyter.example.com → Lab server (UCLA)
media.example.com → Friend's shared Plex
Most tools here are one half of the stack: ngrok and Tailscale are tunnels with no programmable edge; Caddy and Traefik are edges that can't reach behind NAT. Gopher is the combination — a tunnel and an edge you own — the category Cloudflare productized, except self-hosted, so no third party ever sits in front of your services.
| Gopher | ngrok | Cloudflare Tunnel | Tailscale Funnel | Port Forwarding | |
|---|---|---|---|---|---|
| Self-hosted | ✅ | ❌ | ❌ | ❌ | N/A |
| Who can read your requests | ✅ Only you | ❌ ngrok | ❌ Cloudflare | ✅ Only you | ✅ Only you |
| Edge request filtering (bot detection) | ✅ | 💰 Paid | ✅ | ❌ | ❌ |
| Multi-region routing | ✅ | 💰 Paid | ✅ | ⚠️ Limited | ❌ |
| Origin IP hidden | ✅ | ✅ | ✅ | ✅ | ❌ Exposes home IP |
| Unmetered bandwidth | ✅ | ❌ Metered | ✅ | ⚠️ Relay-limited | ✅ |
| Upload size cap | ✅ None | ✅ None | ❌ 100MB (free) | ✅ None | ✅ None |
| Custom domain | ✅ | 💰 Paid only | ⚠️ CF DNS required | ❌ *.ts.net only | ✅ |
| Any DNS registrar | ✅ | ✅ | ❌ Must use CF DNS | ❌ Tailscale subdomain | ✅ |
| Permanent URLs | ✅ | ❌ Ephemeral (free) | ✅ | ✅ | ✅ |
| Tunnel count | ✅ Unlimited | ❌ 1 (free) | ✅ Unlimited | ✅ Unlimited | ✅ Unlimited |
| Protocol support | HTTP / TCP / UDP | HTTP / TCP | HTTP/HTTPS** | HTTPS only*** | All |
| Works behind NAT/CGNAT | ✅ | ✅ | ✅ | ✅ | ❌ |
| Automatic HTTPS | ✅ | ✅ | ✅ | ✅ | ❌ Manual |
| No vendor lock-in | ✅ | ❌ | ❌ | ❌ | ✅ |
* Free with significant limitations
** Cloudflare Tunnel serves HTTP/HTTPS to anonymous visitors; raw TCP needs Spectrum (Pro+), UDP needs Enterprise
*** Tailscale Funnel is HTTPS-only on port 443; no TCP/UDP, no raw port exposure
Cloudflare and ngrok terminate your TLS on their servers, so they can read your requests.
Gopher can manage iptables rules automatically. During the setup wizard, choose Gopher-managed firewall mode and Gopher will:
GOPHER_TUNNELS iptables chainIf you prefer to manage firewall rules yourself, choose Manual mode and open the required ports yourself. Each tunnel you create gets its own port, assigned automatically from 1024 up (skipping any port already in use) — Gopher shows the assigned port when you create one.
Most cloud providers have a firewall that sits in front of the VM and blocks traffic before it ever reaches the OS. You need to open ports there as well — Gopher cannot manage these.
Oracle Cloud (OCI) — the most common gotcha, since the default security list blocks almost everything:
0.0.0.0/0Alternatively, use a Network Security Group attached to your instance instead of the security list.
AWS EC2:
0.0.0.0/0Hetzner:
DigitalOcean:
Tested providers:
Minimum: 1 vCPU, 512 MB RAM handles 10+ tunnels comfortably.
Gopher would not exist without two excellent open-source projects:
Caddy handles all HTTPS termination and subdomain routing on the VPS. It automatically provisions and renews TLS certificates via Let's Encrypt, with zero configuration required from the user. Gopher generates and manages the Caddyfile for you, but Caddy is doing the actual reverse proxying.
rathole is the tunnel engine. It's a Rust-based, extremely lightweight TCP/UDP tunnel that machines use to punch through NAT and firewalls by maintaining a persistent outbound connection to the VPS. Gopher manages the rathole server config and deploys rathole client configs to each machine — but rathole is what makes the tunnels actually work.
If you find Gopher useful, please consider starring those projects too.
Issues and PRs welcome. See open issues.
Areas that would help most:
Apache License 2.0 — see LICENSE and NOTICE
Gopher is a thin management layer on top of two great projects: Caddy by Matt Holt and the team, and rathole (originally by rapiz1). Without them there is no Gopher.
363 commits
12 commits
Go
68.1%
TypeScript
25.7%
Shell
6.1%