Recently I decided I wanted to run my own Bitcoin and Lightning node and I wanted it to be reachable on the public internet. I didn’t, however, want it to actually reside on the server that has the static public IPv4 and IPv6 addresses available. Thus, a reverse proxy was needed. This turned out to be a pretty simple thing to solve for thanks to the Nginx Stream Proxy module and Tailscale. Here’s the basic architecture:
- Nginx on a virtual private server (VPS) at Hetzner listens on ports 8333 & 9735 for TCP connections
- The stream proxy module forwards those connections to bitcoind and lnd over Tailscale
- The server running bitcoind and lnd uses the Hetzner VPS as a Tailscale exit node so that all outbound traffic is via the VPS
Here’s a technical breakdown of how I make that happen. My configuration is done via NixOS flakes, but the general process would work on anything using Nginx and Tailscale.
{ config, username, ... }: let
domain = "example.com";
private_btc = "some-host.your-domain.ts.net";
in {
networking.firewall.allowedTCPPorts = [
8333 # Bitcoin Core
9735 # LND
];
services = {
nginx = {
enable = true;
streamConfig = ''
server {
listen 0.0.0.0:8333;
listen [::]:8333;
proxy_pass ${private_btc}:8333;
}
server {
listen 0.0.0.0:9735;
listen [::]:9735;
proxy_pass ${private_btc}:9735;
}
'';
}; # end nginx
tailscale = {
enable = true;
authKeyFile = config.sops.secrets.tailscale_key.path;
extraUpFlags = [
"--advertise-exit-node"
"--operator"
"${username}"
"--ssh"
];
useRoutingFeatures = "both";
}; # end tailscale
}; # end services
sops = {
age.keyFile = "${config.users.users.${username}.home}/.config/sops/age/keys.txt";
defaultSopsFile = ../secrets.yaml;
secrets = {
tailscale_key = {
restartUnits = [ "tailscaled-autoconnect.service" ];
};
};
}; # end sops
}
Breaking that down a little:
networking.firewall.allowedTCPPorts
opens the firewall ports needed for bitcoind and lndservices.nginx
configures twongx_stream_proxy_module
instances within thestreamConfig
section that route traffic to the backend using the dns name from Tailscaleservices.tailscale
enables Tailscale on the VPS and configures it as an exit node.sops
configures SOPS to securely store secrets
And that’s it on the VPS. For the backend, you could be running a variety of different options from Umbrel to Nix Bitcoin to the services manually configured on a variety of operating systems. Settings those up is best left to a different post, but the keys that relates to this setup are:
- that where ever they run uses the VPS as an exit node
- the services listen on for connections incoming via Tailscale
- the services advertise the IP of the VPS as their public address
Note: the links to Hetzner and Tailscale in this post are referral / affiliate links. The Hetzner one is mine and the Tailscale one is from Jupiter Broadcasting’s Linux Unplugged podcast. I’ve used the JB link because Chris Fisher, Alex Kretzschmar, Brent Gervais, & Wes Payne have taught me about much of what’s here through their podcasting.