# Reverse proxy to Tor

# Advantages

  • no port forwarding needed on the LAN of the host
  • encrypted connection
  • hides the IP of the host

# Requirements

  • a Virtual Private Server (VPS) - eg. a minimal package on Lunanode for ~3.5$/month
  • root access on the VPS - you need to set up webserver and install packages
  • a domain or subdomain - this will be setup on the proxy webserver

Get the Tor .onion address of your BTCPay Server via the Server settings > Services page. See information in the "HTTP-based TOR hidden services" section.

Note: There is also a Docker version of this setup.

# VPS Setup

You will create a nginx reverse proxy and a socat service, which forwards requests to your BTCPay Server.

Login as root and install the required dependencies: (example assumes a Debian based system)

# switch to root user (if not logged in as root)
sudo su -

# install dependencies
apt update
apt install -y certbot nginx socat tor

# Socat setup

Create the service file /etc/systemd/system/http-to-socks-proxy@.service:

Description=HTTP-to-SOCKS proxy

ExecStart=/usr/bin/socat tcp4-LISTEN:${LOCAL_PORT},reuseaddr,fork,keepalive,bind= SOCKS4A:${PROXY_HOST}:${REMOTE_HOST}:${REMOTE_PORT},socksport=${PROXY_PORT}


Create the configuration for the service in /etc/http-to-socks-proxy/btcpayserver.conf:

# create the directory
mkdir -p /etc/http-to-socks-proxy/

# create the file with the content below
nano /etc/http-to-socks-proxy/btcpayserver.conf

Replace the REMOTE_HOST and adapt the ports if needed:


Create a symlink in /etc/systemd/system/multi-user.target.wants to enable the service and start it:

# enable
ln -s /etc/systemd/system/http-to-socks-proxy\@.service /etc/systemd/system/multi-user.target.wants/http-to-socks-proxy\@btcpayserver.service

# start
systemctl start http-to-socks-proxy@btcpayserver

# check service status
systemctl status http-to-socks-proxy@btcpayserver

# check if tunnel is active
netstat -tulpn | grep socat
# should give something like this:
# tcp        0      0*               LISTEN      951/socat

# Webserver setup

# Point domain to the VPS

Create the A record on the DNS server of your domain/subdomain and point it to your VPS IP address.

# Prepare SSL and Let's Encrypt

# generate 4096 bit DH params to strengthen the security, may take a while
openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096

# create directory for Let's Encrypt files
mkdir -p /var/lib/letsencrypt/.well-known
chgrp www-data /var/lib/letsencrypt
chmod g+s /var/lib/letsencrypt

# nginx configuration: http

Create a variable mapping to forward the correct protocol setting and check if the Upgrade header is sent by the client, e.g. /etc/nginx/conf.d/map.conf:

