Skip to content
nickolasdelucaPublic
forked from Gouryella/drip

About

Self-hosted tunneling solution to expose localhost securely - unlimited bandwidth, no third-party servers

Resources

Stars

0 stars

Watchers

0 watching

Forks

 
 

Repository files navigation

Drip Logo

Drip

Your Tunnel, Your Domain, Anywhere

A self-hosted tunnelling service that exposes local services on your own domain.

中文文档

Go License TLS

Drip is a quiet, disciplined tunnel. You light a small lamp on your network, and it carries that light outward—through your own infrastructure, on your own terms.

One Go binary is both the client and, through drip server, the server. Traffic goes from your machine to your server and nowhere else.

This is a fork of Gouryella/drip. It adds a control plane, permanent tunnel names and a Windows service; documentation lives in this repository rather than on the upstream site.


Contents


Why Drip?

  • Control your data — no third-party servers; traffic stays between your client and your server
  • No limits — unlimited tunnels, bandwidth and requests
  • Actually free — use your own domain, no paid tiers, no feature gates
  • One binary — client, server, control plane and admin panel in a single file
  • Open source — BSD 3-Clause

What this fork adds

Control plane A SQLite database of accounts and per-machine credentials (drip_<id>_<secret>), so every client is identified instead of sharing one token
Reservations Pin a subdomain or a TCP port to a machine. It reconnects to the same URL forever, instead of a new random name per session
Wildcard TLS The server obtains and renews *.<domain> itself over ACME DNS-01, so no reverse proxy and no certificate files are needed
Admin panel An embedded web panel on its own port for credentials, reservations and live tunnel state
Windows service drip service install runs configured tunnels as a Windows service that survives reboots and logoff

Single-user self-hosted mode still works exactly as upstream: no database, one shared token or none at all.


Install

Linux and macOS

curl -sL https://raw.githubusercontent.com/nickolasdeluca/drip-ex/main/scripts/install.sh | bash

The server install writes to /usr/local/bin and registers a systemd unit, so it has to run as root:

curl -sL https://raw.githubusercontent.com/nickolasdeluca/drip-ex/main/scripts/install.sh | sudo bash

The script asks whether you are installing the client or the server, downloads the matching release and puts drip on your PATH. Every download is checked against the SHA-256 the release publishes; a mismatch aborts the install. The prompts read from /dev/tty, so they still work when the script arrives on stdin.

Do not run it as sudo bash <(curl ...). Process substitution hands bash a /dev/fd/63 path that only exists inside the calling shell, and sudo closes inherited file descriptors before exec, so root sees bash: /dev/fd/63: No such file or directory. Piping into sudo bash avoids the problem, as does downloading the script first.

Updating

Re-run the same command. The installer detects an existing drip, compares it against the latest release and offers to update — your configuration is left alone. On the server it stops drip-server, swaps the binary and starts it again; if the new version fails to start, it restores the previous binary and brings the service back up.

Windows

irm https://raw.githubusercontent.com/nickolasdeluca/drip-ex/main/scripts/install-client.ps1 | iex

To pass options, download the script first:

irm https://raw.githubusercontent.com/nickolasdeluca/drip-ex/main/scripts/install-client.ps1 -OutFile install-client.ps1
.\install-client.ps1 -InstallService -AllTunnels     # install and register the service
.\install-client.ps1 -Version v1.2.3 -InstallDir C:\tools\drip
.\install-client.ps1 -Uninstall

It verifies the release checksum, installs to %ProgramFiles%\drip when elevated (%LOCALAPPDATA%\Programs\drip otherwise) and adds the directory to PATH.

Manual

Grab an archive from Releases and put drip (or drip.exe) anywhere on your PATH. Or build from source.


Quick start

# 1. Point the client at your server (writes ~/.drip/config.yaml)
drip config init

# 2. Expose a local port
drip http 3000
# → https://swift-otter.your-domain.com

# 3. Or ask for a name
drip http 3000 --subdomain myapp
# → https://myapp.your-domain.com

