Setting up an NGINX Reverse Proxy with SSL for Umbrel/BTCPay Server

Want to securely access BTCPay Server on your Umbrel node via a custom domain? This (potentially outdated - see update note in post!) guide details how I set up an NGINX reverse proxy with a free Let's Encrypt SSL certificate back then. Step-by-step instructions cover DNS, port forwarding, NGINX config, and Certbot.

Run your personal Bitcoin and Lightning Network node, self-host open source apps, cut out the middlemen, and use Bitcoin to its full potential. For free. — Umbrel

Update (May 23rd, 2023): This guide has been popular for nearly two years! However, Umbrel has changed significantly since it was written. People have informed me that these instructions are no longer directly compatible. Please use this guide for general direction and understanding the concepts, but do not follow it as exact step-by-step instructions for current Umbrel versions.

Running BTCPay Server on your Umbrel node is fantastic, but accessing it securely from outside your home network using a custom domain requires a bit more setup. This guide walks through how I configured NGINX as a reverse proxy along with a free Let's Encrypt SSL certificate to achieve just that.

Prerequisites

Before we start, make sure you have:

  • A fully installed and running Umbrel node.
  • The BTCPayServer app installed and enabled via the Umbrel UI.
  • A domain name you own and can manage DNS for.

For the examples below, I'll use these placeholder values (replace them with your own!):

  • Your home's public IP: 100.100.100.100
  • Umbrel's internal IP: 10.10.10.10
  • Your domain name: jorijn.com
  • The desired subdomain for BTCPay Server: btcpay.jorijn.com

Step 1: Pointing your domain to your home IP

Head over to your domain registrar's control panel or DNS provider. You need to edit the DNS zone for your domain (jorijn.com in my example) and add a new A record. This record should point your chosen subdomain (btcpay part) to your home's public IP address (100.100.100.100).

Screenshot of a typical DNS editor showing an A record (Example: Record Type: A, Name/Host: btcpay, Value/Points to: 100.100.100.100)

Important: DNS changes can take time to propagate across the internet – sometimes up to 24 hours, though often much faster. Only proceed once you can confirm the change is live globally.

Step 2: Verifying the DNS change

You can use an online tool like dnschecker.org to see if your new A record has propagated.

Simply enter your full BTCPay Server domain (e.g., btcpay.jorijn.com), select 'A' record type, and hit Search. You should see your home IP address appearing across most locations.

https://dnschecker.org/#A/btcpay.jorijn.com

Step 3: Setting up port forwarding on your router