map $http_x_forwarded_proto $proxy_x_forwarded_proto {
  default $http_x_forwarded_proto;
  ''      $scheme;

map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;

Create a config file for the domain, e.g. /etc/nginx/sites-available/btcpayserver.conf:

server {
  listen 80;
  server_name btcpayserver.mydomain.com;

  # Let's Encrypt verification requests
  location ^~ /.well-known/acme-challenge/ {
    allow all;
    root /var/lib/letsencrypt/;
    default_type "text/plain";
    try_files $uri =404;

  # Redirect everything else to https
  location / {
    return 301 https://$server_name$request_uri;

We will configure the https server part in the same config file once we obtained the SSL certificate.

Enable the web server config by creating a symlink and restarting nginx:

ln -s /etc/nginx/sites-available/btcpayserver.conf /etc/nginx/sites-enabled/btcpayserver.conf

systemctl restart nginx

# Obtain SSL certificate via Let's Encrypt

Run the following command with adapted email and domain parameters:

certbot certonly --agree-tos --email admin@mydomain.com --webroot -w /var/lib/letsencrypt/ -d btcpayserver.mydomain.com

# nginx configuration: https

Now that we have a valid SSL certificate, add the https server part at the end of /etc/nginx/sites-available/btcpayserver.conf:

server {
  listen 443 ssl http2;
  server_name btcpayserver.mydomain.com;

  # SSL settings
  ssl_stapling on;
  ssl_stapling_verify on;

  ssl_session_timeout 1d;
  ssl_session_cache shared:SSL:10m;
  ssl_session_tickets off;

  # Update this with the path of your certificate files
  ssl_certificate /etc/letsencrypt/live/btcpayserver.mydomain.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/btcpayserver.mydomain.com/privkey.pem;

  ssl_dhparam /etc/ssl/certs/dhparam.pem;
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_prefer_server_ciphers off;

  resolver valid=300s;
  resolver_timeout 30s;

  add_header Strict-Transport-Security "max-age=63072000" always;
  add_header Content-Security-Policy "frame-ancestors 'self';";
  add_header X-Content-Type-Options nosniff;

  # Proxy requests to the socat service
  location / {
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

Restart nginx once more:

systemctl restart nginx

Now, visiting btcpayserver.mydomain.com should show your BTCPay Server instance.


If you see an nginx error of "503 Service Temporarily Unavailable" or similar but your BTCPay Server is reachable otherwise, you need to make BTCPay Server aware of your new domain. You can do so by using environment variables (Docker based setup), log into your BTCPay Server via SSH:

sudo su -
cd $BTCPAY_BASE_DIRECTORY/btcpayserver-docker/
export BTCPAY_ADDITIONAL_HOSTS="btcpayserver.mydomain.com"
. ./btcpay-setup.sh -i

# Do all this in a Docker container

Ready made Docker image (opens new window) (Code (opens new window))

# SocaTor = SOCAT + TOR

Based on Docker-Socator (opens new window)

It uses socat to listen on a given TCP port (5000 in this example) and to redirect incoming traffic to a Tor hidden service specified through environment variables. It acts as a relay between the standard web and a hidden service on the Tor network. You can optionally restrict the IP addresses that are allowed to connect to this service by specifying an ALLOWED_RANGE environment variable and using CIDR notation.

Please note:

This container does not have any nginx component because Kubernetes provides for it.

# Usage

Break free from cloud services providers limitations, secure and protect your bitcoin full node, connect that with a BTC Pay server, all behind TOR. Selectively expose the BTCPay Server payment gateway and API to clearnet using socat+tor running on the Internet.

# Build

docker build -t cloudgenius/socator .

# Push

docker push cloudgenius/socator

# Start the image in background (daemon mode) with IP address restriction

docker run -d \
        -p 5000:5000 \
        -e "ALLOWED_RANGE=" \
        -e "TOR_SITE=zqktlwiuavvvqqt4ybvgvi7tyo4hjl5xgfuvpdf6otjiycgwqbym2qad.onion" \
        -e "TOR_SITE_PORT=80" \
        --name socator \

# Start the image in foreground

docker run --rm -ti \
        -p 5000:5000 \
        -e "TOR_SITE=zqktlwiuavvvqqt4ybvgvi7tyo4hjl5xgfuvpdf6otjiycgwqbym2qad.onion" \
        -e "TOR_SITE_PORT=80" \
        --name socator \

Now http://localhost:5000 should show you the tor hidden service you specified in the above command.

# Use that Docker container in a Kubernetes Cluster using these manifests

These manifest assumes a typical Kubernetes cluster that exposes internal services (like socator running internallly at port 5000) to the clearnet/public internet via Nginx Ingress (opens new window) and provide automated Let's Encrypt TLS/SSL certificates via cert-manager (opens new window).

Deployment manifest

apiVersion: apps/v1
kind: Deployment
  name: socator
  replicas: 1
      role: socator
        role: socator
        - image: cloudgenius/socator # code https://github.com/beacloudgenius/socator
          imagePullPolicy: IfNotPresent
          name: socator
            - name: TOR_SITE
              value: 'zqktlwiuavvvqqt4ybvgvi7tyo4hjl5xgfuvpdf6otjiycgwqbym2qad.onion' # BTCPay Server Tor address => docker exec tor cat /var/lib/tor/app-btcpay-server/hostname
            - name: TOR_SITE_PORT
              value: '80'

Service manifest

apiVersion: v1
kind: Service
  name: socator
    - name: http
      port: 5000
    role: socator

Ingress manifest

apiVersion: networking.k8s.io/v1
kind: Ingress
  name: socator
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
    - host: btcpayserver.mydomain.com
          - backend:
                name: socator
                  number: 5000
            path: /
            pathType: Prefix
    - hosts:
        - btcpayserver.mydomain.com
      secretName: socator-tls

# Resources