Client operations
Run blindportd
Connect each paid endpoint to a local service on one Linux host.
Install
On Linux amd64, arm64, or armv7, install the current agent with one command:
curl -fsSL https://blindport.com/downloads/install.sh | sh
The installer detects your Linux architecture, downloads the current binary and checksum from this site, and verifies SHA-256. As a normal user it installs blindportd to $HOME/.local/bin and adds that directory to your shell profile when needed. When run as root, it installs to /usr/local/bin.
Manual download and trust details
GitHub Actions builds versioned static Linux binaries and publishes them through GitHub Releases. The hosted copies are:
sha256sum -c blindportd-c58e51d-linux-amd64.sha256
sudo install -m 0755 blindportd-c58e51d-linux-amd64 /usr/local/bin/blindportd
blindportd -version
A successful checksum says OK and only detects transfer corruption. It does not authenticate the CI-built binary. Do not rename the binary until after checking it because the checksum file contains the release filename.
If GitHub Actions is outside your trust boundary, verify the pinned fingerprint and signed source tag and commit as described in the self-hosting guide, then build the checked-out source locally:
cd go
CGO_ENABLED=0 go build -trimpath \
-ldflags "-s -w -X main.version=c58e51d" \
-o ../blindportd ./cmd/blindportd
cd ..
sudo install -m 0755 blindportd /usr/local/bin/blindportd
The GPG signature authenticates the source history, not GitHub-built artifacts. Primary release-signing key fingerprint: 18ED E472 6C14 1484 4923 D6FF 14EA BFF7 39C1 6205.
Configure local services
Open Connect your service in the dashboard. For every active endpoint, enter the local host:port where your app listens. A common local target is 127.0.0.1:8080.
Relay uses automatic HTTPS by default. Review and accept the linked Let's Encrypt Subscriber Agreement in the dashboard, then install the generated config at $HOME/.config/blindport/config.json. Port and framed IP mappings pass traffic through to the local target without changing its protocol.
{
"version": 3,
"accounts": [
{
"name": "public",
"token_file": "/home/blindport/.config/blindport/tokens/public",
"state_dir": "/home/blindport/.local/state/blindport-public",
"mappings": [
{
"subscription_id": "12312312-3123-4123-8123-123123123123",
"upstream": "127.0.0.1:8080",
"tls_mode": "automatic",
"acme_terms_accepted": true
},
{
"subscription_id": "45645645-6456-4456-8456-456456456456",
"upstream": "127.0.0.1:9000",
"tls_mode": "passthrough"
}
]
}
]
}
The config and its absolute token_file must be regular owner-only files with mode 0600, not symlinks. Each account needs a distinct, non-overlapping absolute state_dir. To add accounts, add another named object with its own token file, state directory, and mappings. Mapping IDs are canonical, unique UUIDv4 values shown in the dashboard. Bracket IPv6 targets, for example [::1]:8080.
Start a persistent user service
After installing the config, run:
export PATH="$HOME/.local/bin:$PATH"
blindportd -install-user-service
The command asks for each missing configured account token without displaying it, stores each at its owner-only token_file, prepares the separate state directories, validates the config, and enables and starts blindportd.service for your user.
systemctl --user status blindportd.service
journalctl --user -u blindportd.service -f
To start the service at boot and keep it running while you are logged out, enable lingering. This policy may require administrator approval:
loginctl enable-linger "$USER"
Update the agent
curl -fsSL https://blindport.com/downloads/install.sh | sh
blindportd -version
systemctl --user restart blindportd.service
The installer replaces only the binary. Keep the existing config, account token files, and configured state directories so client identities and certificates remain available.
Run in the foreground
For a temporary session, use the same config without installing the user service:
export PATH="$HOME/.local/bin:$PATH"
blindportd -config="$HOME/.config/blindport/config.json"
Create every configured owner-only token file first, or run the user-service installer once to prompt for missing tokens. Stop the foreground process with Ctrl+C. Restart it after changing the static config.
Docker
Connect an existing paid Relay subscription directly to an app container. This version 3 example uses the same config.json filename as the standalone setup and reads an owner-only token file rather than putting a bearer token in Compose. Replace the subscription UUID, set .upstream to the app's Compose service name and internal port, and accept the Let's Encrypt Subscriber Agreement before setting the consent label to true.
{
"version": 3,
"accounts": [
{
"name": "public",
"token_file": "/run/secrets/blindport-public"
}
]
}
Save this as config.json. mappings is omitted because --docker reads mappings from container labels. If a mapping object is placed in config.json, it is static and must define its own subscription_id and upstream. An omitted state_dir defaults to /var/lib/blindport/accounts/<account-name> in the image. With one configured account, labels use it automatically; only multi-account setups need an .account label.
sudo install -d -o 10001 -g 10001 -m 0700 /opt/blindport/config /opt/blindport/secrets /opt/blindport/state
sudo install -o 10001 -g 10001 -m 0600 config.json /opt/blindport/config/config.json
sudo install -o 10001 -g 10001 -m 0600 /dev/null /opt/blindport/secrets/public-token
sudoedit /opt/blindport/secrets/public-token
The image runs as UID/GID 10001:10001. Config and token files should use mode 0600. Config, secrets, and state directories must use mode 0700; mode 0600 on a directory omits the execute bit required to reach files inside it. Bind each token file separately as shown below. Do not bind the whole host secrets directory onto /run/secrets, because its ownership and mode replace the container directory and can prevent traversal. A readable token with broader permissions produces a warning and continues, while an inaccessible token remains a startup error.
services:
app:
image: example/app:latest
networks: [blindport]
labels:
tech.blindport.mapping.web.subscription: "12312312-3123-4123-8123-123123123123"
tech.blindport.mapping.web.upstream: "app:8080"
tech.blindport.mapping.web.tls_mode: "automatic"
tech.blindport.mapping.web.acme_terms_accepted: "true"
blindportd:
image: ghcr.io/blindport/blindportd:c58e51d
container_name: blindportd
init: true
restart: unless-stopped
command: ["--docker", "--config=/etc/blindport/config.json"]
group_add: ["${DOCKER_GID:-999}"]
networks:
blindport:
ipv4_address: 172.30.0.2
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /opt/blindport/config/config.json:/etc/blindport/config.json:ro
- /opt/blindport/secrets/public-token:/run/secrets/blindport-public:ro
- /opt/blindport/state:/var/lib/blindport
read_only: true
cap_drop: [ALL]
cap_add: [NET_ADMIN]
security_opt: ["no-new-privileges:true"]
tmpfs: ["/tmp:size=16m,mode=1777"]
networks:
blindport:
name: blindport
ipam:
config:
- subnet: 172.30.0.0/24
app and blindportd share the blindport network, so app:8080 resolves directly. The bind-mounted /opt/blindport/state directory preserves the enrolled identity, ACME account, and certificates. Docker group ID 999 is the default; override DOCKER_GID with stat -c '%g' /var/run/docker.sock when the host socket uses another group. The published v0.3.0 image requires NET_ADMIN at process startup because the executable carries that file capability; cap_drop: [ALL] ensures no other capability is granted. Version 3 config requires blindportd v0.3.0 or newer. A single configured account is selected automatically; multi-account mappings must select one with .account.
Advanced: create orders from labels
Instead of .subscription, a mapping can declare .product. Every declaration requires .upstream, and multi-account setups also require .account. Its mapping name becomes the stable, account-scoped order key: changing that name's product, domain, transport, or billing term is rejected, so use a new name for a different order. Relay requires .domain; Port supports .transport values tcp and udp. Omit .transport for TCP and .billing_term for monthly billing. Relay uses TCP only.
# Relay order, with a required domain
tech.blindport.mapping.web.account: "public"
tech.blindport.mapping.web.product: "relay"
tech.blindport.mapping.web.domain: "web.relay.blindport.com"
tech.blindport.mapping.web.billing_term: "monthly"
tech.blindport.mapping.web.upstream: "app:8080"
tech.blindport.mapping.web.tls_mode: "automatic"
tech.blindport.mapping.web.acme_terms_accepted: "true"
# Port order, TCP by default, or set transport to udp
tech.blindport.mapping.game.account: "public"
tech.blindport.mapping.game.product: "port"
tech.blindport.mapping.game.transport: "udp"
tech.blindport.mapping.game.billing_term: "yearly"
tech.blindport.mapping.game.upstream: "game:27015"
Routed Blindport IP cannot be declared with Docker labels because WireGuard delivery has no upstream mapping. Run it with a separate blindportd -wireguard process.
With NWC configured for the token's account, an eligible declaration starts one initial wallet payment. payment_pending means that payment is awaiting settlement or reconciliation, not that the endpoint is active. Without NWC, the order remains awaiting_payment until you pay it in the dashboard.
Managed Relay names can proceed to payment. A customer-owned Relay domain first returns awaiting_domain; follow the dashboard DNS instructions and wait for verification before a payment is created or attempted. An exact-name order requires its exact DNS-only CNAME. A wildcard order requires only its TXT ownership challenge for payment; add its wildcard CNAME when ready to route traffic.
Removing labels stops local forwarding but does not cancel, refund, or otherwise end a paid subscription.
Source and support
Review the source code, open an issue, or email support@blindport.com. Send security reports only as GPG-encrypted email to security@blindport.com.
Automatic HTTPS
Automatic is the default for Relay. After you explicitly accept the Let's Encrypt agreement, blindportd obtains and renews the certificate, handles HTTPS locally, and sends plaintext traffic to your configured target such as 127.0.0.1:8080. It does not need root access or a local public port.
Keep $HOME/.local/state/blindport private and persistent because it contains the ACME account and certificate keys. Automatic HTTPS applies only to Relay mappings.
Advanced: TLS passthrough
Set "tls_mode": "passthrough" when your local server must own its certificate and terminate TLS. Point upstream to that TLS listener. If it uses HTTP-01, add a separate plaintext http_challenge_upstream. Do not set acme_terms_accepted in passthrough mode.
An exact customer-owned Relay name uses a direct CNAME and can use automatic HTTPS. A wildcard Relay subscription accepts a customer-owned base domain without *., verifies ownership with the dashboard TXT challenge, and its price includes the base plus *.base. Add the displayed wildcard CNAME in DNS-only mode when ready to route traffic; it is not required for payment and does not match the base itself. Pointing the base separately to the same relay target is optional. Use a standard CNAME for a subdomain base. At a zone apex, mandatory NS and SOA records prevent a conventional CNAME; use the authoritative DNS service's ALIAS, ANAME, or CNAME-flattening feature, which normally returns synthesized A and/or AAAA answers. Neither routing record is verified for payment. Wildcard Relay requires TLS passthrough; when a base, including a zone apex, is pointed, the local TLS listener and certificate must serve both the base and descendants.
Routed WireGuard mode
Prefer framed Relay or Port for a domain or individual TCP/UDP service because it does not require host network privileges. Choose WireGuard only when a Blindport IP workload needs the complete routed public /32, native UDP or ICMP semantics, arbitrary ports, or other IPv4 protocols. It is separate from framed mappings and Docker discovery.
Run the agent with network administration privileges. Its first root run prompts for the token and creates private root-owned token and identity paths:
sudo blindportd -wireguard
The host needs Linux kernel WireGuard and CAP_NET_ADMIN or equivalent privileges. The agent creates bpwg0, assigns the leased public /32, and installs source policy routing without replacing the host's main default route. TCP, UDP, ICMP, and other IPv4 packets route directly without framed stream or datagram encapsulation. Your application must listen on the assigned address or a wildcard socket; routed mode does not dial an upstream.
Run routed WireGuard in a container
Use a separate agent process from Docker discovery. It requires an active annual WireGuard IP subscription, host networking, a root-owned owner-only token file, persistent private state, and only the additional NET_ADMIN capability. Do not mount the Docker socket or add mapping labels.
services:
blindport-wireguard:
image: ghcr.io/blindport/blindportd:c58e51d
user: "0:0"
network_mode: host
init: true
restart: unless-stopped
command: ["--wireguard", "--token-file=/run/blindport/token", "--state-dir=/var/lib/blindport"]
volumes:
- ./secrets/wireguard-token:/run/blindport/token:ro
- blindport-wireguard-state:/var/lib/blindport
read_only: true
cap_drop: [ALL]
cap_add: [NET_ADMIN]
security_opt: ["no-new-privileges:true"]
tmpfs: ["/tmp:size=16m,mode=1777"]
volumes:
blindport-wireguard-state:
Tor SOCKS5 transport
Route the agent's framed backend and relay connections through a local Tor listener:
blindportd -socks5=127.0.0.1:9050 -config="$HOME/.config/blindport/config.json"
This mode fails closed if the proxy is unavailable and cannot be combined with WireGuard. It changes the agent's outbound network path; it does not make Blindport an anonymity service.
Share a referral link
Anyone with a Lightning Address can refer customers. You do not need a Blindport account, registration, or a wallet connection to create a link.
- Open the referral link generator on the home page and enter your own Lightning Address, such as
alice@coinos.io. - Copy the generated link and share it. Its format is
https://blindport.com/#ref=alice%40coinos.io. The address identifies the destination for your referral payouts. - A visitor opens the link and creates a new subscription. The first valid referral in that browser tab's session wins. Later links in the same session do not replace it. The subscription keeps that referral for renewals; existing subscriptions are not reassigned.
The current commission is 10% of the service amount actually paid after discounts, excluding stablecoin surcharges and provider-minimum top-ups, rounded down to whole sats. Blindport sets the rate and records it when each invoice is created. A link cannot change the rate or the customer's price. Future invoices may use a different published rate.
The customer pays one normal merchant invoice. Your credit is recorded only after Blindport verifies settlement and activates or renews the subscription. Unpaid, failed, expired, fully credited, or review-required payments earn no automatic commission.
Credits accumulate by Lightning Address. At 10,000 sats of available credit, an operator can reserve a payout and pay it manually using a separate service. Reaching the threshold does not cause an instant payment, and there is no fixed payout schedule. A reserved payout stays reserved while any uncertain result is investigated.
Use an address you control and keep it working. Blindport checks its format only; it does not check ownership, reachability, or whether it can receive payment when you create a link. There is no referrer account or public balance lookup. For payout questions, contact support with the address. See the referral terms for attribution and privacy limits.
Troubleshooting
no token- Run the native agent once in a terminal to enter it. Docker version 3 uses one mounted owner-only
token_fileper configured account. - Subscription is missing
- Confirm payment is settled and the subscription is active. Docker mode will discover it on the next poll; static and legacy modes still require a restart.
- Domain payment is unavailable
- For a customer domain, publish the one exact CNAME name and target from the dashboard and run the DNS check before creating an invoice.
- TLS certificate fails
- For automatic HTTPS, confirm the terms checkbox is accepted, the Relay subscription is active, and the state directory is writable. For passthrough, confirm the local TLS listener serves the ordered hostname and complete certificate chain.
- WireGuard setup fails
- Confirm Linux WireGuard support,
CAP_NET_ADMIN, an active routed Blindport IP order, and no conflicts with interface, table, or rule-priority values.
Current limitations
- Docker and active framed provisioning changes can take up to one configured poll interval. Static configuration and routed WireGuard changes require a restart.
- One production client identity is enrolled per account. Docker labels can initiate one idempotent order and initial NWC payment, but cancellation and renewal policy remain account actions.
- Established TCP sessions do not migrate across relay reconnects or DNS changes.
- Relay and Port use two provider edges for new-connection resilience, without an availability guarantee. DNS is not health steering and may continue returning a failed edge.
- A routed dedicated IP belongs to one provider and is unavailable during that provider's outage. The hosted website and control plane are not highly available.
- A previously issued signed Relay or Port authorization may remain usable through its bounded grace timestamp while the control plane is unreachable. Grace does not extend the paid service term, and a successful online denial removes the connection.
- Framed UDP crosses the TCP/mTLS tunnel and may experience head-of-line blocking.
- Routed mode supports Linux IPv4 /32 leases only. It does not manage host firewalls, application listeners, IPv6, or DNS.