Skip to content

Latest commit

 

History

36 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

decenzed-node — run your own proxy, share links with friends

tests version license

decenzed-node is a standalone VLESS proxy server you run on your own machine (Windows, macOS, Linux). It runs an embedded xray-core and prints share links you hand to friends. There is no coordination server — it's fully self-contained and open source.

Features

  • VLESS (XTLS-Vision), Trojan, Shadowsocks classic + Shadowsocks-2022 protocols
  • Two camouflage modes: REALITY, or TLS behind your own auto-hosted website
  • Automatic Let's Encrypt certificates (obtain + renew) for the TLS mode
  • One subscription link per client — apps pull every protocol automatically
  • Per-user speed cap and per-client / per-protocol traffic stats
  • Per-user domain & IP filtering (blacklist / whitelist) — geosite/geoip categories, custom lists, and geoip:private LAN-egress block on by default
  • DDNS (DuckDNS) for dynamic IPs
  • Wide architecture support — x86, ARM, MIPS (desktops + OpenWRT routers)
  • Single self-contained binary on embedded xray-core, no coordination server

It offers two camouflage modes for VLESS/Trojan (you pick one in setup):

  • REALITY (default) — scans for a live third-party TLS 1.3 + HTTP/2 site to borrow as cover and generates its own REALITY keys. No domain or certificate of your own required.
  • TLS + your own website — masquerades behind your own domain. The website is raised automatically by the node — you don't create or host anything yourself: it serves a small built-in site, obtains a Let's Encrypt certificate automatically (DNS-01 via DuckDNS, so no port 80 needed), and xray falls back to that site on any non-proxy traffic. To a probe or a stray browser the node is just an ordinary HTTPS website on your domain. The same domain doubles as your dynamic-DNS host and the certificate/website host — that's by design, nothing separate to set up. Needs a DuckDNS domain.

