# 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 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 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 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/mydomain.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/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 mydomain.com should show your BTCPay Server instance.

# 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