A LuCI web interface for managing sing-box on OpenWrt. Focused on VLESS + REALITY — the most censorship-resistant proxy protocol available today — with selective traffic splitting, GeoSite rule presets, multi-server failover and real-time monitoring via the built-in Clash API.
Why VLESS+REALITY? REALITY disguises TLS traffic as a legitimate connection to a real, popular website (the "SNI target"), making it indistinguishable from normal HTTPS even under active DPI probing. Combined with
xtls-rprx-visionflow it has near-zero overhead.
- Features
- Screenshots
- Requirements
- Installation
- Quick start
- How it works
- Configuration reference
- Supported protocols
- Development
- Troubleshooting
- Contributing
- Credits
- License
- VLESS + REALITY with
xtls-rprx-visionflow, uTLS fingerprint emulation (chrome/firefox/safari/edge) - Selective traffic splitting — route only blocked services through VPN, keep local traffic direct
- GeoSite presets — one-click rules for YouTube, Google, Telegram, Instagram, Twitter/X, Facebook, OpenAI, Spotify, Discord, Twitch, Netflix, GitHub, Microsoft, Apple, Amazon, WhatsApp and more
- Multi-server with auto-failover —
urltestgroup picks the lowest-latency server automatically - Real-time dashboard — running status, active server, uptime, TUN state, traffic, connections (powered by sing-box's built-in Clash API)
- Subscription import — paste a URL, get all VLESS servers imported as separate UCI sections
(base64-encoded
vless://links, deduplicated by name) - Custom routing rules — GeoSite / IP-CIDR / domain matching with action VPN / Direct / Block
- Network-wide ad blocking via
geosite-category-ads-all - Live log viewer — tail
/tmp/sing-box.logwith level filter, search and auto-refresh - Validation on apply — every config is checked with
sing-box checkbefore deployment, so a typo never takes the router offline
All screenshots are PNG, taken on OpenWrt 24.10 with the Cascade theme.
They live in docs/screenshots/.
| Component | Version | Notes |
|---|---|---|
| OpenWrt | 22.03+ | tested on 24.10 |
| sing-box | 1.11.0+ | needed for rule_set and the new DNS resolver schema |
kmod-tun |
any | for the TUN interface |
jq |
any | URL-encoding of server tags in latency probes |
| Free overlay | ~250 KB | runtime config + logs live in /tmp |
The installer pre-checks the router (OpenWrt version, sing-box availability in
feeds, free overlay, kmod-tun), then downloads the latest .ipk and runs
opkg install. Run it on the router itself:
# Latest release — auto-detected from GitHub
sh -c "$(wget -qO- https://github.com/DmitriyStroganov/LAS/releases/download/v1.0.9/install.sh)"
# Or pin a specific version:
sh install.sh --version 1.0.9
# Or check the environment without installing anything:
sh -c "$(wget -qO- https://github.com/DmitriyStroganov/LAS/releases/download/v1.0.9/install.sh)" -- --checkPre-check output (typical healthy router):
== Environment check ==
✓ OpenWrt 24.10.2
✓ Architecture: mipsel_24kc
✓ kmod-tun loaded
✓ sing-box 1.13.13 already installed
✓ Free overlay: 27 MB
== Pre-check complete ==
If a check fails the installer explains what to fix (add a feed, free overlay
space, upgrade OpenWrt) and exits without touching opkg.
Download from Releases, copy to the router, then:
scp luci-app-singbox_*.ipk root@router:/tmp/
ssh root@router opkg install /tmp/luci-app-singbox_*.ipkopkg will pull sing-box and the rest of the DEPENDS automatically from
your configured feeds. The service is not enabled by default.
# Inside your OpenWrt source tree
git clone https://github.com/DmitriyStroganov/LAS.git \
feeds/luci/applications/luci-app-singbox
./scripts/feeds update luci
./scripts/feeds install luci-app-singbox
make package/luci-app-singbox/compile V=s
# Result: bin/packages/<arch>/luci/luci-app-singbox_*.ipkCopy the contents of files/ onto the router preserving paths:
scp -r files/* root@router:/
ssh root@router '
chmod +x /etc/init.d/sing-box \
/etc/uci-defaults/singbox-migrate \
/usr/share/singbox/*.sh \
/usr/share/ucode/rpc/singbox.uc
/etc/init.d/rpcd restart
'If
/etc/sing-box/config.jsonalready exists from a previous raw sing-box setup, thesingbox-migrateuci-defaults script imports the first REALITY outbound into UCI on next boot.
- Open LuCI → Services → sing-box.
- Open the Servers tab. Replace the disabled example (
example-server) with your own REALITY credentials, or import them from a subscription URL (see Adding a server below). - Open the Routing tab. Enable the GeoSite presets you need (YouTube, Telegram, etc.). Leave the default action on Direct so only blocked services go through VPN.
- Hit Apply Config on the Overview tab. The script validates the generated config and restarts sing-box.
- Verify on the same tab: status dot turns green, traffic counters move, an active server appears next to "Active server".
Open Services → sing-box → Servers. Two paths:
A. Manual entry — click Add Server at the bottom of the grid. Fill in:
| Field | What to put | Example |
|---|---|---|
Name |
Any short identifier (latin, no spaces) | frankfurt-1 |
Address |
Hostname or IP of your REALITY upstream | 1.2.3.4 |
Port |
Usually 443 | 443 |
UUID |
Per-account UUID from your provider | 00000000-0000-0000-0000-000000000000 |
Flow |
xtls-rprx-vision for modern servers, None for older |
xtls-rprx-vision |
SNI |
The decoy hostname REALITY will masquerade as | www.microsoft.com |
uTLS Fingerprint |
Browser to impersonate at the TLS layer | chrome |
REALITY Public Key |
base64url key from your server config | REPLACE_WITH_YOUR_REALITY_PUBLIC_KEY |
REALITY Short ID |
8-16 hex chars from your server config | 0123456789abcdef |
Click Save then Save & Apply at the bottom — this regenerates the config and restarts sing-box.
B. Subscription import — see next section.
Once at least two servers are enabled, an urltest group called auto is
created automatically and picks the lowest-latency one. The Overview tab shows
which one is currently active.
If your provider gives you a subscription URL (a link that returns base64-encoded
vless://... lines), do this:
- Servers tab → scroll to "Import Subscription" section.
- Paste the URL into Subscription URL.
- Click Import Servers. The backend fetches the URL, base64-decodes it,
parses each
vless://link, and adds each as a newconfig serversection. - Already-known servers (matched by name) are skipped — re-importing the same URL is idempotent.
- The URL is persisted in UCI, so the next time you open Settings → Subscription → Subscription URL it will be pre-filled.
- A success notification lists the imported names; the page reloads and the new servers appear in the grid.
To enable automatic background refresh, flip Settings → Subscription →
Auto Update and set an Update Interval (e.g. 24h). A cron-driven
re-import will then keep the server list in sync with your provider.
Open Services → sing-box → Routing.
A. One-click GeoSite presets — click Add Rule (manual). A modal opens with two halves:
- Top half — Quick add, GeoSite presets — scrollable list with checkboxes
for common services (YouTube, Google, Instagram, Twitter/X, Facebook,
Telegram, OpenAI, SoundCloud, Spotify, Discord, Twitch, Netflix, GitHub,
Microsoft, Apple, Amazon, WhatsApp, plus an Ad Block entry that ships with
Action = Block). - Bottom half — Custom rule — for when the preset list doesn't have what you need.
Tick the presets you want, optionally fill the custom-rule form, then click
Add Selected / Custom. Already-installed presets are silently skipped
(matched by match value), so the modal is also idempotent.
B. Inline edit — every rule row in the grid is editable in place:
- Enabled — toggle without leaving the page.
- Type —
GeoSite,IP CIDR, orDomain. - Match — for GeoSite, the rule-set tag (e.g.
geosite-youtube); for IP CIDR, one or more CIDRs space-separated (e.g.91.108.0.0/16 149.154.160.0/20); for Domain, one or more domains (e.g.example.com .blocked.org). - Action —
VPN(route through proxy),Direct(bypass proxy),Block(reject — uses the modernaction:rejectrule, not the deprecatedblockoutbound).
Don't forget Save & Apply to regenerate the config and reload sing-box.
Out of the box (right after install) the package ships with a sensible set of defaults so the most common case — "route blocked services through VPN, everything else direct" — works once you add a server:
- Default action (
general.final_outbound) = Direct. Anything that doesn't match a rule goes through the regular ISP path, not the VPN. - TUN stack =
gvisor(works on every architecture including MIPS). - Auto-route = on — sing-box captures all egress traffic via
sing-tunand decides per-rule. - Strict-route = on — prevents local subnet leaks.
- DNS — direct DNS =
auto(uses the ISP resolver discovered via DHCP); tunnel DNS =8.8.8.8(only used for domains matched by VPN rules). - Clash API = on, port
9090on127.0.0.1only — drives the Overview dashboard and the Servers latency probes. - urltest = on — when 2+ servers are enabled, the lowest-latency one is selected automatically (3-minute test interval, 50 ms tolerance).
- Log rotation = on, max 1 MB, trimmed every 10 minutes by cron.
Without rotation
/tmp/sing-box.logwould fill the tmpfs. - Shipped routing rules (all enabled by default):
- YouTube, Google, Instagram, Twitter/X, Facebook, Telegram (domains + IPs), OpenAI, SoundCloud → VPN
- Ad Block (
geosite-category-ads-all) → Block
- Shipped example server (
example-server) is disabled with placeholder credentials — replace it or delete it before going live.
To change any of these defaults use the Settings tab or edit
/etc/config/singbox directly.
┌──────────────────────────────────────┐
│ LuCI browser UI │
│ overview / servers / routing / │
│ logs / settings (JS views) │
└─────────────────┬────────────────────┘
│ fs.exec() + UCI get/set
▼
┌────────────────────────────────────────────────────────────────────┐
│ Backend (shell + ucode) │
│ │
│ generate-config.sh status.sh update-subscription.sh │
│ │ │ │ │
│ │ UCI → JSON │ Clash API │ base64-decode │
│ ▼ ▼ ▼ │
│ /etc/sing-box/ JSON status uci add server │
│ config.json for dashboard │
│ │ │
│ │ sing-box check ◄── validation gate │
│ ▼ │
│ /etc/init.d/sing-box (procd) │
└─────────────────────────────────┬───────────────────────────────────┘
▼
┌────────────────────────┐
│ sing-box │
│ ┌──────────────────┐ │
│ │ tun-in inbound │ │
│ └────────┬─────────┘ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ urltest / vless │──┼──► REALITY upstreams
│ └────────┬─────────┘ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Clash API :9090 │──┼──► status.sh / dashboard
│ └──────────────────┘ │
└────────────────────────┘
UCI is the single source of truth. All persistent state lives in /etc/config/singbox.
The generate-config.sh script reads UCI, renders sing-box JSON, validates it with
sing-box check, and only then atomically replaces /etc/sing-box/config.json. A typo or a
missing field never makes it to the running daemon.
The package ships a working example config at files/etc/config/singbox.
Below is the schema. Anonymous sections can be repeated.
| Section | Type | Key options |
|---|---|---|
general |
(named) | enabled, log_level, log_output, stack (gvisor/system), tun_address, tun_mtu, auto_route, strict_route, clash_api, clash_api_port, final_outbound |
urltest |
(named) | enabled, interval, tolerance, test_url |
dns |
(named) | direct_server (or auto = ISP), tunnel_server, strategy (prefer_ipv4 / prefer_ipv6 / ipv4_only) |
server |
(anonymous) | name, enabled, type=vless, server, port, uuid, flow, sni, utls_fingerprint, reality_public_key, reality_short_id |
rule |
(anonymous) | name, enabled, type (geosite / ip_cidr / domain), match, outbound (auto / direct / block) |
subscription |
(named) | enabled, url, auto_update, update_interval |
Example:
config server
option name 'my-server'
option enabled '1'
option type 'vless'
option server 'example.com'
option port '443'
option uuid '00000000-0000-0000-0000-000000000000'
option flow 'xtls-rprx-vision'
option sni 'www.microsoft.com'
option utls_fingerprint 'chrome'
option reality_public_key 'base64url-encoded-key'
option reality_short_id '0123456789abcdef'
config rule
option name 'YouTube'
option enabled '1'
option type 'geosite'
option match 'geosite-youtube'
option outbound 'auto'
The RPC layer (ubus call rpc.singbox.<method>) is documented in the header comment of
files/usr/share/ucode/rpc/singbox.uc.
| Protocol | Status |
|---|---|
| VLESS + REALITY | ✅ Full support |
| VMess | 🚧 Planned |
| Trojan | 🚧 Planned |
| Shadowsocks | 🚧 Planned |
| Hysteria2 | ❌ Not supported (UDP blocked by DPI in most censoring networks) |
Adding a protocol means: a new option type in servers.js, a builder in generate-config.sh,
and a parser branch in update-subscription.sh.
.
├── Makefile # OpenWrt package definition
├── files/
│ ├── etc/
│ │ ├── config/singbox # default UCI config (shipped, replaceable)
│ │ ├── init.d/sing-box # procd init script
│ │ └── uci-defaults/singbox-migrate # one-shot migration from raw sing-box
│ └── usr/
│ └── share/
│ ├── luci/menu.d/ # LuCI menu registration
│ ├── rpcd/acl.d/ # rpcd ACL
│ ├── singbox/
│ │ ├── generate-config.sh # UCI → JSON (validated)
│ │ ├── status.sh # Clash API client (status/servers/test)
│ │ └── update-subscription.sh # base64 VLESS import
│ └── ucode/rpc/singbox.uc # ubus RPC surface (status/test/logs/...)
│ └── www/luci-static/resources/view/singbox/
│ ├── overview.js # dashboard
│ ├── servers.js # CRUD + import
│ ├── routing.js # rules + presets
│ ├── logs.js # live log viewer
│ └── settings.js # advanced settings
├── po/ # translation templates (empty by default)
├── README.md
└── LICENSE
The shell scripts target POSIX sh (verified with dash -n). Validate before pushing:
# POSIX shell syntax
for f in files/etc/init.d/sing-box files/etc/uci-defaults/* files/usr/share/singbox/*.sh; do
sh -n "$f" && dash -n "$f"
done
# JSON
python3 -c "import json,glob; [json.load(open(f)) for f in glob.glob('files/**/*.json', recursive=True)]"
# ucode (needs the tree-sitter-ucode grammar, or build ucode from OpenWrt source)
ucode -P files/usr/share/ucode/rpc/singbox.uc # parse-only# Create po/templates/singbox.pot from translatable strings, then:
msginit -l ru_RU.UTF-8 -i po/templates/singbox.pot -o po/ru/singbox.po
# Edit po/ru/singbox.po, rebuild the package.| Symptom | Likely cause / fix |
|---|---|
| Status dot stays red after Apply | Check Logs tab. The most common cause is a wrong REALITY public_key or sni. |
sing-box check failed on Apply |
The error message is shown inline. Usually a missing required option (UUID/port). |
Latency shows - for all servers |
The Clash API is not reachable. Confirm clash_api = 1 and the port is open locally. |
| TUN never comes up | kmod-tun not installed, or auto_route disabled while no manual routes exist. |
| Subscription import reports 0 imported | All server names already exist in UCI (import is deduplicated by fragment/name). |
| Service won't survive reboot | Run /etc/init.d/sing-box enable. The migrate hook does this only once on first boot. |
Logs: LuCI → Services → sing-box → Logs, or logread -e sing-box / tail -f /tmp/sing-box.log.
PRs welcome. Please:
- Fork → feature branch (
feat/…,fix/…,docs/…). - Keep shell POSIX (
sh/dash), no bashisms. - Run the syntax checks above before pushing.
- Don't commit translation
.pofiles for languages you don't actually translate — they rot. - If you add a protocol, update the Supported protocols table and the README schema.
See CONTRIBUTING.md for details.
This package was designed and iteratively debugged on a real OpenWrt router
with extensive help from GLM-5.2 (Zhipu AI) — including the
generate-config.sh UCI → JSON pipeline, the procd init script with
USB-wait, the rpcd ACL, the ucode rpcd backend, the OpenWrt tar.gz .ipk
packaging, the Streaming-endpoint-safe http_get for the Clash API,
the action:reject migration off the deprecated block outbound, the
log rotation machinery, and the LuCI front-end (E() semantics,
form.GridSection quirks, TypedSection vs NamedSection for
anonymous UCI sections, view.extend module-load order).
The development conversation (with all its false starts, debug sessions, and incremental fixes) is captured in the commit history — 9 tagged releases in one day.
Other standing-on-the-shoulders-of:
- SagerNet / sing-box — the proxy core itself, especially the brilliant REALITY transport.
- OpenWrt LuCI — the web framework this package plugs into.
- sing-geosite — the auto-updated domain rule-sets.
GPL-2.0-or-later — same as the OpenWrt LuCI framework.
PKGNICK/PKG_MAINTAINER live in the Makefile. Set them in your fork before
publishing a release.