Background tunnels:

drip http 3000 --daemon      # detach
drip list                    # what is running
drip attach http 3000        # watch its logs
drip stop http 3000          # or: drip stop all

On Windows, prefer the service over --daemon: a daemon dies at logoff and does not come back after a reboot.


Tunnel types

Command Exposes Example
drip http <port> A local HTTP server drip http 3000
drip https <port> A local HTTPS server drip https 8443 --skip-local-tls-verify
drip tcp <port> Any TCP service; the server allocates a public port drip tcp 5432

Flags shared by all three:

Flag Purpose
-n, --subdomain <name> Ask for a specific name
-a, --address <host> Forward somewhere other than 127.0.0.1
-d, --daemon Run in the background
--allow-ip / --deny-ip Restrict callers by IP or CIDR (repeatable)
--auth <password> Require proxy authentication with a password (http/https only)
--auth-bearer <token> Require a bearer token (http/https only)
--bandwidth <rate> Cap throughput: 500K, 1M, 1G
--transport <mode> auto, tcp (direct TLS 1.3) or wss (WebSocket, survives a CDN)
--skip-local-tls-verify Do not verify the certificate of a local HTTPS backend

Global flags: -s/--server, -t/--token, -v/--verbose, -k/--insecure (testing only).

The effective bandwidth limit is the smallest of the server default, the reservation override and what the client asked for — no side can raise a limit another one set.


Configuration file

drip config init writes ~/.drip/config.yaml (%USERPROFILE%\.drip\config.yaml on Windows). Set DRIP_CONFIG to read it from somewhere else.

server: tunnel.example.com:443
token: drip_a1b2c3d4e5f60718_YOUR_SECRET
tls: true

tunnels:
  - name: web
    type: http
    port: 3000
    subdomain: myapp

  - name: api
    type: http
    port: 8080
    subdomain: api
    transport: wss
    auth_bearer: sk-secret
    bandwidth: 5M

  - name: db
    type: tcp
    port: 5432
    subdomain: postgres
    allow_ips:
      - 10.0.0.0/8

Named tunnels are started by name:

drip start web        # one
drip start web api    # several
drip start --all      # everything in the file
drip start            # list what is configured

Tunnels can be managed without editing the file:

drip config tunnel add --name web --type http --port 3000
drip config tunnel add --name db --type tcp --port 5432 --bandwidth 5M
drip config tunnel add --name web --type http --port 9765 --replace
drip config tunnel list
drip config tunnel remove web

Leave --subdomain out and the server assigns the allocation reserved for this client, so a renamed allocation is picked up on the next reconnect. Naming one pins the request to that exact subdomain instead.

Other config commands: drip config show, drip config set --server X --token Y, drip config validate, drip config reset.


Run as a Windows service

Keeps configured tunnels connected across reboots with nobody logged in.

# From an elevated PowerShell prompt
drip config init                    # once, as the user who owns the token
drip config tunnel add --name web --type http --port 3000
drip service install --all          # or: --tunnel web --tunnel api
drip service start
drip service status

install-client.ps1 -InstallService -AllTunnels does all of that during installation, and install-client.ps1 -Uninstall reverses it.

The service cannot read a config inside a user profile, so install copies it to %ProgramData%\drip\config.yaml and runs from there. An existing copy is kept — an administrator may have edited it — which means a copy left by an earlier install can be older than the configuration you just changed. Refresh it with drip service install --reseed.

Command
drip service install Register the service. --all or --tunnel <name> (repeatable); --replace to update one already installed; --start-type delayed|auto|manual; --username / --password to run as a specific account; --config, --log, --name to override paths and the service name
drip service start / stop / restart Control it
drip service status State, PID, start type and the command line it runs
drip service uninstall Stop and remove it; configuration and logs are kept

How it handles configuration. A service runs as LocalSystem, whose home is C:\Windows\system32\config\systemprofile, so it cannot read a config file from your user profile. service install copies yours to %ProgramData%\drip\config.yaml and restricts it to SYSTEM and Administrators — it holds your token. Pass --config to point at a different file, which is then left untouched.

