Nym Mixnet & dVPN: A Node Operator's Guide (2026)
Published by Weisser Zwerg Blog on
Support reclaiming our digital civil rights and privacy by running your own Nym node as a node operator.
Rationale
This post is a complete rework and 2026 update of my earlier guide: Nym Mixnet & dVPN: A Node Operator’s Guide.
This time, the setup is much smoother because Nym now provides dedicated operator tooling.
The main helper is the Nym Node CLI, which drives most of the installation and configuration steps for you.
If you want to inspect the code behind it, the tooling lives in the Nym repository as nym-node-cli.py.
The Nym mixnet and its distributed VPN (dVPN) depend on people like you to run nodes, which keep the network robust.
Nym’s blockchain-based incentives reward node operators with cryptocurrency, creating a potential small business opportunity.
If you want to learn about using the NymVPN as a user, please refer to my earlier blog post: Nym Mixnet: NymVPN.
If you’re eager to dive deeper, I recommend checking out the Nym Docs for more detailed information. In addition, you can find helpful resources and support at the following platforms:
- Nym Forum: A community-driven space to discuss Nym’s technology and get answers to your questions.
- Nym on Discord: Join the conversation and connect with other users and developers in real time.
- operators:nymtech.chat
- Nym on GitHub: Explore the code, report issues, and contribute to the project.
- Governator
- Spectre Explorer
- Nym Harbour Master
- Nymesis
- Nym Explorer V2
- Nym Node Status UI
The Result
Below you can see the outcome of this guide: the Nym node I set up for this 2026 post, and the node I set up in my earlier guide. The table links to several public dashboards so you can verify that both nodes are visible on the network.
These explorers and dashboards do not all show exactly the same information at the same time. Some focus on operator status and routing details, while others focus on explorer style listings and node metadata.
| weisser-zwerg.dev (wznymnode2.root.sx) | weisser-zwerg.dev (wznymnode.root.sx) | |
|---|---|---|
| Spectre Explorer | DBBCDYsgAAj7g4FLQkSxXZAcdG5m9Hx8vMreqRaX1Yqo | E67dRcrMNsEpNvRAxvFTkvMyqigTYpRWUYYPm25rDuGQ |
| Nym Harbour Master | DBBCDYsgAAj7g4FLQkSxXZAcdG5m9Hx8vMreqRaX1Yqo | E67dRcrMNsEpNvRAxvFTkvMyqigTYpRWUYYPm25rDuGQ |
| Nymesis | DBBCDYsgAAj7g4FLQkSxXZAcdG5m9Hx8vMreqRaX1Yqo | E67dRcrMNsEpNvRAxvFTkvMyqigTYpRWUYYPm25rDuGQ |
| Nym Explorer V2 | DBBCDYsgAAj7g4FLQkSxXZAcdG5m9Hx8vMreqRaX1Yqo | E67dRcrMNsEpNvRAxvFTkvMyqigTYpRWUYYPm25rDuGQ |
In the appendix, I will publish additional information about the newly created node. You can reproduce these details yourself via API calls as shown here: Investigating the Node via API.
Delegating
If you prefer not to run your own nym-node, you can still support the network by delegating your NYM tokens to an existing node, for example to my node.
But before you can delegate, you’ll need to acquire some NYM tokens.
Download the Nym Wallet: Visit the Nym Wallet website and download the wallet application for your operating system.
Then you can use the wallet to buy Nym tokens or you use one of the listed exchanges on CoinGecko markets: https://www.coingecko.com/en/coins/nym#markets.
While I haven’t personally used these services, you can buy
NYMtokens on centralized exchanges (CEX) like Kraken (on the native NYX network) or on decentralized exchanges (DEX) and CEX platforms like KuCoin or ByBit (on the ERC20 Ethereum network). However, if you purchase tokens on the ERC20 network, you’ll need to transfer them to the native NYX network via GravityBridge.
The guide How to get and stake NYM tokens guides you through the whole process.
Part of the process is to install the Keplr browser extension wallet for the Inter Blockchain Ecosystem.
Browse Nym nodes in the explorer and look for:
- Nodes with high routing scores (close to 100%)
- Check for lower operating costs/margins for better rewards
- Ensure the node isn’t over-saturated (below 100%)
Once you have chosen a node, open its page in the explorer. If you are connected with Keplr, you should see a Delegate button. You can use it to delegate to your selected node, including mine: DBBCDYsgAAj7g4FLQkSxXZAcdG5m9Hx8vMreqRaX1Yqo.
Different Readers and Different Levels of Prior Knowledge
Different readers have different levels of prior knowledge, so it is hard to write one guide that fits everyone. If the explanations below feel too short or too advanced for you, just copy the URL of this post into an AI assistant like ChatGPT and ask it to walk you through the sections you are not yet comfortable with. For example, you can ask it to explain unfamiliar concepts or show the same steps for a different Linux distribution.
Proof of Stake (PoS) and Proof of Work (PoW)
The Nym mixnet combines Proof of Stake (PoS) and Proof of Work (PoW) mechanisms:
- Proof of Stake: You’ll need to hold Nym tokens and bond them to your node. To maximize your node’s financial performance, you’ll also want to encourage others in the community to delegate some of their Nym tokens to your node. This delegation acts as a sign of trust and helps the network prioritize your node. However, the system is designed with balance in mind - nodes that hold too many delegated funds may see reduced rewards. This encourages a healthy, decentralized network. (For more details, check out the Nym Tokenomics documentation page.)
- Proof of Work: Your node must perform useful tasks, like participating in the Nym mixnet to ensure secure and private communication.
Steps to Set Up a Nym-Node:
- Get Nym Tokens: Acquire some Nym tokens to begin.
- Choose a VPS Provider: Research and pick a Virtual Private Server (VPS) provider that fits your needs.
- Install the Node: Set up your
nym-nodeon your VPS. - Bond Tokens: Bond 100 Nym tokens to your node (currently worth around $3.48 or €2.99).
- Build Trust: Convince others in the community to delegate their tokens to your node.
If running your own Nym node feels like too much of a commitment, you can still contribute to the network by delegating your Nym tokens to an existing node (for example, mine!). Detailed instructions for this will follow below.
Acquire Nym Tokens
To begin, you’ll need to get some Nym tokens. For detailed instructions, check out the Nym Wallet Preparation page.
Step 1: Set Up Your Nym Wallet:
- Download the Nym Wallet: Visit the Nym Wallet website and download the wallet application for your operating system.
- Create Your Wallet Account:
- If you don’t have an account, the wallet will guide you through creating one.
- You’ll receive a BIP 39 Mnemonic Word List - a unique set of 24 words that acts as your account identifier.
- Important: Store these 24 words securely, such as in a password manager. You’ll need them to log in and access your wallet in the future.
Step 2: Fund Your Wallet with NYM Tokens:
- To bond a node, you’ll need at least 100
NYMtokens. However, to cover gas fees, it’s recommended to have some more. I’d suggest a minimum of 200NYMtokens. - The Nym Wallet Preparation page links to a list of exchanges via CoinGecko markets, which is a good starting point: https://www.coingecko.com/en/coins/nym#markets
When you pick an exchange, confirm two practical points before you deposit money:
- You can withdraw
NYMto a wallet address you control.- The withdrawal is supported on the correct network (Nyx, the Cosmos SDK based Nym chain), not only as a wrapped token on another chain.
A good habit is to do one small test withdrawal first, then proceed with the full amount once you see it arrive in your Nym Wallet.
The Nym documentation suggests using the Bity broker, but I noticed that the CHF → NYM trading pair has been discontinued on Bity. A support request to the Bity support team confirmed that:
The CHF → NYM trading pair has been discontinued some time ago, which is why you now receive the message indicating that this pair is no longer supported. This explains the discrepancy between the current error and your previous purchase history, as NYM was available on Bity in the past. Unfortunately, it is no longer possible to place new orders for this pair via our platform.
Additional Note:
NYM is the native token of Nym’s own chain called Nyx, which is built with the Cosmos SDK.
The Nym project uses this blockchain for payment-related aspects of the mixnet rather than operating a separate blockchain.
Research and Select a Virtual Private Server (VPS) Provider
Choosing the right VPS provider is crucial for running a Nym node effectively. To help you make an informed decision, the Tor Project’s Good Bad ISPs page offers valuable advice that also applies to Nym nodes.
The Nym community also maintains an ISP table called Where to host your nym node?. It is community driven, so it gets better when operators share real experiences. If you discover a provider that works well for Nym nodes, consider adding your findings.
The Amnezia guide How to run your VPN also lists VPS providers.
If you want additional background reading, these two posts give a practical feel for how providers react to privacy infrastructure:
- Running a Tor Exit node/Mysterium Node in Linode. (24629) | Linode Questions, and
- Lars-Sören Steck: Euer Lars vom netcup-Team: Betreiben eines (Non-exit) Tor-Nodes.
The Delegations Program is run by the Nym team. Its goal is to help nodes get established in underserved parts of the world and to give new operators a stronger start. In practice, the program delegates stake to selected nodes. This can improve their chance of entering the network’s active set, so they can route traffic and earn rewards.
If you plan to apply, review the operational requirements. Two important references are the Minimum Requirements for VPS hardware and the expectation to support Network Decentralization.
According to the Nym blog post Nym node Delegation Programme is now open, preference is given to nodes that avoid heavily used providers such as AWS, Hetzner, Contabo, or Google Cloud. The goal is to avoid concentration, since concentration makes the network easier to disrupt and easier to observe.
There was also a post in the Nym Node Ops announcements channel about ISP MIGRATION. I only quote part of it here:
There has been continuous problems with Stark industries and its subsidiaries like PQ.hosting and TheHosting.
Nym Network has been, is and will be permissionless and the operators can run services of their choice. However, as a team we support operators via Delegation Program (DP) and Service Grant Program (SGP) and we would like to see operators looking for an alternative solution and moving away from the services provided by Stark Industries.
For better anonymity and network resilience, avoid VPS providers and countries that already host a large number of nodes. As of now, it’s recommended to steer clear of the following providers:
- Stark industries, PQ.hosting, and TheHosting.
- Frantech / Ponynet / BuyVM (AS53667)
- OVH SAS / OVHcloud (AS16276)
- Online S.A.S. / Scaleway (AS12876)
- Hetzner Online GmbH (AS24940)
- IONOS SE (AS8560)
- netcup GmbH (AS197540)
- Psychz Networks (AS40676)
- 1337 Services GmbH / RDP.sh (AS210558)
You can use the Nym Explorer V2 to identify areas with high node concentration. This helps you choose a location that contributes to a more diverse and balanced network.
One reason for dense node concentration in a few countries is simple economics: some regions have many VPS providers offering excellent value for money. As an operator, you can help the network by choosing a less common provider or a less common region.
In addition, if you plan to operate a Nym exit gateway, you should think carefully about the legal jurisdictions of your hosting provider and the country where your server is located. Exit gateways can attract more attention than other node types because they connect the Nym network to the public Internet. That makes provider policy, local law, and abuse handling procedures much more important.
Side note: If you receive an “Exit Gateways Abuse Report” from your ISP, the Nym project recommends starting from the Tor Project’s “Response template for Tor relay operator to ISP”.
When you receive an abuse report, use the Tor template for a quick response, but replace all occurrences of “tor” with “proxy server” before you send it. Nym is not Tor, and mentioning Tor can raise unnecessary suspicion with some providers. The Nym team has also mentioned they are working with lawyers on a Nym specific template.
Second, join this Matrix channel
!YfoUFsJjsXbWmijbPG:nymtech.chatand share as much as you can, for example screenshots, provider name, server location, and what kind of complaint it is.Last, use the community legal counsel as a shared knowledge base. Read https://nym.com/docs/operators/community-counsel and add your findings by opening a PR: https://nym.com/docs/operators/add-content.
There was also a relevant comment from the Node Operators Legal Forum:
It might be time to revive the Universal Privacy Alliance, which was originally intended to support privacy providers with legal issues.
After some research, I chose the VPS S plan from Avoro, which is part of dataforest GmbH in Germany. The plan costs EUR 5.50 per month and includes 4 vCPU, 8 GB RAM, and IPv4 plus IPv6 support. These specifications match the recommendations in the Nym operator guide and provide a comfortable baseline for a reliable node.
There are a few details worth knowing about Avoro’s current setup.
At the moment you may need to contact Avoro support to enable an IPv6 address, because the web interface does not always provide a self service option for this feature.
Also, the web interface may show a DNS name such as v0000000000.v-server.me, but that name can resolve to a different IP than the one displayed in the panel.
Avoro support clarified that this hostname is symbolic and not intended to be used as a working DNS record.
To handle this, I created my own DNS name and pointed it to the correct server IP. For that, I used FreeDNS[1], which is a simple service for hosting DNS records.
FreeDNS started in 2001 as a hobby project by Joshua Anderson and grew into a widely used service that offers both free and optional paid features.
In practical terms, FreeDNS can be used in two different ways:
- You can use FreeDNS as the authoritative DNS provider for a domain you own, by pointing your domain’s nameservers to FreeDNS and then managing your zone there.
- You can create a hostname under a shared domain from their public registry, meaning you pick an existing domain and create a subdomain under it.
The registry is a large list of shared domains contributed by FreeDNS members. When you open the “Registry” page, you will see many domains and an “owner” column.
Here is what the ownership model means in FreeDNS:
- The “owner” is the FreeDNS member who added that domain to the platform and controls its configuration inside FreeDNS.
- Some domains are “shared public”, meaning any FreeDNS user can create hostnames under that domain without prior approval from the owner.
- Some domains are “shared private”, meaning the owner can review and potentially reject hostnames.
- Some domains are “stealth”, meaning they are not shared publicly. Stealth is described as a premium option.
The main risk is this: if you create a hostname under a shared domain that you do not own, you depend on the domain owner and on FreeDNS keeping that domain active. If the owner removes the domain, changes sharing settings, or if the domain is disabled due to policy or abuse handling, your hostname may stop working. If you use a shared domain: treat it as “best effort”.
For the operating system, I selected Debian 13.3 (trixie). It is stable, widely supported, and commonly used in Nym operator guides.
Once your VPS is provisioned and you can log in via SSH, you are ready to proceed with installing the Nym node.
VPS Setup & Configuration
The Nym docs section on VPS Setup & Configuration is a good baseline for preparing your server. In the following snippet, I install the core tooling I rely on, confirm that time synchronization is working, and set the server locale.
apt update -y
apt --fix-broken install -y
# Install common admin tools and dependencies used throughout this guide
apt install -y --no-install-recommends \
ca-certificates curl wget jq tmux git rsync sqlite3 coreutils \
ufw netcat-openbsd ipset tcpdump \
pkg-config build-essential libssl-dev \
nginx certbot python3-certbot-nginx python-is-python3 \
tree emacs-nox
# Verify NTP via systemd-timesyncd
timedatectl show -p NTP -p NTPSynchronized -p NTPService
# Output should show:
# NTP=yes
# NTPSynchronized=yes
# If needed, enable time sync:
# systemctl enable --now systemd-timesyncd
# timedatectl set-ntp true
# timedatectl status
# timedatectl timesync-status
# Set your timezone (example)
# timedatectl list-timezones | grep -i zurich
# timedatectl set-timezone Europe/Zurich
# timedatectl show -p Timezone --value
# Confirm ufw is installed and available
ufw version
# Enable the locales you want and generate them
sed -i -e 's/^\s*#\s*\(en_US.UTF-8 UTF-8\)/\1/' -e 's/^\s*#\s*\(de_DE.UTF-8 UTF-8\)/\1/' /etc/locale.gen
locale-gen
I ran these steps twice by rebuilding the VPS from scratch. Because of that, I also restored the node identity from backups (see the backup section later in this post).
If you ever want to reinstall your node, or move it to another VPS, this is the point where you should restore the node identity, before you continue with node setup.
# After copying backup-restore-bundle.sh and a backup bundle (for example dotnym.2026-01-14-054215.bundle) to the new VPS, run: bash backup-restore-bundle.sh dotnym.2026-01-14-054215.bundleAfter a migration, remember to update your bonded node settings if anything that is publicly advertised has changed. The official docs explain this under Change Settings via Desktop Wallet. This is especially important if you move your node to a different IP address, change the DNS name, or change the HTTP port.
From here onwards there are two clean paths. Pick one and stick to it, so you do not mix assumptions from different installation styles.
- Path A: Use the new operator tooling end to end (
nym-node-cli.py) - Path B: Follow the manual docs and scripts (more control, more responsibility)
I will describe Path A, which uses the new operator tooling end to end via nym-node-cli.py.
This is the most consistent with the current direction of the operator docs
(one network-tunnel-manager.sh (NTM) + separate QUIC tool, orchestrated by CLI)
and the tooling update work referenced in the operator tooling revamp.
Here is what the rough outline
- Fresh VPS baseline
- Confirm IPv4 + IPv6 exist and route correctly (Nym expects both).
- Run
nym-node-cli.pyas root- The CLI fetches and runs:
nym-node-prereqs-install.sh(deps + UFW rules)nym-node-install.sh(node install/init)- systemd setup scripts
- and for exit-gateway, it also pulls NTM + QUIC script.
- The CLI fetches and runs:
- Let the prereqs script do the firewall
- It installs
ufw, enables it, and adds allow rules for:22/tcp,80/tcp,443/tcp1789/tcp,1790/tcp,8080/tcp,9000/tcp,9001/tcp51822/udp(WireGuard)- plus an allow rule on
nymwgfor51830/tcp(“bandwidth queries/topup - inside the tunnel”) - then
ufw reloadand shows status.
- Action for you: review/tighten these rules based on your actual mode and ports. The script is convenient but somewhat “broad.”
- It installs
- Start
nym-node, then bond- Nym’s routing config doc explicitly says: have the latest
nym-nodeinstalled, finish VPS setup, ensure the service is running, and bond the node before running NTM.
- Nym’s routing config doc explicitly says: have the latest
- Run NTM routing configuration
- WireGuard enabled:
complete_networking_configuration - WireGuard disabled:
nym_tunnel_setup
- WireGuard enabled:
- If WireGuard enabled: deploy QUIC bridge
- The changelog states QUIC bridge is required for nodes enabling WireGuard and provides the
quic_bridge_deployment.sh full_bridge_setupflow.
- The changelog states QUIC bridge is required for nodes enabling WireGuard and provides the
- Validate
- Check firewall, NTM tests, and node logs.
Confirm IPv4 and IPv6 exist and route correctly
In the appendix section Troubleshoot IPv4 + IPv6 you will find deeper diagnostics. In most cases, however, a quick check like the following is enough to confirm that your VPS has working outbound connectivity on both IPv4 and IPv6.
# Show your public IPv4 and IPv6 addresses
curl -4 https://ifconfig.me
curl -6 https://ifconfig.me
# Confirm basic DNS resolution plus routing works over both families
ping -4 -c 3 www.google.com
ping -6 -c 3 www.google.com
Nym CLI Script Audit
The Nym Node CLI (nym-node-cli.py) orchestrates installation and configuration by calling several helper scripts.
If you prefer to review what will run on your server, you can download these scripts first and inspect them locally on the VPS.
# Choose which branch you want to audit
branch="develop"
base="https://raw.githubusercontent.com/nymtech/nym/${branch}/scripts/nym-node-setup"
# Create a folder dedicated to the audit copy of the scripts
mkdir -p /root/nym-node-cli-scripts-for-audit
cd /root/nym-node-cli-scripts-for-audit
# Download the helper scripts used by the CLI
for f in \
nym-node-prereqs-install.sh \
nym-node-install.sh \
setup-systemd-service-file.sh \
start-node-systemd-service.sh \
network-tunnel-manager.sh \
quic_bridge_deployment.sh \
setup-nginx-proxy-wss.sh \
landing-page.html
do
wget -qO "$f" "$base/$f"
done
# Quick overview of what you downloaded
ls -la
Example: quickly search for network and service related actions:
grep -RIn --color=auto -e 'apt ' -e 'curl ' -e 'wget ' -e 'systemctl' -e 'ufw' -e 'iptables' -e 'sysctl' .
Run nym-node-cli.py as root
At this stage, we run the Nym Node CLI script directly.
I run it as root because it needs to install packages, write system configuration, and create systemd services.
# Pick the branch you want to use for installation
branch="develop"
base="https://raw.githubusercontent.com/nymtech/nym/${branch}/scripts/nym-node-setup"
# Create a dedicated directory for the CLI
mkdir -p /root/nym-node-cli
cd /root/nym-node-cli
# Download the CLI script
wget -qO nym-node-cli.py "${base}/nym-node-cli.py"
chmod +x ./nym-node-cli.py
# Create an environment file consumed by the CLI
cat > env.sh <<'EOF'
export MODE="exit-gateway"
export HOSTNAME="wznymnode2.root.sx"
export LOCATION="DE"
export EMAIL="operator@weisser-zwerg.dev"
export MONIKER="weisser-zwerg.dev (wznymnode2.root.sx)"
export DESCRIPTION="weisser-zwerg.dev operated nym-node"
export WIREGUARD="true"
EOF
Now run the installer.
If you want a full transcript that captures both command output and your interactive input, you can use script:
# Install the 'script' utility (part of util-linux) if it is not present
apt-get update
apt-get install -y util-linux
# Record a complete installation transcript
LOG="/root/nym-install.$(date -u +%Y-%m-%d-%H%M%S).log"
script -q -f -a "$LOG" -c "python3 ./nym-node-cli.py install"
Or, if you prefer a normal interactive run without logging:
python3 ./nym-node-cli.py install
A few practical notes about the variables in env.sh:
MODEselects what you are operating. I recommend using--mode exit-gatewayinitially, because this mode offers the full range of functionality, making it the best starting point. Once you confirm everything is running smoothly, you can switch to a more restricted mode if needed.HOSTNAMEshould match the DNS name you control and point to your VPS. This matters for TLS certificates and for the landing page setup.LOCATIONis usually a two letter country code. It is used for metadata and discovery in tooling.EMAILis used for certificate related automation and can also be used as an operator contact.MONIKERandDESCRIPTIONare human readable labels that appear in explorers.WIREGUARD="true"enables WireGuard related setup steps, which is relevant if you are preparing the node for dVPN connectivity.
NGINX
On my first run, the CLI failed to set up Nginx, and the failure was easy to miss because the rest of the process continued. The key hint was in the log output:
* * * Starting nginx configuration for landing page, reverse proxy and WSS * * *
Landing page at /var/www/wznymnode2.root.sx/index.html
Cleaning existing nginx configuration
Failed to restart nginx.service: Unit nginx.service not found.
Failed to start nginx.service: Unit nginx.service not found.
This error means the Nginx package was not installed at the time the script attempted to configure and restart it.
Installing nginx and the certificate tooling upfront fixed the issue on the second run.
Bonding (One Wallet per Node)
During installation, the CLI prints a lot of output. The important part is the bonding flow, where you connect your node identity to a wallet and submit an on chain bonding transaction.
Here are some relevant excerpts from my run:
Error: Package 'ntp' has no installation candidate
Error: Package 'ntpdate' has no installation candidate
...
2026-01-13T07:24:59.736762Z WARN nym-node/src/cli/commands/run/mod.rs:53: you don't seem to have accepted the terms and conditions of a Nym node operator
2026-01-13T07:24:59.736804Z WARN nym-node/src/cli/commands/run/mod.rs:54: please familiarise yourself with <https://nymtech.net/terms-and-conditions/operators/v1.0.0> and run the binary with '--accept-operator-terms-and-conditions' flag if you agree with them
...
Identity Key: DBBCDYsgAAj7g4FLQkSxXZAcdG5m9Hx8vMreqRaX1Yqo
Host: wznymnode2.root.sx
...
Running: curl -4 https://ifconfig.me
45.157.233.31
...
========================================================
* * * FOLLOW THESE STEPS TO BOND YOUR NODE * * *
If you already bonded your node before, just press enter
========================================================
1. Open your wallet and go to Bonding menu
2. Paste Identity key and your IP address (printed above)
3. Setup your operators cost and profit margin
4. Copy the long contract message from your wallet
5. Paste the contract message from clipboard here and press enter:
...
The warnings about ntp and ntpdate can be ignored if your server already uses systemd-timesyncd and time synchronization is working.
On newer Debian and Ubuntu systems, ntp and ntpdate are often not available as installable packages, because time sync is typically handled by systemd tooling instead.
The warning about accepting the operator terms and conditions is expected during the initial run.
It will disappear once the node is started with the --accept-operator-terms-and-conditions flag.
In my case, the tooling later added this flag to the systemd unit at /etc/systemd/system/nym-node.service.
Because I already operated a first Nym node from my initial guide, I ran into an important bonding constraint.
I initially assumed I could bond a second node with the same wallet. That is not possible. I got confirmation in the Node Operators channel that a single wallet can only be used to bond one node.
So, I created another wallet, funded it from my first wallet, and then continued with bonding:
- New wallet address: n1myvxdm68x35eqswl042me9gsenq0pn39kltfx0
- Funding transaction: 7755A3ACF61BA2935766DF2D851ACE2BE8D2D1CD0ADC5F10FC8ED15861EF3B1F
- Bonding transaction: 96AB17E0E6763A28775073D07698723BEB13572EC839D1D695566AB65D4D562F
The bonding process itself is well documented in the official docs under Bond via the Desktop wallet (recommended). It includes detailed instructions and screenshots for the core steps:
- Enter your Identity Key
- Configure your host address
- Finalize and broadcast the bonding transaction
I explained the meaning of “Amount”, “Operating Cost”, and “Profit Margin” in my previous guide, and those explanations are still valid: Amount, Operating Cost, and Profit Margin.
If you plan to join the Nym Delegation Program or a Service Grants program, double check that your operating cost and profit margin follow the published program rules: Nym delegations program update.
After the bonding transaction is complete, the CLI continues with many additional steps. During this phase it may pause and ask you for confirmation by pressing enter.
QUIC Bridge
The next time you need to actively pay attention is when the installer reaches the QUIC bridge step and prints something like the following:
================================================================
SUCCESS: Bridge configuration created
================================================================
SECURITY NOTICE:
Config file created at: /etc/nym/bridges.toml
Permissions set to 640 (owner: nym)
Keys directory created at: /etc/nym/keys with mode 700
To further restrict access, consider:
chmod 600 /etc/nym/bridges.toml
To start the bridge:
systemctl enable nym-bridge
systemctl start nym-bridge
================================================================
Detected nftables - please manually configure port 4443/udp
Example: nft add rule inet filter input udp dport 4443 accept
Created symlink '/etc/systemd/system/multi-user.target.wants/nym-bridge.service' → '/usr/lib/systemd/system/nym-bridge.service'.
Processing triggers for man-db (2.13.1-1) ...
nym-bridge installed via .deb.
Detected nym-bridge binary in /usr/bin
Press Enter to continue...
The critical part is easy to overlook:
Detected nftables - please manually configure port 4443/udp
Example: nft add rule inet filter input udp dport 4443 accept
This means the bridge service is installed, but the UDP port it needs is not yet allowed through your firewall. If you miss this, the bridge may appear “running”, but it will not be reachable from the Internet.
What the QUIC bridge does in simple terms: it provides a UDP based transport endpoint (QUIC) that other components can reach. Because it listens on UDP/4443, you must ensure the VPS firewall allows inbound UDP traffic on that port, for both IPv4 and IPv6 if you use both.
At this point, open a second ssh session to your server.
In that second session, confirm that no rule exists yet for UDP port 4443:
nft list ruleset | grep 4443 || true
If your INPUT policy is DROP, then inbound UDP/4443 will be blocked until you add an explicit allow rule.
In your setup, you used UFW, which typically programs nftables through the iptables-nft compatibility layer.
A safe approach is to add the rule to the UFW chains and then persist it.
# Identify your uplink interface (do not assume eth0)
UPLINK_DEV="$(ip -o route show default | awk '{print $5}' | head -n1)"
echo "$UPLINK_DEV"
# Allow inbound QUIC bridge traffic on UDP/4443
iptables -I ufw-user-input 1 -i "$UPLINK_DEV" -p udp --dport 4443 -j ACCEPT
ip6tables -I ufw6-user-input 1 -i "$UPLINK_DEV" -p udp --dport 4443 -j ACCEPT
# Persist across reboot if you use netfilter-persistent
netfilter-persistent save
Now verify the rules. You can check via nftables and via the iptables compatible view:
nft list ruleset | grep 4443 || true
iptables -S ufw-user-input | grep 4443 || true
ip6tables -S ufw6-user-input | grep 4443 || true
Once UDP/4443 is allowed, return to your first ssh session and continue the installer by pressing enter when prompted.
The remaining steps are typically safe to acknowledge, but still read each prompt carefully.
After the installation finishes, verify that the QUIC bridge service is installed, enabled, and running:
systemctl is-enabled nym-bridge
systemctl status nym-bridge --no-pager
Netfilter persistence
After the full installation script finishes, it is a good moment to save firewall rules one more time, especially if you made manual changes during the install:
netfilter-persistent save
It is also a reasonable point to restart your Nym node service and confirm it starts cleanly:
systemctl restart nym-bridge
systemctl restart nym-node
journalctl -u nym-node | grep "starting Nym Node" || true
Verify Open Ports
As a final sanity check, it is worth looking at which ports are currently open on your VPS, and which processes are listening on them. This helps you confirm that the Nym services started correctly and that you did not accidentally expose something you did not intend.
A simple command that shows both TCP and UDP listeners is:
ss -lntup
Here is an example from my node after the installer finished:
ss -lntup
root@v1768142702:~# ss -lntup
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
udp UNCONN 0 0 0.0.0.0:51822 0.0.0.0:*
udp UNCONN 0 0 *:4443 *:* users:(("nym-bridge",pid=12304,fd=9))
udp UNCONN 0 0 [::]:51822 [::]:*
tcp LISTEN 0 1024 10.1.0.1:51830 0.0.0.0:* users:(("nym-node",pid=4453,fd=96))
tcp LISTEN 0 511 0.0.0.0:9001 0.0.0.0:* users:(("nginx",pid=4412,fd=5),("nginx",pid=4411,fd=5),("nginx",pid=4410,fd=5),("nginx",pid=4409,fd=5),("nginx",pid=4408,fd=5))
tcp LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=4412,fd=7),("nginx",pid=4411,fd=7),("nginx",pid=4410,fd=7),("nginx",pid=4409,fd=7),("nginx",pid=4408,fd=7))
tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=4412,fd=9),("nginx",pid=4411,fd=9),("nginx",pid=4410,fd=9),("nginx",pid=4409,fd=9),("nginx",pid=4408,fd=9))
tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=812,fd=6))
tcp LISTEN 0 1024 *:8080 *:* users:(("nym-node",pid=4453,fd=15))
tcp LISTEN 0 1024 *:9000 *:* users:(("nym-node",pid=4453,fd=79))
tcp LISTEN 0 511 [::]:9001 [::]:* users:(("nginx",pid=4412,fd=6),("nginx",pid=4411,fd=6),("nginx",pid=4410,fd=6),("nginx",pid=4409,fd=6),("nginx",pid=4408,fd=6))
tcp LISTEN 0 511 [::]:443 [::]:* users:(("nginx",pid=4412,fd=8),("nginx",pid=4411,fd=8),("nginx",pid=4410,fd=8),("nginx",pid=4409,fd=8),("nginx",pid=4408,fd=8))
tcp LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=4412,fd=10),("nginx",pid=4411,fd=10),("nginx",pid=4410,fd=10),("nginx",pid=4409,fd=10),("nginx",pid=4408,fd=10))
tcp LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=812,fd=7))
tcp LISTEN 0 1024 *:1790 *:* users:(("nym-node",pid=4453,fd=21))
tcp LISTEN 0 1024 *:1789 *:* users:(("nym-node",pid=4453,fd=22))
How to read this output:
*:4443/udpserved bynym-bridgeis the QUIC bridge. This is the port you manually opened earlier.:80and:443served bynginxare typically your landing page and TLS reverse proxy. Port443is also commonly used for WSS related components.:22served bysshdis your SSH access. This should be open only to the sources you trust.- Ports served by
nym-nodeare the node internal and external services. Some may be intended to be public, while others are intended to be reachable only locally or through a private tunnel.
In your example, one listener is bound to 10.1.0.1:51830. That is a private address, which is bound to an internal interface, such as a WireGuard or routing interface.
If you want to focus only on sockets that are reachable from the public Internet, pay attention to listeners bound to 0.0.0.0:<port> and [::]:<port> or *:<port>.
These bindings usually mean “all interfaces”.
Listeners bound to a specific private address (for example 10.x.x.x) are generally limited to that internal interface.
UFW vs. netfilter-persistent package conflict
During installation I noticed an inconsistency in the helper scripts.
Early on, the scripts configure firewall rules using ufw.
Later, network-tunnel-manager.sh and quic_bridge_deployment.sh install iptables-persistent, which pulls in netfilter-persistent.
On Debian, these packages conflict with ufw, so you can end up with a partially removed ufw package and firewall persistence handled by netfilter-persistent instead.
This is not automatically “bad”, but it is something you should be aware of. The important operational point is that you should have exactly one clear source of truth for firewall configuration.
If you think you are managing rules with
ufw, but the system is actually loading rules from/etc/iptables/rules.v4and/etc/iptables/rules.v6at boot, troubleshooting becomes very confusing.
You can see the conflict directly in the package metadata on Debian:
apt-cache show ufw | grep -E '^(Package|Version|Depends|Breaks|Conflicts|Replaces):'
Example output:
Package: ufw
Version: 0.36.2-9
Depends: iptables, procps, ucf, python3:any, debconf (>= 0.5) | debconf-2.0
Breaks: iptables-persistent, netfilter-persistent
To understand why this happens, it helps to grep the scripts you audited earlier.
In my case, the prereqs script clearly installs and enables ufw, but later scripts install iptables-persistent:
grep -RInE 'apt(-get)? .*install .*iptables-persistent|apt(-get)? .*install .*netfilter-persistent|ufw' \
/root/nym-node-cli-scripts-for-audit 2>/dev/null
Example hits (trimmed):
nym-node-prereqs-install.sh:... apt install ... ufw ...
nym-node-prereqs-install.sh:... ufw enable
nym-node-prereqs-install.sh:... ufw allow ...
network-tunnel-manager.sh:... apt-get install -y iptables-persistent
quic_bridge_deployment.sh:... apt-get install -y iptables-persistent
This is not necessarily a problem, but you should decide which system you want to keep.
If you are happy with the current firewall rules
If your node works and the current rules look correct, the simplest option is to accept the result and clean up any residual package state.
In my case, the system ended up using netfilter-persistent, and ufw was already removed except for leftover config files.
First, check the current state:
dpkg -l ufw || true
command -v ufw || echo "ufw binary not found"
systemctl is-enabled netfilter-persistent || true
systemctl status netfilter-persistent --no-pager || true
ls -l /etc/iptables/ || true
Example output showed:
ufwinrcstate and noufwbinary presentnetfilter-persistentenabled and active/etc/iptables/rules.v4and/etc/iptables/rules.v6present
If your system looks similar, you can fully remove ufw to avoid confusion later:
apt-get purge -y ufw
At this point, your firewalling is controlled by netfilter-persistent.
You can create a backup of the current configuration like this:
nft list ruleset > "/root/nft.rules.backup.$(date +%F-%H%M%S)"
iptables-save > "/root/iptables.rules.v4.backup.$(date +%F-%H%M%S)"
ip6tables-save > "/root/iptables.rules.v6.backup.$(date +%F-%H%M%S)"
cp -a /etc/iptables "/root/etc-iptables.backup.$(date +%F-%H%M%S)" 2>/dev/null || true
Backup
If you run a node for more than a few days, you will eventually need backups. A backup is what lets you rebuild your VPS after an incident, migrate to a new host, or upgrade safely without losing your node identity.
The Nym docs page Where can I find my private and public keys and config? explains where the node stores its identity keys and configuration.
To make this easy and repeatable, I prepared a small set of scripts that handle backup, restore, and binary upgrade. You can clone them like this:
cd /root
git clone https://gist.github.com/cs224/3af61447aa358fceb77985c01e87a4e9 nym-backup/
tree nym-backup/
cd /root/nym-backup/
rm -rf .git
chmod u+x *.sh
Example layout:
nym-backup/
├── backup-create-backup.sh
├── backup-create-bundle.sh
├── backup-init.sh
├── backup-restore-bundle.sh
├── bashrc
└── upgrade-swap-binary-symlink.sh
In nym-backup/bashrc you will find a few shell aliases that you can copy into your own ~/.bashrc.
These are:
git-lsshows the backups you have created (each backup is a git commit with metadata).nym-lsshows availablenym-nodebinaries from GitHub releases.nym-dl nym-binaries-v2025.21-mozzarelladownloads the specified release into/root/nym-binariesas/root/nym-binaries/nym-node-v2025.21-mozzarella.
Initialize the backup repository
The backup structure is based on a local git repository. The advantage is that you get a full history of changes, and you can quickly review what changed between snapshots.
Initialize it once:
bash backup-init.sh
Example output:
Initialized empty Git repository in /root/nym-backup/dotnym-repo/.git/
[main (root-commit) 238980a] Initialize backup repository
2 files changed, 12 insertions(+)
create mode 100644 .gitignore
create mode 100644 meta/current_target.txt
Init complete:
Repo: /root/nym-backup/dotnym-repo
Bundles: /root/nym-backup/dotnym-bundles
Create a backup snapshot
Whenever you feel it is a good moment, create a backup snapshot. I recommend doing it after initial setup, before upgrades, and after any meaningful configuration change.
bash backup-create-backup.sh
Example output (trimmed):
[main 4c9090c] Snapshot 2026-01-14-152512
...
Committed snapshot 2026-01-14-152512
You can review the backup history and inspect changes:
# Review snapshots (convenience alias)
git-ls
# Show which files changed in the latest snapshot
git -C /root/nym-backup/dotnym-repo show --pretty="" --name-only HEAD~0
# Show the actual diff for the latest snapshot
git -C /root/nym-backup/dotnym-repo show HEAD~0
Create a portable bundle
A snapshot is stored in the local git repo. A bundle is the portable artifact you can move to another machine. It is the unit you use for disaster recovery and migrations.
Create a bundle like this:
bash backup-create-bundle.sh
Example output (trimmed):
Created and verified bundle: /root/nym-backup/dotnym-bundles/dotnym.2026-01-14-162512.bundle
A bundle is a self contained git history export. It is convenient because you can copy a single file to another VPS and restore everything needed for your node identity from it.
Restore
A bundle is what you use to restore a node identity after you reinstall a VPS or move to a different VPS. This is the same workflow I referenced earlier in VPS Setup & Configuration.
If you ever want to reinstall your node, or move it to another VPS, restore the node identity before you continue with node setup. If you install and start a fresh node first, you might generate a new identity and then have to undo it.
# After copying backup-restore-bundle.sh and a backup bundle
# (for example "dotnym.2026-01-14-054215.bundle") to the new VPS, run:
bash backup-restore-bundle.sh dotnym.2026-01-14-054215.bundle
Restore on a different machine
If you change any bonded settings during a migration, you must also update them on chain. Typical examples are:
- You moved to a different public IP address
- You changed from using an IP to using a DNS name (or the other way around)
- You changed the HTTP port that should be advertised
The docs page Change Settings via Desktop Wallet explains how to announce these changes via the desktop wallet.
Why this matters: explorers and clients use the bonded metadata to find and reach your node. If the on chain host information is outdated, your node can be technically “up”, but unreachable for routing and selection.
Upgrade
The script upgrade-swap-binary-symlink.sh upgrades the node binary using a symlink swap.
It stops the node, points /root/nym-binaries/nym-node to a specific versioned binary, and then starts the node again.
Example run:
bash nym-backup/upgrade-swap-binary-symlink.sh v2025.21-mozzarella
Example output (trimmed):
Pre-flight:
Input: v2025.21-mozzarella
Version: v2025.21-mozzarella
Release tag: nym-binaries-v2025.21-mozzarella
New binary: /root/nym-binaries/nym-node-v2025.21-mozzarella
Symlink: /root/nym-binaries/nym-node
Old target: <none>
Checking new binary runs...
Stopping nym-node.service...
Swapping symlink atomically...
Starting nym-node.service...
Updated: /root/nym-backup/dotnym-repo/meta/current_upgrade.txt
Upgrade successful.
Symlink now points to:
/root/nym-binaries/nym-node-v2025.21-mozzarella
Running version:
nym-node
Binary Name: nym-node
Build Timestamp: 2025-11-25T14:26:29.627763948Z
Build Version: 1.22.0
Commit SHA: 22793bc45ea21561671d6670497ff42bc36b9d76
Commit Date: 2025-11-25T15:16:42.000000000+01:00
Commit Branch: HEAD
rustc Version: 1.88.0
rustc Channel: stable
cargo Profile: release
The upgrade script itself does not create a backup commit. After a successful upgrade, it is a good habit to record the new state in your backup repository:
./backup-create-backup.sh
Example output:
[main 651bc56] Snapshot 2026-01-14-154213
10 files changed, 10 insertions(+), 1 deletion(-)
create mode 100644 meta/current_upgrade.txt
Committed snapshot 2026-01-14-154213
Suggested upgrade routine that works well in practice:
- Create a backup snapshot and bundle.
- Upgrade by swapping the binary symlink.
- Confirm the node starts cleanly and stays stable for a few minutes.
- Create a new backup snapshot so the repo records the upgrade metadata.
This routine is boring, but it is reliable.
Conclusion
In this post, you have seen how to use the Nym Node CLI to set up a nym-node from scratch.
Compared to the approach I described in my earlier guide, Nym Mixnet & dVPN: A Node Operator’s Guide, the 2026 setup flow is much smoother and more consistent.
Running a node is a practical way to support privacy and censorship resistance. The Nym mixnet becomes stronger when more independent operators contribute infrastructure, keep nodes online reliably, and increase geographic diversity.
If you have questions, corrections, or suggestions, feel free to use the comments section below. I am also interested in hearing about your operator setup, what worked well for you, and what was unclear or difficult.
Appendix
Investigating the Node via API
curl -sS -X 'GET' 'https://wznymnode2.root.sx/api/v1/description' -H 'accept: application/json' | jq
{
"moniker": "weisser-zwerg.dev (wznymnode2.root.sx)",
"website": "https://weisser-zwerg.dev/posts/digital-civil-rights-networking-nym-node-operator-guide-2026/",
"security_contact": "operator@weisser-zwerg.dev",
"details": "weisser-zwerg.dev operated nym-node"
}
curl -sS -X 'GET' 'https://validator.nymtech.net/api/v1/nym-nodes/bonded' -H 'accept: application/json' | jq '.data[] | select(.bond_information.node.identity_key=="DBBCDYsgAAj7g4FLQkSxXZAcdG5m9Hx8vMreqRaX1Yqo") | .rewarding_details.cost_params'
{
"profit_margin_percent": "0.2",
"interval_operating_cost": {
"denom": "unym",
"amount": "250000000"
}
}
curl -sS -X 'GET' 'https://validator.nymtech.net/api/v1/nym-nodes/described' -H 'accept: application/json' | jq | grep DBBCDYsgAAj7g4FLQkSxXZAcdG5m9Hx8vMreqRaX1Yqo
curl -sS -X 'GET' 'https://wznymnode2.root.sx/api/v1/roles' -H 'accept: application/json' | jq
{
"mixnode_enabled": false,
"gateway_enabled": true,
"network_requester_enabled": true,
"ip_packet_router_enabled": true
}
curl -sS -X 'GET' 'https://wznymnode2.root.sx/api/v1/build-information' -H 'accept: application/json' | jq
{
"binary_name": "nym-node",
"build_timestamp": "2025-11-25T14:26:29.627763948Z",
"build_version": "1.22.0",
"commit_sha": "22793bc45ea21561671d6670497ff42bc36b9d76",
"commit_timestamp": "2025-11-25T15:16:42.000000000+01:00",
"commit_branch": "HEAD",
"rustc_version": "1.88.0",
"rustc_channel": "stable",
"cargo_profile": "release",
"cargo_triple": "x86_64-unknown-linux-gnu"
}
curl -sS -X 'GET' 'https://wznymnode2.root.sx/api/v1/auxiliary-details' -H 'accept: application/json' | jq
{
"location": "DE",
"announce_ports": {
"verloc_port": null,
"mix_port": null
},
"accepted_operator_terms_and_conditions": true
}
{
"location": "DE",
"announce_ports": {
"verloc_port": 1790,
"mix_port": 1789
},
"accepted_operator_terms_and_conditions": true
}
curl -sS -X 'GET' 'https://wznymnode2.root.sx/api/v1/load' -H 'accept: application/json' | jq
{
"total": "low",
"machine": "negligible",
"network": "negligible"
}
# The [Nym Node Troubleshooting](https://nym.com/docs/operators/troubleshooting/nodes) guide mentions to check the `blacklist`, which you can do as follows:
curl -sS -X 'GET' https://validator.nymtech.net/api/v1/gateways/blacklisted | jq | grep DBBCDYsgAAj7g4FLQkSxXZAcdG5m9Hx8vMreqRaX1Yqo
Troubleshoot IPv4 + IPv6
When node setup fails, networking is a common root cause. Before changing anything, it helps to confirm four things in order: addresses, default routes, kernel routing decisions, and real outbound connectivity.
First, confirm that your VPS actually has public IPv4 and IPv6 addresses assigned:
ip -br -4 addr
ip -br -6 addr
Next, confirm that default routes exist for both IPv4 and IPv6:
ip -4 route show default
ip -6 route show default
Then, confirm that the kernel can select a valid egress path and source address. This is useful when you have multiple interfaces, multiple IPs, or policy routing:
ip -4 route get 1.1.1.1
ip -6 route get 2606:4700:4700::1111
Finally, confirm real outbound connectivity, not just correct looking routing tables:
curl -4 https://ifconfig.me
curl -6 https://ifconfig.me
ping -4 -c 3 1.1.1.1
ping -6 -c 3 2606:4700:4700::1111
uv Python Environment
Normally I prefer to use uv to manage Python versions and virtual environments.
It is fast, predictable, and keeps system packages separate from project tooling.
In this post, however, we already installed system Python packages, which is perfectly fine for a VPS where you want a straightforward baseline.
If you still prefer uv, you can install it and add it to your PATH like this:
curl -LsSf https://astral.sh/uv/install.sh | sh
line='export PATH="/root/.local/bin:$PATH"'
file=/root/.bashrc
grep -qxF "$line" "$file" || printf '\n%s\n' "$line" >> "$file"
source /root/.bashrc
Then, you can install Python executables via uv.
For example, to install Python 3.12 and make it the default:
uv python install 3.12 --default
Footnotes
Originally, I used Duck DNS, but it encountered several downtimes and service degradations for my use case. DuckDNS is a free dynamic DNS service that maps a subdomain under
duckdns.orgto your public IP.Then I moved to No IP, but I found the manual renewal process annoying for long term operations. No IP’s free hostnames require confirmation roughly every 30 days to remain active. Finally, I ended up with FreeDNS, mainly because it gives me more flexibility around DNS management and does not require the same recurring confirmation workflow for the setup I wanted. ↩︎
