tunnel.dsouza.ioSelf-hosted ngrok-style tunneling with frp, Caddy, and Cloudflare
The request flow from browser to your laptop:
In the Cloudflare dashboard, go to your dsouza.io zone, then DNS → Records → Add Record.
Type: A
Name: *.tunnel
Value: <your-server-ip>
Proxy: DNS only (gray cloud)
TTL: Auto
Caddy needs a Cloudflare API token to perform DNS-01 challenges for wildcard certificate issuance.
Go to My Profile → API Tokens → Create Token, then use the "Custom token" template:
Permissions: Zone → DNS → Edit
Zone scope: Specific zone → dsouza.io
Copy the token and save it somewhere secure — you'll need it for the Caddy config.
Your stock Caddy doesn't include the Cloudflare DNS module. You'll use xcaddy to build a custom binary.
First, install xcaddy (requires Go):
# Install Go if you don't have it
sudo apt install -y golang
# Install xcaddy
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
Build Caddy with the Cloudflare plugin:
xcaddy build --with github.com/caddy-dns/cloudflare
This produces a caddy binary in the current directory. Replace your existing install:
# Stop caddy
sudo systemctl stop caddy
# Back up the old binary
sudo cp $(which caddy) /usr/bin/caddy.bak
# Replace with the new one
sudo cp ./caddy /usr/bin/caddy
sudo chmod +x /usr/bin/caddy
# Verify the module is present
caddy list-modules | grep cloudflare
# Should output: dns.providers.cloudflare
apt upgrade may overwrite your custom binary. Consider pinning the package or using a systemd override to point to a custom path.
Add the following to your Caddyfile. This handles TLS for *.tunnel.dsouza.io and proxies traffic to frps.
*.tunnel.dsouza.io {
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
reverse_proxy 127.0.0.1:8080
}
Set the API token as an environment variable for the Caddy service:
sudo systemctl edit caddy
Add the following:
[Service]
Environment=CLOUDFLARE_API_TOKEN=your-token-here
Then restart Caddy:
sudo systemctl daemon-reload
sudo systemctl start caddy
Caddy will automatically request a wildcard certificate via the DNS-01 challenge. Check the logs to confirm:
journalctl -u caddy --no-pager -f
Download the latest frp release:
# Check https://github.com/fatedier/frp/releases for latest version
FRP_VERSION="0.61.1"
wget https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/frp_${FRP_VERSION}_linux_amd64.tar.gz
tar xzf frp_${FRP_VERSION}_linux_amd64.tar.gz
cd frp_${FRP_VERSION}_linux_amd64
# Copy server binary
sudo cp frps /usr/local/bin/
sudo chmod +x /usr/local/bin/frps
Create the server config:
sudo mkdir -p /etc/frp
# Control port — where frpc connects
bindPort = 7000
# HTTP vhost port — where Caddy sends traffic
vhostHTTPPort = 8080
# Subdomain configuration
subDomainHost = "tunnel.dsouza.io"
# Authentication — generate a strong random token
auth.method = "token"
auth.token = "your-strong-random-token-here"
openssl rand -hex 32
Create a systemd service:
[Unit]
Description=frp server
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/frps -c /etc/frp/frps.toml
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable frps
sudo systemctl start frps
7000 is open for inbound TCP (the frpc control connection). Port 8080 does not need to be open externally — only Caddy talks to it on localhost.
On macOS:
brew install frpc
Or download the binary manually from the frp releases page (grab the darwin_arm64 archive for Apple Silicon, or darwin_amd64 for Intel).
Create a base config file for convenience:
serverAddr = "dsouza.io"
serverPort = 7000
auth.method = "token"
auth.token = "your-strong-random-token-here"
Say you have a local dev server on port 3000 and want it reachable at myapp.tunnel.dsouza.io.
Create a tunnel config (or add to your base config):
serverAddr = "dsouza.io"
serverPort = 7000
auth.method = "token"
auth.token = "your-strong-random-token-here"
[[proxies]]
name = "myapp"
type = "http"
localPort = 3000
subdomain = "myapp"
Start the tunnel:
frpc -c ~/.config/frp/myapp.toml
That's it. Open https://myapp.tunnel.dsouza.io in a browser and you'll hit your local port 3000, with TLS handled by Caddy.
To expose multiple services, add more [[proxies]] blocks in the same config:
[[proxies]]
name = "myapp"
type = "http"
localPort = 3000
subdomain = "myapp"
[[proxies]]
name = "api"
type = "http"
localPort = 8000
subdomain = "api"
Once everything is set up, exposing a new service is just:
# 1. Create a config (or add a [[proxies]] block)
# 2. Run frpc
frpc -c ~/.config/frp/myapp.toml
# 3. Visit https://myapp.tunnel.dsouza.io
Useful commands on the server:
# Check frps status
sudo systemctl status frps
# View frps logs
journalctl -u frps --no-pager -f
# Check Caddy logs
journalctl -u caddy --no-pager -f
# Verify wildcard cert
curl -v https://test.tunnel.dsouza.io 2>&1 | grep subject