1. Requirements

  • A public IP (static or dynamic). If the machine is on a home LAN, forward its port on the router. CGNAT is not supported (some ISPs / mobile put you behind carrier NAT — inbound connections can't reach you).
  • If the node runs on a desktop/laptop behind a home router, give that machine a fixed LAN IP via a DHCP reservation (bind its MAC to one address in your router's DHCP settings). Otherwise its LAN IP can change on reboot/lease renewal and your port-forward will silently point at the wrong device.
  • Open/forward one TCP port for VLESS (default 8443). One TCP port hosts exactly one protocol — if you also enable Trojan and/or Shadowsocks, each needs its own additional forwarded/opened TCP port. setup asks for every port the same way and pre-fills a free one (Trojan 32000–35000, Shadowsocks 35000–38000).
  • Admin/root to install and manage the background service. The interactive CLI requests it automatically (see §3); on OpenWRT you already run as root.
  • A duckdns.org subdomain — required for the TLS camouflage mode (certificate + DNS-01), optional otherwise (a stable domain in share links).

2. Get the binary

Download the prebuilt binary for your OS, or build from source (Go 1.26+):

cd src
go build -o decenzed-node ./cmd/decenzed-node   # Windows: decenzed-node.exe

Put it in a folder you can write to — it keeps its data in a decenzed-data/ folder next to the executable (config, xray.json, stats, logs).

On an OpenWRT router (auto-detects CPU architecture)

You can run the node on the router itself. One line installs the correct binary for your router's CPU (arm64, armv7, mipsel, …) from the latest release, verifies its SHA-256, and puts decenzed-node on your PATH:

wget -O - https://github.com/icecube092/decenzed-node/releases/latest/download/install-openwrt.sh | sh

Then, on the router:

decenzed-node setup   # network check + port 8443 (443 is taken by LuCI) + camouflage
                      # (REALITY or your own TLS site), then installs the procd boot service
decenzed-node link    # the connection link to paste into your client
# later: decenzed-node check   # re-verify the RUNNING node is reachable from outside

Notes for routers:

  • The flash-tight router binaries (32-bit ARM and 32-bit MIPS) are UPX-compressed (~10–12 MB on disk; ~30 MB uncompressed, decompressed into RAM at startup). Everything else ships uncompressed — desktop/server (amd64, 386, Windows, macOS) and arm64 routers (which have ample flash) to avoid antivirus false positives, and the 64-bit MIPS arches because UPX can't pack them. Devices with only 8–16 MB of flash may still need USB storage + extroot: re-run the installer with DIR=/mnt/usb, and/or set DECENZED_DATA=/mnt/usb/decenzed-data so config/logs live off the flash.
  • On OpenWRT the background service is managed by procd (/etc/init.d/decenzed-node), so service install|status|start|stop|restart work natively, and update self-replaces the binary and restarts the service.
  • Domain/IP filters: the geosite.dat / geoip.dat data files are several MB each and load into RAM — heavy for flash-tight routers. They're fetched only if you use a geosite:/geoip:COUNTRY source; prefer custom text lists or geoip:private, or move data to USB with DECENZED_DATA. See Per-user domain & IP filters.
  • Manual control: /etc/init.d/decenzed-node {start|stop|restart|status}; logs via logread -e decenzed.

How it works on a router — where the port points. The node is a TCP server: it listens on your chosen port (e.g. 8443) on the router, and clients connect inbound to it. What you do to expose that port depends on where the node sits:

  • Node on your main/edge router (the box that holds the public IP — e.g. a Routerich AX3000 as your primary router). The listening port is already on the WAN edge; there is nothing to "forward". OpenWRT blocks WAN input by default, so the port just has to be allowed through the firewall — and the installer does this for you: it adds an idempotent Allow-decenzed-node rule accepting inbound TCP 8443 from WAN. Chose a non-default port? Re-run the installer with PORT=<port> (or edit that rule in LuCI → Network → Firewall → Traffic Rules). To skip the automatic rule entirely, install with NO_FIREWALL=1.
  • Node on a router behind another router/ISP box. Forward TCP 8443 on the upstream box to this router's LAN IP (Port Forwarding / Virtual Server), exactly as decenzed-node check prints.

External port ≠ internal port (443 → 8443). If you want clients to connect on a tidy public port (e.g. 443) but the node listens on a different local one (e.g. 8443, since 443 is often taken by LuCI), forward WAN 443 → LAN 8443 on your router and tell setup about it: after the bind-port prompt, setup asks for the external port clients dial (default same) — enter 443. The node keeps binding 8443, while share links, subscriptions, and self-checks all use 443. Each protocol has its own bind/external pair; stats and check show the mapping as 443(->8443) when the two differ.

Either way you need a real public IP. Behind CGNAT (your ISP shares one IP across many customers) inbound connections can't reach you — ask your ISP for a public/"white" IP, or front the node with a cheap VPS. decenzed-node check detects CGNAT/private IPs and warns you.

Extra protocols (Trojan / Shadowsocks)

VLESS is always on. setup asks a separate y/n question for each optional protocol:

  • Trojan — shares VLESS's camouflage (REALITY, or TLS with a fallback to your website; no XTLS flow).
  • Shadowsocks — classic chacha20-ietf-poly1305, multi-user. The broadest client support; no TLS/REALITY masking (a distinct, less-stealthy traffic type).
  • Shadowsocks-20222022-blake3-aes-128-gcm, stronger, but many clients reject it, so it's offered as a separate inbound/port for the clients that do accept it. (If a client won't import your Shadowsocks entry, use the classic one — or VLESS/Trojan.)

If you answer yes, setup asks for that protocol's port, pre-filled with a random free port from a recommended range (Trojan 32000–35000, Shadowsocks 35000–38000); on a re-run your saved port is offered as the default, even if it's currently in use. VLESS keeps 8443.

One TCP port hosts one protocol, so each enabled protocol needs its own forwarded/opened port. decenzed-node link prints one subscription link per client (TLS mode) covering every enabled protocol. Per-user speed caps and traffic stats apply across all of them (each client is metered by a single identity regardless of which protocol it connects with). On OpenWRT open the extra WAN ports by re-running the installer with the full list, e.g. PORT="8443 33001 36001 36002" ./install-openwrt.sh (space- or comma-separated).

Supported architectures

Every release ships prebuilt static binaries (pure Go, CGO_ENABLED=0, no libc dependency — they run on glibc and musl/OpenWRT alike). install-openwrt.sh auto-detects the router's CPU (uname -m + ELF endianness) and downloads the matching one.

Desktop

OS Asset
Windows x86-64 decenzed-node-windows-amd64.exe
macOS (Intel) decenzed-node-darwin-amd64
macOS (Apple Silicon) decenzed-node-darwin-arm64
Linux x86-64 decenzed-node-linux-amd64

Routers / OpenWRT

Asset Go arch Typical OpenWRT targets & CPUs
decenzed-node-linux-arm64 arm64 (aarch64) Filogic MT798x — Routerich AX3000, mvebu, ipq807x, bcm27xx
decenzed-node-linux-armv7 arm, GOARM=7 ipq40xx, mvebu (32-bit), sunxi, bcm53xx
decenzed-node-linux-armv6 arm, GOARM=6 bcm2708 / older ARMv6
decenzed-node-linux-armv5 arm, GOARM=5 kirkwood / older ARMv5
decenzed-node-linux-mipsle-softfloat mipsle, softfloat ramips (mt7620/mt76x8), rt305x — most budget routers
decenzed-node-linux-mips-softfloat mips, softfloat ath79, lantiq (big-endian MIPS)
decenzed-node-linux-mips64le mips64le octeon / newer 64-bit MIPS (LE)
decenzed-node-linux-mips64 mips64 64-bit MIPS (BE)
decenzed-node-linux-amd64 amd64 x86_64 routers & VMs
decenzed-node-linux-386 386 legacy x86

MIPS builds use GOMIPS=softfloat because router SoCs have no FPU. If your device isn't covered, open an issue with the output of uname -m and . /etc/openwrt_release; echo "$DISTRIB_TARGET".

3. How to run it

  • Interactive shell — double-click the .exe (Windows) or run with no arguments. A prompt opens where you type commands. Type q to leave the current command (setup, debug, logs -f, …) and return to the prompt; exit the shell with exit/quit/q or Ctrl+D.
  • One commanddecenzed-node <command>.
  • Service — once installed, it runs in the background on boot.
  • Admin only when needed — elevation (Windows: UAC prompt; Linux/macOS: sudo) happens once when you open the interactive shell, and for one-shot commands only when they need it (service, update, setup, and the config-changing link add/remove/edit and debug, which restart the service). Read-only commands (version, link, stats, config, check, logs) never prompt. On OpenWRT you're already root, so nothing changes. Skip elevation entirely with DECENZED_NO_ELEVATE=1.

4. Setup

decenzed-node setup

The wizard starts with a network readiness check, in order:

  1. Detects your public IP (warns if it looks like CGNAT) and auto-detects your country (used to label proxies, e.g. RS [VLESS]).
  2. Picks the VLESS TCP port (default 8443), then prints step-by-step port-forward instructions for your router.
  3. Self-checks that port from your public IP (it spins up a temporary listener, since the node isn't running yet).
  4. Runs a speed test.

It then asks a y/n for each optional protocol (Trojan, Shadowsocks, and Shadowsocks-2022), each on its own port pre-filled with a free one — see the Extra protocols section.

Then it asks the policy questions — press Enter to keep the value shown in [brackets], or type no to clear/disable it:

  • Blocked protocols (default bittorrent; no = block none).
  • Block private/LAN IP ranges (default yes, recommended) — stops proxy users from reaching the node's own local network. See Per-user domain & IP filters.
  • Default domain filter for new users (default no) — a mode (blacklist/whitelist) + sources applied to newly created clients only.
  • Per-user speed cap (default 50 Mbit/s; no = unlimited).
  • Domain for share links (so links keep working when your IP changes). Setup first asks whether it should set up DuckDNS for you:
    • No — you bring your own domain (one you bought, or one already kept pointed at this node's IP by another program). Just type it in; the node uses it in links but never updates it — your own DNS is responsible for that. If you have a dynamic IP and don't want the built-in DuckDNS updater, you can point your own domain at the node and run ddclient yourself to keep its A record updated. Answer no here too if you have no domain — links then use the raw public IP.
    • Yes — the DuckDNS flow: it asks for your token, then the subdomain you created on duckdns.org — DuckDNS does not auto-create it, so sign in, add a subdomain, and enter its label without .duckdns.org. The node then keeps <subdomain>.duckdns.org pointed at your current IP.
  • Public IP for share links (no = auto-detect each time; only asked when you configure no domain at all).

Then it asks for the camouflage mode (reality or tls):

  • reality — scans for a REALITY camouflage domain (a live TLS 1.3 + HTTP/2 site near you) and generates your REALITY keypair.
  • tls — masquerade behind your own website (requires a DuckDNS domain, configured just above). It asks the Let's Encrypt account details: a contact email and acceptance of the Subscriber Agreement, then obtains the certificate right away over DNS-01 (so a misconfiguration fails now, not at first start). No domain scan is done in this mode.

It then creates your first client, writes the xray config, and — as its final step — offers to install & start the boot service (needs admin/root). It prints your first share link at the end.

Testing the TLS mode. The staging-vs-production Let's Encrypt CA is fixed at build time. Normal binaries use production; build a test binary with the Let's Encrypt staging CA (untrusted certs, far higher rate limits) via make build-test / make build-test-win, or go build -tags staging.

5. Check a running node

decenzed-node check

Run this once the node is up (setup installs the service for you). It shows your public IP, runs a speed test, refreshes your DuckDNS record, and dials back to your own domain/IP on every enabled protocol's port to confirm the running service is reachable from outside; disabled protocols are reported as such. If a port isn't reachable it prints port-forward instructions (listing the ports to open). (A serving machine mostly uploads, so ≥10 Mbit/s upload is recommended; the loopback self-check may fail from inside your own LAN even when forwarding is correct — test from mobile data to be sure.)

6. Background service

setup already installs and starts the boot service on its last step. To manage it directly:

decenzed-node service status
decenzed-node service install     # (re)install + start now (needs admin/root)
decenzed-node service restart     # apply a new binary after 'update'

Or run in the foreground for a quick test: decenzed-node start.

7. Share with friends — the link command

decenzed-node link                 # subscription link per client
decenzed-node link -l              # + one line per per-protocol connection link
decenzed-node link -s              # + a sing-box outbound per protocol
decenzed-node link add alice       # create a client (prompts for its domain filter)
decenzed-node link edit alice      # configure a client's per-user domain/IP filter
decenzed-node link remove alice    # revoke a friend

In TLS mode each client gets one subscription linkhttps://<your-domain>:<port>/sub/<client-id> — that you paste into a client (v2rayN/NG, nekobox, Hiddify, sing-box, …) as a subscription. The app fetches every enabled protocol (VLESS/Trojan/Shadowsocks) from it automatically, names the profile Decenzed-<node-id>, and labels each proxy by location+protocol (e.g. RS [VLESS]); it picks up changes on refresh. The subscription is served by the node's own decoy website, behind xray's TLS fallback on your domain — so it needs no extra port and looks like an ordinary HTTPS request. Use -l to also print each protocol's raw link (to copy individually) or -s for sing-box outbounds. (In REALITY mode there's no hosted subscription, so link prints the per-protocol links directly.) Adding/removing a client restarts the service so the change takes effect immediately.

8. Monitor & tune

decenzed-node stats                # protocols, per-client/per-inbound traffic, status
decenzed-node logs                 # tail the log (both app and xray)
decenzed-node logs xray -f         # follow only xray's logs (type q to stop)
decenzed-node debug                # toggle verbose logging (all xray logs)
decenzed-node config node|xray     # inspect app-config / generated xray JSON
decenzed-node update               # check for a newer version; ask before installing

update checks the release manifest and, only if a newer version exists, asks y/n before downloading. After installing it restarts the background service and re-launches the CLI so the session runs the new version immediately (the running process keeps the old code in memory until it restarts — replacing the binary file alone doesn't change an already-running program, on any OS).

After an update, re-run decenzed-node setup. A new version may add config fields or change defaults (e.g. new protocol options or the split bind/public port). Re-running setup re-asks every field with your current value as the default — press Enter to keep each one — and regenerates xray.json from the updated schema, so nothing is lost and the node picks up any new behaviour. Then decenzed-node check re-verifies the running node is still reachable. stats shows the enabled protocols, whether debug mode is on, and lifetime traffic broken down per protocol (across all clients) and per client (across all protocols, with the per-user speed cap noted there — the overall load is uncapped). xray exposes per-user and per-inbound counters separately but not their cross, so a full per-protocol-per-client matrix isn't available.

Logs are one file, each line tagged app (the node) or xray (the embedded core). logs app/logs xray filter by source; -f follows. Runtime xray errors land here too. debug turns on verbose logging (every xray line, including debug) until you turn it off; the log is capped at 10 MB (the oldest lines are dropped, recent history kept).

To change any setting, re-run decenzed-node setup — it re-asks every field with the current value as the default and rebuilds xray.json. You never edit xray JSON by hand.

9. Per-user domain & IP filters

Each client can have its own domain/IP filter, enforced by xray itself (routing rules keyed by the client). A filter has a mode and a list of sources:

  • blacklist — block the listed targets, allow everything else.
  • whitelist — allow only the listed targets, block everything else.

Configure a client interactively — link add prompts for it on creation (pre-filled with the setup default), and link edit <name> opens a small REPL:

decenzed-node link edit alice
  user> mode blacklist
  user> domains geosite:category-ads-all, geoip:ru, file:blocked
  user> done          # saves + reloads the service (q cancels)

setup asks for a default mode + sources applied to new users only (existing clients are never touched). stats shows each client's active filter.

Source formats (they combine — several sources = one filter)

Source Matches Needs a data file?
domain:example.com that domain + subdomains no
full: / keyword: / regexp: explicit xray domain matcher no
example.com (bare) same as domain: no
geosite:CATEGORY a domain list (e.g. geosite:category-ads-all) geosite.dat
geoip:CODE destination IP by country (e.g. geoip:ru) geoip.dat
geoip:private private/LAN/loopback ranges no (expanded to CIDRs)
file:NAME your text list at decenzed-data/domains/NAME.txt no
url:https://… a remote text list (e.g. hosted on GitHub), cached locally no
ext:FILE.dat:CATEGORY a category in your own .dat that .dat

There's no ! negation in xray — "allow everything except X" is just blacklist mode with X; "only X" is whitelist mode.

Custom lists (file: and url:)

A custom list is a plain text file, one source per line — any source from the table above (bare domains, domain:, geoip:, …). Blank lines and # comments are ignored (a trailing # … on a line is stripped too):

# my blocklist
domain:ru            # all .ru domains + subdomains
mail.ru
vk.com
geoip:ru             # + Russian IPs (needs geoip.dat)
geoip:private        # + LAN ranges

Local file — save it as decenzed-data/domains/<NAME>.txt (create the domains/ folder next to the binary if it doesn't exist) and reference it by its name without the path or .txt:

decenzed-node link edit alice
  user> mode blacklist
  user> domains file:blocklist       # -> decenzed-data/domains/blocklist.txt
  user> done

Remote list (GitHub, etc.) — host the same text file anywhere reachable over HTTPS (a GitHub repo works well) and reference it by its raw URL:

  user> domains url:https://raw.githubusercontent.com/<you>/<repo>/main/blocklist.txt

On GitHub, open the file and click Raw to get the https://raw.githubusercontent.com/… URL. The node downloads and caches the list into decenzed-data/domains/ when you add it, and decenzed-node update re-fetches every url: list in your config (if a refresh fails, the last cached copy is kept). You maintain only the file in your repo — no local editing, and it works offline from the cache. Lists combine, so you can mix file:blocklist, url:https://…, geosite:category-ads-all, geoip:ru in one filter.

geosite vs geoip

  • geosite = domain lists (matched against the sniffed domain/SNI). Best for categories of services (ads, trackers, social, …). Loyalsoldier's geosite is China-centric — it has geosite:cn, geolocation-!cn, category-ads-all, gfw, … but no per-country domain lists (no geosite:ru). The full category list lives in v2fly/domain-list-community.
  • geoip = IP ranges by country (geoip:ru, geoip:cn, geoip:us, … all two-letter codes) plus geoip:private, geoip:cloudflare, geoip:telegram, …. Best for "by country of the destination server" and catching direct connections to an IP (no domain to sniff). Using a country geoip source switches xray to domainStrategy: IPIfNonMatch (a DNS lookup per otherwise unmatched connection) so it also applies to domain traffic.

geoip:private is blocked globally by default (the setup question above) as an egress-safety measure, and needs no download.

⚠️ geosite.dat / geoip.dat are heavy — mind flash-tight routers

geosite.dat and geoip.dat are several megabytes each (and grow over time), downloaded into decenzed-data/domains/ and loaded into RAM by xray at start. On budget OpenWRT routers with only 8–16 MB of flash / limited RAM this can be too much. Recommendations for routers:

  • The node only downloads them if you actually use a geosite:/geoip:COUNTRY source — if you don't, nothing is fetched.
  • Prefer custom text lists (file:NAME, a few KB) or geoip:private (no file at all) over the big .dat files where possible.
  • If you need the .dat files, put data on USB/extroot and set DECENZED_DATA=/mnt/usb/decenzed-data so they live off the router's flash.
  • Country geoip matching also enables per-connection DNS resolution (IPIfNonMatch) — extra CPU on weak SoCs.

The files are downloaded on demand when you add such a source (with a y/n prompt), and decenzed-node update refreshes them (geosite/geoip before the binary), only for the files you actually use. They come from the Loyalsoldier/v2ray-rules-dat release (verified by its SHA-256 sidecar); override the source with the DECENZED_GEOSITE_URL / DECENZED_GEOIP_URL env vars.

Notes

  • Per-user speed cap is enforced by a small throttle proxy in front of xray (keyed by client source IP) — application-level, no OS/tc config.
  • Data lives next to the binary in decenzed-data/ so the CLI and the service (which may run as a different user) share the same files.
  • TLS mode stores the Let's Encrypt certificate, key, and ACME account key in decenzed-data/ (cert.pem, key.pem, account.key). The node renews the certificate automatically (checked daily, renewed ~30 days before expiry) and xray hot-reloads it with no restart and no dropped connections. The built-in decoy website serves on 127.0.0.1 and is reachable only through xray's TLS fallback, never directly.
  • Uninstall the service: decenzed-node service uninstall.

License

This project is licensed under the GNU Affero General Public License v3.0. See the LICENSE file for the full license text.

Support

Buy Me a Coffee at ko-fi.com

About

Self-hosted VLESS/Trojan/Shadowsocks proxy server with REALITY & TLS camouflage. Single binary, xray-core, OpenWRT-ready.

Topics

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Contributors

Languages