Reconnection. The service retries forever with exponential backoff and jitter, so a reboot before the network is up, or a server restart, is not fatal. Windows restarts the service itself if it exits (after 5s, 30s, then 60s).

Logs. %ProgramData%\drip\logs\service.log, plus start, stop and failure events in the Windows event log. To debug a service that will not stay up, run the same supervisor in the foreground:

drip service run --config C:\ProgramData\drip\config.yaml --all --verbose

On Linux and macOS, supervise drip start --all with systemd or launchd instead.


Running your own server

The server needs a domain, a port and a way to obtain certificates. Pick one of three TLS modes:

tls_mode Certificates come from Use when
acme The server itself, over ACME DNS-01 You control the DNS zone and want *.<domain> handled for you
manual tls_cert / tls_key on disk You already have a certificate
none A reverse proxy in front (Caddy, nginx) Something else terminates TLS

Only DNS-01 can issue a wildcard, and tunnel subdomains are unpredictable, so acme mode requires DNS provider API credentials:

acme.dns_provider Credential
cloudflare An API token with Zone.DNS:Write on the zone
hostinger An API token generated in hPanel under API

Hostinger tokens expire on a date chosen when they are created. Pick one well past the certificate lifetime, or renewals start failing with HTTP 401 months after setup. Adding another provider is one import plus one entry in internal/server/tls/dns.go.

Issuance takes a few minutes on Hostinger. The domain and the wildcard are separate certificates whose DNS-01 challenges share one _acme-challenge name, and Hostinger refuses a record TTL below 60 seconds, so the server waits out the CA's cached answer between the two rather than have the second validation read the first token. acme.propagation_delay_seconds tunes that wait.

Docker

No container image is published — Docker is unsupported for now — but the Dockerfile and compose files are here and build locally:

cd deployments
cp config.acme.example.yaml config.yaml   # edit: domain, DNS token, email
docker compose up -d --build

deployments/ holds the compose files and examples:

File
docker-compose.yml Server with direct TLS
docker-compose.caddy.yml Caddy terminating TLS in front of the server
config.example.yaml Direct TLS with certificate files
config.acme.example.yaml Automatic wildcard certificates
config.caddy.example.yaml Behind a reverse proxy
Caddyfile, nginx.example.conf Reverse proxy examples

Binary

drip server --config /etc/drip/config.yaml

Or entirely from flags and environment variables:

drip server \
  --domain tunnel.example.com \
  --port 443 \
  --db /var/lib/drip/drip.db \
  --tls-mode acme \
  --acme-dns-provider cloudflare \
  --acme-dns-token "$CF_API_TOKEN" \
  --acme-email ops@example.com \
  --admin 127.0.0.1:8444

Every server flag has an environment variable (DRIP_DOMAIN, DRIP_PORT, DRIP_DB_PATH, DRIP_TLS_MODE, …); run drip server --help for the full list. scripts/install-server.sh installs the binary and a systemd unit for you, and offers managed mode during setup: it writes db_path, require_auth and admin_address, and can also publish the panel (admin_public) or close the fleet (reservations_only). Managed mode writes no shared token unless you ask for one — a client using a shared token has no identity, so it can never bind an allocation. Declining managed mode keeps the upstream single-token server.


Control plane

Point the server at a database and every machine gets its own credential instead of sharing one token:

drip server --db /var/lib/drip/drip.db --require-auth
Server mode Configuration
Per-client credentials db_path set
One shared token token set, no db_path
Anonymous Neither (upstream default)

Onboard a machine:

export DRIP_DB_PATH=/var/lib/drip/drip.db

drip admin account create acme
drip admin client create laptop-01 --account acme
# → drip_a1b2c3d4e5f60718_<secret>   shown once, never again

drip admin reservation create --account acme --subdomain myapp --client a1b2c3d4e5f60718

Put that token in the machine's ~/.drip/config.yaml and it binds myapp on every connection — with no --subdomain flag at all, since the first reservation bound to a client is claimed automatically. That is the path the Windows service is built for.

