# Networking
The normal deployment publishes Nginx on ports 80 and 443. Nginx routes the configured hosts to BTCPay Server and optional services, while the ACME companion obtains HTTPS certificates.
# Public HTTPS
For automatic HTTPS:
- Point the DNS records for
BTCPAY_HOSTat the server. - Allow incoming TCP traffic on ports 80 and 443.
- Keep
BTCPAYGEN_REVERSEPROXY=nginx. - Optionally set
LETSENCRYPT_EMAILfor expiry notifications.
Port 80 must be reachable for the default ACME HTTP challenge. Changing
REVERSEPROXY_HTTP_PORT prevents direct validation unless another network
device forwards public port 80 to it.
BTCPAY_ADDITIONAL_HOSTS accepts comma-separated hostnames. By default,
certificates are requested for the primary (BTCPAY_HOST) and additional hosts (BTCPAY_ADDITIONAL_HOSTS). Set
BTCPAY_LETSENCRYPT_HOSTS to a comma-separated subset, or explicitly set it to
an empty string to disable certificate requests.
# External Reverse Proxy
To terminate HTTPS on an existing reverse proxy, keep the bundled Nginx for BTCPay's internal routing but disable its HTTPS companion:
export BTCPAYGEN_REVERSEPROXY="nginx"
export BTCPAY_HOST="btcpay.example.com"
export BTCPAY_PROTOCOL="https"
export TRUST_DOWNSTREAM_PROXY="true"
. ./btcpay-setup.sh -i
# Disable the HTTPS companion after setup saves the proxy settings above.
btcpay-fragments exclude nginx-https
The external proxy must preserve the original host and HTTPS scheme so BTCPay
generates correct URLs. Replace BTCPAY_SERVER_IP with an address through which
the external proxy can reach the BTCPay host. The examples below assume the
certificate is already provisioned on the external server.
If the external proxy runs on the BTCPay host, set
REVERSEPROXY_HTTP_PORT=10080 and use port 10080 instead of 80 below.
# Nginx
# Add this map once inside the http block, outside any server block.
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name btcpay.example.com;
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name btcpay.example.com;
ssl_certificate /etc/letsencrypt/live/btcpay.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/btcpay.example.com/privkey.pem;
client_max_body_size 100M;
# Some signing workflows use large request and response headers.
client_header_buffer_size 500k;
large_client_header_buffers 4 500k;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
location / {
proxy_pass http://BTCPAY_SERVER_IP:80;
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Proxy "";
}
}
Replace the hostname and certificate paths, then validate and reload Nginx:
sudo nginx -t && sudo systemctl reload nginx
# Apache
Enable the required modules on Debian-based systems:
sudo a2enmod headers proxy proxy_http ssl
Apache 2.4.47 or later can proxy HTTP and WebSocket traffic through
mod_proxy_http:
<VirtualHost *:80>
ServerName btcpay.example.com
Redirect permanent / https://btcpay.example.com/
</VirtualHost>
<VirtualHost *:443>
ServerName btcpay.example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/btcpay.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/btcpay.example.com/privkey.pem
ProxyRequests Off
ProxyPreserveHost On
ProxyAddHeaders On
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port "443"
RequestHeader unset Proxy early
ProxyPass / http://BTCPAY_SERVER_IP:80/ upgrade=websocket
ProxyPassReverse / http://BTCPAY_SERVER_IP:80/
LimitRequestBody 104857600
# Some signing workflows use large request headers.
LimitRequestLine 500000
LimitRequestFieldSize 500000
</VirtualHost>
Replace the hostname and certificate paths, then validate and reload Apache:
sudo apachectl configtest && sudo systemctl reload apache2
Firewall Nginx's HTTP port so only the external proxy can connect. This
restriction is required because TRUST_DOWNSTREAM_PROXY=true accepts incoming
X-Forwarded-* headers as authoritative. Never expose this unencrypted,
trusted backend port publicly.
# Cloudflare Tunnel
The opt-add-cloudflared fragment exposes bundled Nginx through a token-based
Cloudflare Tunnel without opening inbound ports. Cloudflare terminates and can
observe or modify the traffic. Follow the Cloudflare Tunnel guide
for setup.
# Lightning Ports
Bitcoin CLN and LND publish host TCP port 9735 for peer connections. Groestlcoin CLN and LND publish host TCP port 9736. Bitcoin CLN also exposes container port 9736 internally for gRPC, but does not publish it on the host. Phoenixd does not expose a public peer-listening port through this stack. Open only the host ports required by the implementation you select.
# Optional Nginx Routes
Fragments declare required routes and optional routes. Required routes are
enabled automatically. Manage optional routes with btcpay-routes:
btcpay-routes show
btcpay-routes add lnd-rest
btcpay-routes add lnd-grpc
btcpay-routes add clightning-rest
btcpay-routes remove lnd-rest
The command returns JSON for show, add, and remove. Enabled routes are
stored as relative symlinks under nginx/enabled-routes and synchronized during
generation. Changes validate and reload a running Nginx container; a failed
validation or reload is rolled back.
# LND REST and gRPC APIs
The lnd-rest and lnd-grpc routes are available when Bitcoin LND and the
bundled Nginx reverse proxy are selected. They are disabled by default. No
additional Compose fragment or host port is required.
Enable only the API required by the external client:
btcpay-routes add lnd-rest
btcpay-routes add lnd-grpc
To enable or disable both together:
btcpay-routes add lnd-rest lnd-grpc
btcpay-routes remove lnd-rest lnd-grpc
Use btcpay-routes show to check which routes are enabled. With
BTCPAY_HOST=btcpay.example.com, the public endpoints are:
- REST:
https://btcpay.example.com/lnd-rest/btc/ - gRPC:
btcpay.example.com:443with TLS
After Bitcoin is synchronized, open Server Settings > Services in BTCPay
Server and select LND (REST) or LND (gRPC) for the endpoint, macaroons,
and temporary QR-code configuration. REST clients send the macaroon in the
Grpc-Metadata-macaroon header; gRPC clients use the macaroon metadata key.
The public endpoint uses the HTTPS certificate for BTCPAY_HOST, not LND's
internal TLS certificate.
Macaroons grant control over the Lightning node. Use the least-privileged macaroon supported by the client, protect it as a secret, and avoid distributing the admin macaroon unless full node control is required. Nginx blocks LND's unauthenticated wallet creation, unlocking, password-change, and state methods. Other calls still require an appropriate macaroon.
These routes do not publish LND's internal ports 8080 or 10009 on the host;
traffic passes through the configured HTTPS port, normally 443.
If another reverse proxy is placed in front of the bundled Nginx, it must also forward the enabled route. The REST route uses regular HTTPS forwarding. The gRPC route requires the external proxy to support HTTP/2 gRPC forwarding; the generic external Nginx and Apache examples above only configure HTTP and WebSocket forwarding.
# Unsafe Exposures
Internal Compose expose entries are not host-published ports. Fragments named
*-expose deliberately bind additional RPC endpoints, generally to host
loopback. opt-expose-unsafe publishes Bitcoin P2P port 8333 and must be used
only on a trusted LAN or behind firewall rules that allow known peers.