To allow external access for issuing the SSL certificate (Let's Encrypt) and later for accessing BTCPay Server, we need to forward specific ports from your internet router to your Umbrel node's internal IP address. The exact steps depend heavily on your router's make and model – consult its manual or interface.

First, find your Umbrel's internal IP. You can usually find this in your router's connected devices list, or by logging into Umbrel via SSH and running a command like ip addr show wlan0 (if on WiFi) or ip addr show eth0 (if wired).

umbrel@umbrel:~ $ ip addr show wlan0
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether dc:a6:32:a4:91:8c brd ff:ff:ff:ff:ff:ff
    inet **10.10.10.10**/24 brd 192.168.121.255 scope global dynamic noprefixroute wlan0
    # [...]

In this example, the internal IP is 10.10.10.10.

Now, create two port forwarding rules in your router settings:

  1. HTTP Traffic for NGINX & Let's Encrypt:
    • Name: NGINX HTTP (or similar)
    • Forward external port 80 (standard HTTP) to Umbrel IP 10.10.10.10 on internal port 15080.
    • Protocol: TCP
  2. HTTPS Traffic for NGINX & Let's Encrypt:
    • Name: NGINX HTTPS (or similar)
    • Forward external port 443 (standard HTTPS) to Umbrel IP 10.10.10.10 on internal port 15443.
    • Protocol: TCP

(Why these internal ports? We'll configure NGINX to listen on 15080 and 15443 later to avoid conflicts with Umbrel's main web server which already uses 80 and 443 internally).

Step 4: Installing NGINX & Certbot on Umbrel

Now, let's SSH into your Umbrel node again. First, it's always a good idea to update the package list:

umbrel@umbrel:~ $ sudo apt update
# ... (output omitted) ...

Next, install NGINX (the reverse proxy) and Certbot (for Let's Encrypt SSL certificates) along with their dependencies:

umbrel@umbrel:~ $ sudo apt install python3-acme python3-certbot python3-mock python3-openssl python3-pkg-resources python3-pyparsing python3-zope.interface python3-certbot-nginx nginx
# ... (output omitted) ...

Don't worry if the installation seems to fail or hang at the end! This is expected because Umbrel's main web server is already using port 80 internally, which NGINX tries to bind to by default.

We need to tell NGINX to use a different default port. Edit the default NGINX configuration file:

umbrel@umbrel:~ $ sudo sed -i 's/80 default_server/15080 default_server/g' /etc/nginx/sites-available/default

(This command replaces 80 default_server with 15080 default_server in the file).

Now, finish the installation process (this usually fixes dependencies and completes the setup):

umbrel@umbrel:~ $ sudo apt install -f

After this, you should be able to access the default NGINX welcome page by Browse to your Umbrel's internal IP on port 15080, e.g., http://10.10.10.10:15080/.

Step 5: Creating the NGINX configuration for BTCPay Server

We need to tell NGINX how to handle requests for your btcpay.jorijn.com domain and pass them through to the internal BTCPay Server application running on Umbrel (which typically listens on port 3003).

Create a new NGINX configuration file specifically for your BTCPay site:

umbrel@umbrel:~ $ sudo nano /etc/nginx/sites-available/btcpay

Paste the following configuration into the editor. Make sure to replace btcpay.jorijn.com with your actual domain!

# Set higher buffer sizes for BTCPay Server potentially large headers/requests
proxy_buffer_size          128k;
proxy_buffers              4 256k;
proxy_busy_buffers_size    256k;
client_header_buffer_size 500k;
large_client_header_buffers 4 500k;
http2_max_field_size       500k;
http2_max_header_size      500k;

# Required for WebSocket support (used by BTCPay)
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    # IMPORTANT: Change this to your domain
    server_name btcpay.jorijn.com;

    location / {
        # Forward requests to the internal BTCPay Server app
        proxy_pass [http://127.0.0.1:3003](http://127.0.0.1:3003);

        # Set headers to pass correct information to BTCPay
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket headers
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    # Listen on the internal port we forwarded earlier (for HTTP)
    listen 15080;
    listen [::]:15080; # Also listen on IPv6

    # SSL configuration will be added here by Certbot later
}

Save the file (CTRL+O in nano, then Enter) and exit the editor (CTRL+X).

Now, enable this new configuration by creating a symbolic link:

umbrel@umbrel:~ $ sudo ln -s /etc/nginx/sites-available/btcpay /etc/nginx/sites-enabled/

Test that your NGINX configuration syntax is still correct:

umbrel@umbrel:~ $ sudo nginx -t
# Expect output like: nginx: configuration file /etc/nginx/nginx.conf test is successful

If the test is successful, reload NGINX to apply the new configuration:

umbrel@umbrel:~ $ sudo systemctl reload nginx.service

Step 6: Requesting your SSL certificate with Certbot

Now we use Certbot to automatically obtain a free SSL certificate from Let's Encrypt and configure NGINX to use it for HTTPS.

Run the following command, replacing btcpay.jorijn.com with your domain and jorijn@jorijn.com with your email address (Let's Encrypt uses this for renewal reminders).

umbrel@umbrel:~ $ sudo certbot --nginx -d btcpay.jorijn.com -m jorijn@jorijn.com --agree-tos --no-eff-email --tls-sni-01-port 15443 --http-01-port 15080
  • --nginx: Use the NGINX plugin to automatically configure NGINX.
  • -d: Specify the domain name.
  • -m: Your email address.
  • --agree-tos: Agree to the Let's Encrypt Terms of Service.
  • --no-eff-email: Optional: Avoid subscribing to the EFF newsletter.
  • --tls-sni-01-port 15443: Tell Certbot to perform validation challenges via the internal HTTPS port we forwarded earlier.
  • --http-01-port 15080: Tell Certbot to use the internal HTTP port for validation if needed.

Certbot will likely ask if you want to redirect HTTP traffic to HTTPS. For now, choose option 1: No redirect. We will configure this manually in the next step for better control.

If successful, Certbot will tell you it has deployed the certificate and updated your NGINX configuration.

Step 7: Manually adding the HTTP-to-HTTPS redirect

To ensure all visitors use the secure HTTPS connection, we'll manually add a redirect rule. This forces any traffic hitting the non-secure port 15080 for your domain to be redirected to the secure port 15443 (which your router forwards from 443).

Open your BTCPay NGINX configuration file again:

umbrel@umbrel:~ $ sudo nano /etc/nginx/sites-available/btcpay

Certbot should have added lines related to SSL and listening on port 15443 inside the original server { ... } block. We need to add a new server block below the existing one to handle the redirect.

Paste the following block at the end of the file. Remember to replace btcpay.jorijn.com twice with your domain!


# This new server block handles the redirect
server {
    listen 15080;
    listen [::]:15080;

    # IMPORTANT: Change this to your domain
    server_name btcpay.jorijn.com;

    # If the host matches, permanently redirect to HTTPS
    if ($host = btcpay.jorijn.com) {
        return 301 https://$host$request_uri;
    }

    # Return 404 for other requests to this port (optional but good practice)
    return 404;
}

Save the file (CTRL+O, Enter) and exit (CTRL+X).

Finally, test the configuration again and reload NGINX:

umbrel@umbrel:~ $ sudo nginx -t
# Expect success message
umbrel@umbrel:~ $ sudo systemctl reload nginx.service

That's it! Your BTCPay Server should now be securely accessible via HTTPS at your custom domain, e.g., https://btcpay.jorijn.com/.

The Pay Button and Beyond

Now that NGINX is handling the domain and SSL, BTCPay Server should automatically generate the correct embed codes (like the Pay Button code) using your https://btcpay.jorijn.com domain. You can grab these from your BTCPay Server interface and integrate them into your website.

Hopefully, this guide helps you get your own secure setup running! Remember this guide might be outdated for the latest Umbrel versions, so adapt as needed.