drip admin account create|list Accounts
drip admin client create|list|disable|enable|rotate|delete Machine credentials
drip admin reservation create|list|bind|enable|disable|delete Names and ports
drip admin user create|list|role|passwd|enable|disable|delete Admin panel users

Notes worth knowing:

  • Only sha256(secret) is stored, and a token is displayed exactly once.
  • Reservations survive credential deletion — the name stays with the account.
  • --reservations-only turns the deployment into a closed fleet: registrations that do not bind a reservation are refused.
  • admin, api, www and a handful of others are reserved and cannot be claimed by clients.

Admin panel

drip server --db /var/lib/drip/drip.db --admin 127.0.0.1:8444

The panel is compiled into the binary and served on its own port, never on the tunnel data path. The first visit creates the first operator account. English and Brazilian Portuguese ship; the language follows the browser.

Reach it over a private network, a VPN or an SSH tunnel — exposing it publicly is discouraged, especially before that first-run screen has been used.

Users

Administrators add more people under Users. A user is either an admin, who can change anything, or a viewer, who sees everything and changes nothing; new users are viewers unless made admins. Every user can change their own password there, and nobody can change their own role or remove themselves. The last enabled administrator cannot be demoted, disabled or deleted.

drip admin user does the same from a shell, writing straight to the database — which is also the way back in when every administrator has lost their password:

drip admin user create alice --role viewer    # prompts for the password
drip admin user passwd root                   # reset, ending root's sessions

Publishing the panel

A managed deployment can also serve the panel on the server domain, over the public HTTPS port, in place of the landing page:

drip server --db /var/lib/drip/drip.db --admin 127.0.0.1:8444 --admin-public

--admin-public is an addition, not a move: the panel keeps its own listener, so an operator locked out of the public mount still has a loopback way in, and --admin therefore stays required.

What the published mount does not serve is first-run setup. It refuses every request until an administrator exists, and refuses POST /api/bootstrap for good, so the unauthenticated setup screen is reachable only on the panel's own address. Sign-in is throttled per source address and per username either way.

The landing page moves to /install, and tunnel subdomains are untouched: only a request whose Host is exactly the server domain reaches the panel.

Behind a TLS-terminating reverse proxy, note that sign-in throttling counts the address the connection arrives from — every client looks like the proxy, so the per-username half of the limit is the one doing the work.


Command reference

Command
drip http|https|tcp <port> Start a tunnel
drip start [names…] [--all] Start tunnels defined in the config file
drip list Show background tunnels (-i for an interactive picker)
drip attach [type] [port] Follow a background tunnel's output
drip stop <type> <port> or drip stop all Stop background tunnels
drip config init|show|set|validate|reset Client configuration
drip config tunnel add|list|remove Tunnels this client exposes
drip service … Windows service (see above)
drip server Run the tunnel server
drip server config Server configuration helpers
drip admin … Control plane: accounts, credentials, reservations
drip version Version, commit and build time

Build from source

Requires Go 1.26+.

make build            # bin/drip for this platform
make build-all        # linux, macOS and Windows, amd64 and arm64
make test             # go test -race -cover ./...
make e2e              # full end-to-end suite with a real server and client
make fmt lint         # gofmt and golangci-lint
make demo             # local server, admin panel and two clients

make build-all VERSION=v1.2.3 stamps the version into the binaries. Releases are produced by GoReleaser from .goreleaser.yaml when a v*.*.* tag is pushed.


Recent upstream changes

2025-02-14

  • Bandwidth limiting (QoS) — per-tunnel control with a token bucket; the server enforces min(client, server) as the effective limit
  • Transport protocol control — independent configuration for the service domain and the tunnel domain

2025-01-29

  • Bearer token authentication for tunnel access control
  • Code optimisation — large modules split into focused components

License

BSD 3-Clause License — see LICENSE.

About

Self-hosted tunneling solution to expose localhost securely - unlimited bandwidth, no third-party servers

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages